用Java學資料結構-佇列Queue
基本概念
Queue 是一種線性的資料結構
一定要遵守 FIFO (First In First Out) 原則
與 Stack 相反,Stack 是 LIFO (Last In First Out)
就好像是排隊的概念,先來的先服務
Queue定義與特性
元素只能在佇列的一端插入,稱為隊尾(rear/tail) -> 後面來的人只能排在後面
元素只能在佇列的另一端刪除,稱為隊首(front/head) -> 先來的人,可以先被服務
隊列的操作方式是先進先出(FIFO)
graph LR
A[入隊/Enqueue] --> B[4] --> C[3] --> D[2] --> E[1] --> F[出隊/Dequeue]
style A fill:#796400
style F fill:#9F5000copy
應用場景實例
當我們的應用場景必須遵守先進先出的規則時,就可以使用 Queue 來實現。
或是有需要做緩衝區的地方,也可以使用 Queue 來實現。
Queue類型
普通佇列(FIFO Queue)
graph LR
A[入隊/Enqueue] --> B[4] --> C[3] --> D[2] --> E[1] --> F[出隊/Dequeue]
style A fill:#796400
style F fill:#9F5000copy
雙端佇列(Deque)
雙端佇列是一種可以從兩端進行操作的佇列,可以從隊首或隊尾進行插入和刪除操作。
他結合了 Stack 和 Queue 的特性,可以實現 LIFO 和 FIFO 兩種操作。
graph LR
A[隊首插入/InsertFront、隊首刪除/DeleteFront] --> B[1] --> C[2] --> D[3] --> E[隊尾插入/InsertLast、隊尾刪除/DeleteLast]
E --> D
D --> C
C --> B
B --> A
style A fill:#796400
style E fill:#9F5000copy
優先佇列(Priority Queue)
每個元素都有一個優先級
高優先級的元素先出隊
內部實現方式通常是堆(heap)
預設會使用最小堆,也可以使用最大堆
例如在排工作任務時,就可以使用優先佇列,先處理優先級高的任務。
graph TD
subgraph 優先佇列示意圖
A[10 優先級:1] --> B[8 優先級:2]
A --> C[7 優先級:2]
B --> D[5 優先級:3]
B --> E[4 優先級:3]
C --> F[3 優先級:3]
C --> G[2 優先級:3]
style A fill:#842B00,stroke:#333
style B fill:#796400,stroke:#333
style C fill:#796400,stroke:#333
endcopy
sequenceDiagram
participant 入佇列
participant PriorityQueue
participant 出佇列
入佇列->>PriorityQueue: 加入(7, 優先級:2)
入佇列->>PriorityQueue: 加入(10, 優先級:1)
入佇列->>PriorityQueue: 加入(3, 優先級:3)
Note over PriorityQueue: 根據優先級自動排序
出佇列->>PriorityQueue: poll()
PriorityQueue-->>出佇列: 返回 10 (最高優先級)
出佇列->>PriorityQueue: poll()
PriorityQueue-->>出佇列: 返回 7 (次高優先級)copy
循環佇列(Circular Queue)
是環狀結構、固定大小的佇列
頭尾相接,形成一個環
這樣可以充分利用空間,避免浪費
使用頭和尾來來判斷元素的位置
graph TD
subgraph 循環佇列
A[0] --> B[1]
B --> C[2]
C --> D[3]
D --> E[4]
E --> A
end
style A fill:#f96
style E fill:#96fcopy
阻塞佇列(BlockingQueue)
可以支援多執行緒的佇列
當佇列是空的時候,從佇列中取元素的操作會被阻塞
當佇列是滿的時候,往佇列中加入元素的操作會被阻塞
這樣可以避免多執行緒的競爭問題
sequenceDiagram
participant Producer
participant BlockingQueue
participant Consumer
Producer->>BlockingQueue: put(task)
Note right of BlockingQueue: 如果佇列滿,則阻塞
BlockingQueue-->>Consumer: take()
Note left of BlockingQueue: 如果佇列空,則阻塞copy
用 Java 實做基本操作與實作
public class SimpleQueue {
private int [] elements;
private int front;
private int rear;
private int size;
public SimpleQueue (int capacity) {
elements = new int [capacity];
front = 0 ;
rear = -1 ;
size = 0 ;
}
}copy
新增元素(enqueue/offer)
public void enqueue (int element) {
if (size == elements.length) {
throw new IllegalStateException ("Queue is full" );
}
rear = (rear + 1 ) % elements.length;
elements[rear] = element;
size++;
}copy
移除元素(dequeue/poll)
public int dequeue () {
if (size == 0 ) {
throw new IllegalStateException ("Queue is empty" );
}
int element = elements[front];
front = (front + 1 ) % elements.length;
size--;
return element;
}copy
查看隊首(peek)
public int peek () {
if (size == 0 ) {
throw new IllegalStateException ("Queue is empty" );
}
return elements[front];
}copy
查看佇列大小(size)
public int size () {
return size;
}copy
檢查是否為空(isEmpty)
public boolean isEmpty () {
return size == 0 ;
}copy
測試程式
public class SimpleQueueTest {
public static void main (String[] args) {
SimpleQueue queue = new SimpleQueue (5 );
queue.enqueue(1 );
queue.enqueue(2 );
queue.enqueue(3 );
queue.enqueue(4 );
queue.enqueue(5 );
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.peek());
System.out.println(queue.size());
System.out.println(queue.isEmpty());
}
}copy
操作時間複雜度
新增元素(enqueue/offer):O(1)
移除元素(dequeue/poll):O(1)
查看隊首(peek):O(1)
查看佇列大小(size):O(1)
檢查是否為空(isEmpty):O(1)
進階應用 (擴展知識)
生產者消費者模式
生產者消費者模式是一種經典的多執行緒設計模式
主要就是分為 生產者(Producer) 和 消費者(Consumer) 兩個角色
生產者
消費者
這樣可以有效的解耦生產者和消費者,提高系統的穩定性和可擴展性
簡單的生產者消費者模式範例public class ProducerConsumer {
private BlockingQueue<Integer> queue = new ArrayBlockingQueue <>(10 );
public void producer () {
while (true ) {
try {
queue.put(1 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void consumer () {
while (true ) {
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}copy
BFS演算法實現
BFS (Breadth First Search) 是一種廣度優先搜索演算法
是一種圖形與樹的搜索演算法,常用於圖形的最短路徑問題
核心概念是 逐層擴展 ,先擴展所有的鄰居節點,再擴展鄰居的鄰居節點
而 Queue 是 BFS 演算法的重要工具,用來存儲待擴展的節點
BFS 的基本概念
廣度優先,從起點開始,先訪問當前節點的所有鄰居節點,再逐層深入,直到所有可達的節點都被訪問過
FIFO 特性,BFS是先進先出的,也跟 Queue 的特性一樣
graph LR
A[Alice] --- B[Bob]
A --- C[Charlie]
B --- D[David]
B --- E[Eve]
C --- F[Frank]
E --- F
style A fill:#5B5B00,stroke:#333
style B fill:#844200,stroke:#333
style C fill:#642100,stroke:#333
style D fill:#548C00,stroke:#333
style E fill:#548C00,stroke:#333
style F fill:#548C00,stroke:#333
%% 關係說明
classDef direct fill:#9cf;
classDef secondDegree fill:#cfc;
classDef root fill:#f96;copy
如果我想要寫一個推薦好友的功能,我們可以使用 BFS 演算法,這樣推薦的好友清單就會優先推薦與你關係最近的好友。
public class SocialNetworkRecommendation {
static class User {
String id;
String name;
User(String id, String name) {
this .id = id;
this .name = name;
}
@Override
public String toString () {
return name;
}
}
private Map<String, User> users;
private Map<User, List<User>> network;
public SocialNetworkRecommendation () {
users = new HashMap <>();
network = new HashMap <>();
initializeUsers();
initializeNetwork();
}
private void initializeUsers () {
addUser("1" , "Alice" );
addUser("2" , "Bob" );
addUser("3" , "Charlie" );
addUser("4" , "David" );
addUser("5" , "Eve" );
addUser("6" , "Frank" );
}
private void addUser (String id, String name) {
User user = new User (id, name);
users.put(name.toLowerCase(), user);
}
private void initializeNetwork () {
User alice = users.get("alice" );
User bob = users.get("bob" );
User charlie = users.get("charlie" );
User david = users.get("david" );
User eve = users.get("eve" );
User frank = users.get("frank" );
network.put(alice, Arrays.asList(bob, charlie));
network.put(bob, Arrays.asList(alice, david, eve));
network.put(charlie, Arrays.asList(alice, frank));
network.put(david, Arrays.asList(bob));
network.put(eve, Arrays.asList(bob, frank));
network.put(frank, Arrays.asList(charlie, eve));
}
public void recommendFriendsForUser (String userName) {
User user = users.get(userName.toLowerCase());
if (user == null ) {
System.out.println("找不到用戶: " + userName);
return ;
}
Queue<User> queue = new LinkedList <>();
Map<User, Integer> distance = new HashMap <>();
Set<User> visited = new HashSet <>();
List<User> recommendations = new ArrayList <>();
queue.offer(user);
distance.put(user, 0 );
visited.add(user);
System.out.println("\n為 " + user + " 尋找好友推薦:" );
while (!queue.isEmpty()) {
User current = queue.poll();
int currentDistance = distance.get(current);
for (User friend : network.getOrDefault(current, Collections.emptyList())) {
if (!visited.contains(friend)) {
queue.offer(friend);
visited.add(friend);
distance.put(friend, currentDistance + 1 );
if (currentDistance + 1 > 1 ) {
recommendations.add(friend);
}
}
}
}
if (recommendations.isEmpty()) {
System.out.println("沒有可推薦的好友" );
} else {
System.out.println("好友推薦清單(不包含已是好友的用戶):" );
for (int i = 0 ; i < recommendations.size(); i++) {
User recommended = recommendations.get(i);
System.out.printf("第%d優先順位 - %s (距離: %d度關係)%n" ,
i + 1 , recommended, distance.get(recommended));
}
}
}
public static void main (String[] args) {
SocialNetworkRecommendation system = new SocialNetworkRecommendation ();
Scanner scanner = new Scanner (System.in);
while (true ) {
System.out.print("\n請輸入要查詢的用戶名稱 (輸入exit退出): " );
String input = scanner.nextLine().trim();
if (input.equalsIgnoreCase("exit" )) {
break ;
}
system.recommendFriendsForUser(input);
}
scanner.close();
}
}copy
稍微解釋一下這個程式
首先我們建立了一個用戶類 User,用來表示用戶的基本信息
然後我們建立了一個 SocialNetworkRecommendation 類,用來表示社交網路推薦系統
在這個類中,我們建立了用戶列表 users 和好友網路 network
用戶列表 users 用來存放所有用戶的信息,好友網路 network 用來存放用戶之間的好友關係
在 initializeUsers 方法中,我們初始化了用戶列表
在 initializeNetwork 方法中,我們初始化了好友網路
在 recommendFriendsForUser 方法
Queue 用來存放待處理的用戶,有 FIFO 的特性
LinkedList 適合用來實現 Queue,頻繁的插入和刪除操作
Map 用來存放每個用戶與起始用戶的距離
Set 用來存放已經處理過的用戶,因為不需要重複的值,並且查找速度快
List 用來存放推薦的用戶,且要保持順序
在 recommendFriendsForUser 方法中,我們使用 BFS 演算法來尋找好友推薦
首先將起始用戶加入佇列、設定距離為0,並標記為已處理
當佇列不為空時,持續處理
取出佇列中的用戶,取得該用戶與起始用戶的距離
遍歷好友網路中的所有用戶
如果該用戶尚未處理過,將該用戶加入佇列,待會要遍歷這個用戶的好友
把這個好友標注為已處理,設定這個好友與起始用戶的距離
只推薦非直接好友的用戶(即距離大於1的用戶)
任務排程系統
我們來做一個任務排程系統
排序的規則
任務的優先級越高,越先執行
如果優先級相同,則按照任務的到達時間先後執行
我們會使用 PriorityQueue 來實現這個系統
PriorityQueue 的特性
是基於堆(heap)的一種優先佇列
預設是最小堆,也可以自定義排序規則
優先級高的元素先出隊
內部實現是一個完全二叉樹
public class TaskSchedulingSystem {
static class Task implements Comparable <Task> {
private String name;
private int priority;
private int executionTime;
private long submissionTime;
public Task (String name, int priority, int executionTime) {
this .name = name;
this .priority = priority;
this .executionTime = executionTime;
this .submissionTime = System.currentTimeMillis();
}
@Override
public int compareTo (Task other) {
if (this .priority != other.priority) {
return Integer.compare(this .priority, other.priority);
}
return Long.compare(this .submissionTime, other.submissionTime);
}
@Override
public String toString () {
return String.format("Task[%s, 優先級=%d, 執行時間=%ds]" ,
name, priority, executionTime);
}
}
static class TaskScheduler {
private PriorityQueue<Task> taskQueue;
public TaskScheduler () {
taskQueue = new PriorityQueue <>();
}
public void addTask (Task task) {
taskQueue.offer(task);
System.out.println("新增任務: " + task);
}
public void executeTasks () {
System.out.println("\n開始執行任務..." );
int totalTasks = taskQueue.size();
int completedTasks = 0 ;
while (!taskQueue.isEmpty()) {
Task currentTask = taskQueue.poll();
completedTasks++;
System.out.printf("\n執行任務 (%d/%d): %s\n" ,
completedTasks, totalTasks, currentTask);
try {
Thread.sleep(currentTask.executionTime * 1000 );
System.out.println("任務完成!" );
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("\n所有任務已完成!" );
}
}
public static void main (String[] args) {
TaskScheduler scheduler = new TaskScheduler ();
scheduler.addTask(new Task ("備份資料庫" , 1 , 5 ));
scheduler.addTask(new Task ("更新快取" , 2 , 2 ));
scheduler.addTask(new Task ("發送電子報" , 3 , 3 ));
scheduler.addTask(new Task ("日誌清理" , 2 , 1 ));
scheduler.addTask(new Task ("系統掃描" , 1 , 4 ));
scheduler.executeTasks();
}
}copy
銀行排隊系統
再來我們來做一個銀行排隊系統
銀行排隊系統的規則
可指定有多少個服務櫃檯
客戶到達時,會取號,VIP客戶優先
每個服務櫃檯只能服務一個客戶
模擬服務時間,每個客戶服務時間為1秒
我們會使用 PriorityQueue 來實現這個系統
public class BankQueueSystem {
static class Customer implements Comparable <Customer> {
private String number;
private boolean isVip;
private long arrivalTime;
public Customer (String number, boolean isVip) {
this .number = number;
this .isVip = isVip;
this .arrivalTime = System.currentTimeMillis();
}
@Override
public int compareTo (Customer other) {
if (this .isVip != other.isVip) {
return this .isVip ? -1 : 1 ;
}
return Long.compare(this .arrivalTime, other.arrivalTime);
}
@Override
public String toString () {
return String.format("號碼:%s %s" , number, isVip ? "(VIP)" : "" );
}
}
static class BankCounter {
private String name;
private Customer currentCustomer;
public BankCounter (String name) {
this .name = name;
}
public void serveCustomer (Customer customer) {
this .currentCustomer = customer;
System.out.printf("%s開始服務 %s\n" , name, customer);
}
public void finishService () {
System.out.printf("%s完成服務 %s\n" , name, currentCustomer);
this .currentCustomer = null ;
}
public boolean isAvailable () {
return currentCustomer == null ;
}
}
private PriorityQueue<Customer> waitingQueue;
private List<BankCounter> counters;
private int normalNumber = 1 ;
private int vipNumber = 1 ;
public BankQueueSystem (int counterCount) {
this .waitingQueue = new PriorityQueue <>();
this .counters = new ArrayList <>();
for (int i = 0 ; i < counterCount; i++) {
counters.add(new BankCounter ("櫃檯" + (i + 1 )));
}
}
public void addCustomer (boolean isVip) {
String number;
if (isVip) {
number = String.format("V%03d" , vipNumber++);
} else {
number = String.format("N%03d" , normalNumber++);
}
Customer customer = new Customer (number, isVip);
waitingQueue.offer(customer);
System.out.printf("新客戶取號: %s\n" , customer);
}
public void processQueue () {
System.out.println("\n開始處理排隊..." );
while (!waitingQueue.isEmpty()) {
for (BankCounter counter : counters) {
if (counter.isAvailable() && !waitingQueue.isEmpty()) {
Customer customer = waitingQueue.poll();
counter.serveCustomer(customer);
try {
Thread.sleep(1000 );
counter.finishService();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
System.out.println("所有客戶已處理完畢!" );
}
public static void main (String[] args) {
BankQueueSystem bank = new BankQueueSystem (3 );
bank.addCustomer(false );
bank.addCustomer(true );
bank.addCustomer(false );
bank.addCustomer(true );
bank.addCustomer(false );
bank.processQueue();
}
}copy