一、隊列的概念:
隊列(簡稱作隊,Queue)也是一種特殊的線性表,隊列的數據元素以及數據元素間的邏輯關系和線性表完全相同,其差別是線性表允許在任意位置插入和刪除,而隊列只允許在其一端進行插入操作在其另一端進行刪除操作。
隊列中允許進行插入操作的一端稱為隊尾,允許進行刪除操作的一端稱為隊頭。隊列的插入操作通常稱作入隊列,隊列的刪除操作通常稱作出隊列。
下圖是一個依次向隊列中插入數據元素a0,a1,...,an-1后的示意圖:
上圖中,a0是當前 隊頭數據元素,an-1是當前 隊尾數據元素。
為了避免當只有一個元素時,對頭和隊尾重合使得處理變得麻煩,所以引入兩個指針:front指針指向隊頭元素,rear指針指向隊尾元素的下一個位置,這樣的話,當front指針等於rear時,此隊列不是還剩一個元素,而是空隊列。
二、隊列的抽象數據類型:
數據集合:
隊列的數據集合可以表示為a0,a1,…,an-1,每個數據元素的數據類型可以是任意的類型。
操作集合:
(1)入隊列append(obj):把數據元素obj插入隊尾。
(2)出隊列delete():把隊頭數據元素刪除並由函數返回。
(3)取隊頭數據元素getFront():取隊頭數據元素並由函數返回。
(4)非空否isEmpty():非空否。若隊列非空,則函數返回false,否則函數返回true。
三、循環順序隊列:
線性表有順序存儲和鏈式存儲,隊列是一種特殊的線性表,同樣也存在這兩種存儲方式。我們先來看一下隊列的順序存儲。
1、順序隊列的“假溢出”:
上圖中,front指針指向隊頭元素,rear指針指向隊尾元素的下一個位置。圖(d)中b、c、d出隊后,front指針指向元素e,rear指針在數組外面。假設這個隊列的總個數不超過5個,但目前如果接着入隊的話,因數組末尾元素已經被占用,再向后加就會產生數組越界的錯誤,可實際上隊列在下標為0、1、2、3、4的地方還是空閑的,我們把這種現象叫做“假溢出”。
2、循環順序隊列:
所以解決假溢出的辦法就是后面滿了,就再從頭開始,也就是頭尾相接的循環。我們把隊列的這種邏輯上首尾相連的順序存儲結構稱為循環隊列。
如何判斷循環隊列究竟是空的還是滿的:
現在問題又來了,我們之前說,空隊列時,front指針等於rear指針,那么現在循環隊列滿的時候,也是front等於rear,那么如何判斷循環隊列究竟是空的還是滿的?有如下辦法:
- 辦法1:設置一個標志位flag。初始時置flag=0;每當入隊列操作成功就置flag=1;每當出隊列操作成功就置flag=0。則隊列空的判斷條件為:rear == front && tag==0;隊列滿的判斷條件為:rear = = front && tag= =1。
- 辦法2:保留一個元素的存儲空間。此時,隊列滿時的判斷條件為 (rear + 1) % maxSize == front;隊列空的判斷條件還是front == rear。
- 辦法3:設計一個計數器count,統計隊列中的元素個數。此時,隊列滿的判斷條件為:count > 0 && rear == front ;隊列空的判斷條件為count == 0。
我們在接下來的代碼中采用方法3來實現。
3、代碼實現:(循環順序隊列的創建)
(1)Queue.java:(隊列接口)
1 //隊列接口 2 public interface Queue { 3 4 //入隊 5 public void append(Object obj) throws Exception; 6 7 //出隊 8 public Object delete() throws Exception; 9 10 //獲得隊頭元素 11 public Object getFront() throws Exception; 12 13 //判斷對列是否為空 14 public boolean isEmpty(); 15 }
(2)CircleSequenceQueue.java:(循環順序隊列)
1 //循環順序隊列 2 public class CircleSequenceQueue implements Queue { 3 4 static final int defaultSize = 10; //默認隊列的長度 5 int front; //隊頭 6 int rear; //隊尾 7 int count; //統計元素個數的計數器 8 int maxSize; //隊的最大長度 9 Object[] queue; //隊列 10 11 public CircleSequenceQueue() { 12 init(defaultSize); 13 } 14 15 public CircleSequenceQueue(int size) { 16 init(size); 17 } 18 19 public void init(int size) { 20 maxSize = size; 21 front = rear = 0; 22 count = 0; 23 queue = new Object[size]; 24 } 25 26 @Override 27 public void append(Object obj) throws Exception { 28 // TODO Auto-generated method stub 29 if (count > 0 && front == rear) { 30 throw new Exception("隊列已滿!"); 31 } 32 queue[rear] = obj; 33 rear = (rear + 1) % maxSize; 34 count++; 35 } 36 37 @Override 38 public Object delete() throws Exception { 39 // TODO Auto-generated method stub 40 if (isEmpty()) { 41 throw new Exception("隊列為空!"); 42 } 43 Object obj = queue[front]; 44 front = (front + 1) % maxSize; 45 count--; 46 return obj; 47 } 48 49 @Override 50 public Object getFront() throws Exception { 51 // TODO Auto-generated method stub 52 if (!isEmpty()) { 53 return queue[front]; 54 } else { 55 return null; 56 } 57 } 58 59 @Override 60 public boolean isEmpty() { 61 // TODO Auto-generated method stub 62 return count == 0; 63 } 64 65 }
(3)Test.java:
1 public class Test { 2 public static void main(String[] args) throws Exception { 3 4 CircleSequenceQueue queue = new CircleSequenceQueue(); 5 queue.append("a"); 6 queue.append("b"); 7 queue.append("c"); 8 queue.append("d"); 9 queue.append("e"); 10 queue.append("f"); 11 12 queue.delete(); 13 queue.delete(); 14 15 queue.append("g"); 16 17 while (!queue.isEmpty()) { 18 System.out.println(queue.delete()); 19 } 20 } 21 }
運行效果:
4、循環隊列應用:
使用順序循環隊列和多線程實現一個排隊買票的例子。而且,我們只允許這個隊伍中同時排隊的只有10個人,那就需要用到隊列了。
生產者(等候買票)
消費者 (買票離開)
這里面我們需要用到上面的Queue.java類和CircleSequenceQueue.java類。
代碼結構:
(3)WindowQueue.java:
1 //賣票窗口 2 public class WindowQueue { 3 4 //賣票的隊列 5 int maxSize = 10; 6 CircleSequenceQueue queue = new CircleSequenceQueue(maxSize); 7 int num = 0; //統計賣票的數量,一天最多賣100張票。 8 boolean isAlive = true; //判斷是否繼續賣票。 9 10 //排隊買票 11 public synchronized void producer() throws Exception { 12 if (queue.count < maxSize) { 13 queue.append(num++); //等待買票的數量加1 14 System.out.println("第" + num + "個客戶排隊等待買票!"); 15 this.notifyAll();//喚醒賣票的線程 16 } else { 17 try { 18 System.out.println("隊列已滿...請等待!"); 19 this.wait();//隊列滿時,排隊買票線程等待。 20 } catch (Exception ex) { 21 ex.printStackTrace(); 22 } 23 } 24 } 25 26 //賣票 27 public synchronized void consumer() throws Exception { 28 if (queue.count > 0) { 29 Object obj = queue.delete(); 30 int temp = Integer.parseInt(obj.toString()); 31 System.out.println("第" + (temp + 1) + "個客戶買到票離開隊列!"); 32 //如果當前隊列為空,並且賣出票的數量大於等於100,說明賣票結束 33 if (queue.isEmpty() && this.num >= 100) { 34 this.isAlive = false; 35 } 36 this.notifyAll(); //喚醒排隊買票的線程。 37 } else { 38 try { 39 System.out.println("隊列已空...請等待!"); 40 this.wait();//隊列空時,賣票線程等待。 41 } catch (Exception ex) { 42 ex.printStackTrace(); 43 } 44 } 45 } 46 }
(4)Producer.java:
1 //買票者 2 public class Producer implements Runnable { 3 4 WindowQueue queue; 5 6 public Producer(WindowQueue queue) { 7 this.queue = queue; 8 } 9 10 @Override 11 public void run() { 12 // TODO Auto-generated method stub 13 while (queue.num < 100) { 14 try { 15 queue.producer(); 16 } catch (Exception ex) { 17 ex.printStackTrace(); 18 } 19 } 20 } 21 22 }
(5)Consumer.java:
//賣票者 public class Consumer implements Runnable { WindowQueue queue; public Consumer(WindowQueue queue) { this.queue = queue; } @Override public void run() { // TODO Auto-generated method stub while (queue.isAlive) { try { queue.consumer(); } catch (Exception ex) { ex.printStackTrace(); } } } }
(6)test.java:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 5 WindowQueue queue = new WindowQueue(); 6 7 Producer p = new Producer(queue);//注意一定要傳同一個窗口對象 8 Consumer c = new Consumer(queue); 9 10 //排隊買票線程 11 Thread pThread = new Thread(p); 12 //賣票線程 13 Thread cThread = new Thread(c); 14 15 pThread.start(); //開始排隊買票 16 cThread.start(); //開始賣票 17 } 18 19 }
注意第07行的注釋。
運行效果:
四、鏈式隊列:
鏈式隊列其實就是特殊的單鏈表,只不過它只能尾進頭出而已。鏈式隊列的存儲結構如下圖所示:
1、鏈式隊列的實現:
(1)Node.java:結點類
1 //結點類 2 public class Node { 3 4 Object element; //數據域 5 Node next; //指針域 6 7 //頭結點的構造方法 8 public Node(Node nextval) { 9 this.next = nextval; 10 } 11 12 //非頭結點的構造方法 13 public Node(Object obj, Node nextval) { 14 this.element = obj; 15 this.next = nextval; 16 } 17 18 //獲得當前結點的后繼結點 19 public Node getNext() { 20 return this.next; 21 } 22 23 //獲得當前的數據域的值 24 public Object getElement() { 25 return this.element; 26 } 27 28 //設置當前結點的指針域 29 public void setNext(Node nextval) { 30 this.next = nextval; 31 } 32 33 //設置當前結點的數據域 34 public void setElement(Object obj) { 35 this.element = obj; 36 } 37 38 public String toString() { 39 return this.element.toString(); 40 } 41 }
(2)Queue.java:
1 //隊列接口 2 public interface Queue { 3 4 //入隊 5 public void append(Object obj) throws Exception; 6 7 //出隊 8 public Object delete() throws Exception; 9 10 //獲得隊頭元素 11 public Object getFront() throws Exception; 12 13 //判斷對列是否為空 14 public boolean isEmpty(); 15 }
(3)LinkQueue.java:
1 public class LinkQueue implements Queue { 2 3 Node front; //隊頭 4 Node rear; //隊尾 5 int count; //計數器 6 7 public LinkQueue() { 8 init(); 9 } 10 11 public void init() { 12 front = rear = null; 13 count = 0; 14 } 15 16 @Override 17 public void append(Object obj) throws Exception { 18 // TODO Auto-generated method stub 19 Node node = new Node(obj, null); 20 21 //如果當前隊列不為空。 22 if (rear != null) { 23 rear.next = node; //隊尾結點指向新結點 24 } 25 26 rear = node; //設置隊尾結點為新結點 27 28 //說明要插入的結點是隊列的第一個結點 29 if (front == null) { 30 front = node; 31 } 32 count++; 33 } 34 35 @Override 36 public Object delete() throws Exception { 37 // TODO Auto-generated method stub 38 if (isEmpty()) { 39 new Exception("隊列已空!"); 40 } 41 Node node = front; 42 front = front.next; 43 count--; 44 return node.getElement(); 45 } 46 47 @Override 48 public Object getFront() throws Exception { 49 // TODO Auto-generated method stub 50 if (!isEmpty()) { 51 return front.getElement(); 52 } else { 53 return null; 54 } 55 } 56 57 @Override 58 public boolean isEmpty() { 59 // TODO Auto-generated method stub 60 return count == 0; 61 } 62 63 }
(4)Test.java:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 5 LinkQueue queue = new LinkQueue(); 6 queue.append("a"); 7 queue.append("b"); 8 queue.append("c"); 9 queue.append("d"); 10 queue.append("e"); 11 queue.append("f"); 12 13 queue.delete(); 14 queue.delete(); 15 16 queue.append("g"); 17 18 while (!queue.isEmpty()) { 19 System.out.println(queue.delete()); 20 } 21 } 22 }
運行效果:
2、鏈式隊列的應用:
題目:
編寫一個判斷一個字符串是否是回文的算法。
思路:
設字符數組str中存放了要判斷的字符串。把字符數組中的字符逐個分別存入一個隊列和棧中,然后逐個出隊和出棧比較出隊的字符與出棧的字符是否相同,若全部相等則該字符串為回文。
代碼實現:
這里面需要用到上面一段中的LinkQueue類。代碼結構如下:
(4)Stack.java:棧接口
1 //棧接口 2 public interface Stack { 3 4 //入棧 5 public void push(Object obj) throws Exception; 6 7 //出棧 8 public Object pop() throws Exception; 9 10 //獲得棧頂元素 11 public Object getTop() throws Exception; 12 13 //判斷棧是否為空 14 public boolean isEmpty(); 15 }
(5)LinkStack.java:
1 public class LinkStack implements Stack { 2 3 Node head; //棧頂指針 4 int size; //結點的個數 5 6 public LinkStack() { 7 head = null; 8 size = 0; 9 } 10 11 @Override 12 public Object getTop() throws Exception { 13 // TODO Auto-generated method stub 14 return head.getElement(); 15 } 16 17 @Override 18 public boolean isEmpty() { 19 // TODO Auto-generated method stub 20 return head == null; 21 } 22 23 @Override 24 public Object pop() throws Exception { 25 // TODO Auto-generated method stub 26 if (isEmpty()) { 27 throw new Exception("棧為空!"); 28 } 29 Object obj = head.getElement(); 30 head = head.getNext(); 31 size--; 32 return obj; 33 34 } 35 36 @Override 37 public void push(Object obj) throws Exception { 38 // TODO Auto-generated method stub 39 head = new Node(obj, head); 40 size++; 41 } 42 43 }
(6)Test.java:測試類
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 5 String str1 = "ABCDCBA"; //是回文 6 String str2 = "ABCDECAB"; //不是回文 7 8 try { 9 if (Test.isHuiWen(str1)) { 10 System.out.println(str2 + ":是回文!"); 11 } else { 12 System.out.println(str2 + ":不是回文!"); 13 } 14 } catch (Exception ex) { 15 ex.printStackTrace(); 16 } 17 } 18 19 20 //方法:判斷字符串是否回文 21 public static boolean isHuiWen(String str) throws Exception { 22 int n = str.length(); 23 LinkStack stack = new LinkStack();//創建堆棧 24 LinkQueue queue = new LinkQueue();//創建隊列 25 for (int i = 0; i < n; i++) { 26 stack.push(str.subSequence(i, i + 1)); //把字符串每個字符壓進堆棧 27 queue.append(str.subSequence(i, i + 1));//把字符串每個字符壓入隊列 28 } 29 while (!queue.isEmpty() && !stack.isEmpty()) { 30 if (!queue.delete().equals(stack.pop())) { //出隊列,出棧,同時判斷是否相同 31 return false; 32 } 33 } 34 35 return true; 36 } 37 38 }
3、循環隊列和鏈式隊列的比較:
(1)從時間上看,它們的基本操作都是常數時間,即O(1)的。不過循環隊列是事先申請好空間,使用期間不釋放;而鏈式隊列,每次申請和釋放結點也會存在一定的時間開銷,如果入棧和出棧比較頻繁,則兩者還是有細微的差別。
(2)從空間上看,循環隊列必須有一個固定的長度,所以就有了存儲元素個數和空間浪費的問題。而鏈式隊列不存在這個問題,盡管它需要一個指針域,會產生一些空間上的開銷,但也可以接受。所以在空間上,鏈式隊列更加靈活。
總結:總的來說,在可以確定隊列長度的最大值的情況下,建議用循環隊列,如果你無法估計隊列的長度,那就用鏈式隊列。
五、優先級隊列:
優先級隊列是帶有優先級的隊列。
用順序存儲結構實現的優先級隊列稱作順序優先級隊列。
用鏈式存儲結構存儲的優先級隊列稱作鏈式優先級隊列。
順序優先級隊列和順序循環隊列相比主要有兩點不同:
(1)對於順序優先級隊列來說,出隊列操作不是把隊頭數據元素出隊列,而是把隊列中優先級最高的數據元素出隊列。(入隊操作沒區別)
(2)對於順序優先級隊列來說,數據元素由兩部分組成,一部分是原先意義上的數據元素,另一部分是優先級。通常設計優先級為int類型的數值,並規定數值越小優先級越高。
1、順序優先隊列的實現:
設計順序優先級隊列分為兩個類:
數據元素類
優先級隊列類
代碼實現:
(1)Element.java:
1 //優先級隊列元素類 2 public class Element { 3 4 private Object element; // 數據 5 private int priority; // 優先級 6 7 public Element(Object obj, int priority) { 8 this.element = obj; 9 this.priority = priority; 10 } 11 12 public Object getElement() { 13 return element; 14 } 15 16 public void setElement(Object element) { 17 this.element = element; 18 } 19 20 public int getPriority() { 21 return priority; 22 } 23 24 public void setPriority(int priority) { 25 this.priority = priority; 26 } 27 28 }
(2)Queue.java:
1 //隊列接口 2 public interface Queue { 3 4 //入隊 5 public void append(Object obj) throws Exception; 6 7 //出隊 8 public Object delete() throws Exception; 9 10 //獲得隊頭元素 11 public Object getFront() throws Exception; 12 13 //判斷對列是否為空 14 public boolean isEmpty(); 15 }
(3)PrioritySequenceQueue.java:
1 //優先級隊列 2 public class PrioritySequenceQueue implements Queue { 3 4 static final int defaultSize = 10; //默認隊列長度 5 int front; //隊頭 6 int rear; //隊尾 7 int count; //計數器 8 int maxSize; //隊列最大長度 9 Element[] queue; //隊列 10 11 public PrioritySequenceQueue() { 12 init(defaultSize); 13 } 14 15 public PrioritySequenceQueue(int size) { 16 init(size); 17 } 18 19 public void init(int size) { 20 maxSize = size; 21 front = rear = 0; 22 count = 0; 23 queue = new Element[size]; 24 } 25 26 @Override 27 public void append(Object obj) throws Exception { 28 // TODO Auto-generated method stub 29 //如果隊列已滿 30 if (count >= maxSize) { 31 throw new Exception("隊列已滿!"); 32 } 33 queue[rear] = (Element) obj; 34 rear++; 35 count++; 36 } 37 38 @Override 39 public Object delete() throws Exception { 40 // TODO Auto-generated method stub 41 if (isEmpty()) { 42 throw new Exception("隊列為空!"); 43 } 44 //默認第一個元素為優先級最高的。 45 Element min = queue[0]; 46 int minIndex = 0; 47 for (int i = 0; i < count; i++) { 48 if (queue[i].getPriority() < min.getPriority()) { 49 min = queue[i]; 50 minIndex = i; 51 } 52 } 53 54 //找的優先級別最高的元素后,把該元素后面的元素向前移動。 55 for (int i = minIndex + 1; i < count; i++) { 56 queue[i - 1] = queue[i]; //移動元素 57 } 58 rear--; 59 count--; 60 return min; 61 } 62 63 @Override 64 public Object getFront() throws Exception { 65 // TODO Auto-generated method stub 66 if (isEmpty()) { 67 throw new Exception("隊列為空!"); 68 } 69 //默認第一個元素為優先級最高的。 70 Element min = queue[0]; 71 int minIndex = 0; 72 for (int i = 0; i < count; i++) { 73 if (queue[i].getPriority() < min.getPriority()) { 74 min = queue[i]; 75 minIndex = i; 76 } 77 } 78 return min; 79 } 80 81 @Override 82 public boolean isEmpty() { 83 // TODO Auto-generated method stub 84 return count == 0; 85 } 86 87 }
2、代碼測試:
設計一個程序模仿操作系統的進程管理問題。進程服務按優先級高的先服務,優先級相同的先到先服務的原則管理。
模仿數據包含兩個部分:進程編號和優先級。如下有五個進程:
1 30
2 20
3 40
4 20
5 0 ----------優先級最高,先服務
(4)Test.java:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 5 PrioritySequenceQueue queue = new PrioritySequenceQueue(); 6 Element temp; 7 8 //五個進程入隊 9 queue.append(new Element(1, 30)); 10 queue.append(new Element(2, 20)); 11 queue.append(new Element(3, 40)); 12 queue.append(new Element(4, 20)); 13 queue.append(new Element(5, 0)); 14 15 //按照優先級出隊。 16 System.out.println("編號 優先級"); 17 while (!queue.isEmpty()) { 18 temp = (Element) queue.delete(); 19 System.out.println(temp.getElement() + " " + temp.getPriority()); 20 } 21 } 22 }
運行效果:









