數據結構Java實現07----隊列:順序隊列&順序循環隊列、鏈式隊列、順序優先隊列


一、隊列的概念:

  隊列(簡稱作隊,Queue)也是一種特殊的線性表,隊列的數據元素以及數據元素間的邏輯關系和線性表完全相同,其差別是線性表允許在任意位置插入和刪除,而隊列只允許在其一端進行插入操作在其另一端進行刪除操作

隊列中允許進行插入操作的一端稱為隊尾,允許進行刪除操作的一端稱為隊頭。隊列的插入操作通常稱作入隊列,隊列的刪除操作通常稱作出隊列

下圖是一個依次向隊列中插入數據元素a0,a1,...,an-1后的示意圖:

faf7d1e6-07b1-44bb-9752-5fcc113a0648

上圖中,a0是當前 隊頭數據元素,an-1是當前 隊尾數據元素。

為了避免當只有一個元素時,對頭和隊尾重合使得處理變得麻煩,所以引入兩個指針:front指針指向隊頭元素,rear指針指向隊尾元素的下一個位置,這樣的話,當front指針等於rear時,此隊列不是還剩一個元素,而是空隊列。

 

二、隊列的抽象數據類型:

數據集合:

  隊列的數據集合可以表示為a0,a1,…,an-1,每個數據元素的數據類型可以是任意的類型。

操作集合:

(1)入隊列append(obj):把數據元素obj插入隊尾。

(2)出隊列delete():把隊頭數據元素刪除並由函數返回。

(3)取隊頭數據元素getFront():取隊頭數據元素並由函數返回。

(4)非空否isEmpty():非空否。若隊列非空,則函數返回false,否則函數返回true。

 

三、循環順序隊列:

線性表有順序存儲和鏈式存儲,隊列是一種特殊的線性表,同樣也存在這兩種存儲方式。我們先來看一下隊列的順序存儲。

1、順序隊列的“假溢出”:

607ede1a-91b5-4ac9-8850-07078379fddb

上圖中,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 }

運行效果:

251fa2d6-120f-4b20-8754-f7a795b20cf1 

 

4、循環隊列應用:

  使用順序循環隊列和多線程實現一個排隊買票的例子。而且,我們只允許這個隊伍中同時排隊的只有10個人,那就需要用到隊列了。

    生產者(等候買票)

    消費者 (買票離開)

這里面我們需要用到上面的Queue.java類和CircleSequenceQueue.java類。

代碼結構:

62c99e55-fe55-41d0-8794-7a74a37ca826

(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行的注釋。

運行效果:

bb9b709e-bc10-48ab-89e6-260ca38ae5d2

  

四、鏈式隊列:

    鏈式隊列其實就是特殊的單鏈表,只不過它只能尾進頭出而已。鏈式隊列的存儲結構如下圖所示:

6c1486eb-a189-413f-bce1-a8f516aede26

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 }

運行效果:

7ee743ea-3e2a-4795-b7b3-10dfef4f3e9b 

 

2、鏈式隊列的應用:

題目:

  編寫一個判斷一個字符串是否是回文的算法。

思路:

  設字符數組str中存放了要判斷的字符串。把字符數組中的字符逐個分別存入一個隊列和棧中,然后逐個出隊和出棧比較出隊的字符與出棧的字符是否相同,若全部相等則該字符串為回文。

代碼實現:

  這里面需要用到上面一段中的LinkQueue類。代碼結構如下:

3a04b30d-6ed1-4556-a408-afc8b4c6068a

(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 }

運行效果:

f22bc718-69dd-488e-a893-dd93109da4cf

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM