java數據結構與算法之(Queue)隊列設計與實現

【版權申明】轉載請註明出處(請尊重原創,博主保留追究權)
http://blog.csdn.net/javazejian/article/details/53375004
出自【zejian的博客】

關聯文章:

java數據結構與算法之順序表與鏈表設計與實現分析
java數據結構與算法之雙鏈表設計與實現
java數據結構與算法之改良順序表與雙鏈表類似ArrayList和LinkedList(帶Iterator迭代器與fast-fail機制)
java數據結構與算法之棧(Stack)設計與實現
java數據結構與算法之隊列(Queue)設計與實現
java數據結構與算法之遞歸思維(讓我們更通俗地理解遞歸)
java數據結構與算法之樹基本概念及二叉樹(BinaryTree)的設計與實現
java數據結構與算法之平衡二叉查找樹(AVL樹)的設計與實現

  本篇是數據結構算法的第五篇,本篇我們將來了解一下知識點:

隊列的抽象數據類型

  隊列同樣是一種特殊的線性表,其插入和刪除的操作分別在表的兩端進行,隊列的特點就是先進先出(First In First Out)。我們把向隊列中插入元素的過程稱爲入隊(Enqueue),刪除元素的過程稱爲出隊(Dequeue)並把允許入隊的一端稱爲隊尾,允許出的的一端稱爲隊頭,沒有任何元素的隊列則稱爲空隊。其一般結構如下:

關於隊列的操作,我們這裏主要實現入隊,出隊,判斷空隊列和清空隊列等操作,聲明隊列接口Queue(隊列抽象數據類型)如下:

package com.zejian.structures.Queue;

/**
* Created by zejian on 2016/11/28.
* Blog :http://blog.csdn.net/javazejian/article/details/53375004 [原文地址,請尊重原創]
* 隊列抽象數據類型
*/
public interface Queue<T> {

   /**
    * 返回隊列長度
    * @return
    */
   int size();

   /**
    * 判斷隊列是否爲空
    * @return
    */
   boolean isEmpty();

   /**
    * data 入隊,添加成功返回true,否則返回false,可擴容
    * @param data
    * @return
    */
   boolean add(T data);

   /**
    * offer 方法可插入一個元素,這與add 方法不同,
    * 該方法只能通過拋出未經檢查的異常使添加元素失敗。
    * 而不是出現異常的情況,例如在容量固定(有界)的隊列中
    * NullPointerException:data==null時拋出
    * @param data
    * @return
    */
   boolean offer(T data);

   /**
    * 返回隊頭元素,不執行刪除操作,若隊列爲空,返回null
    * @return
    */
   T peek();

   /**
    * 返回隊頭元素,不執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
    * @return
    */
   T element();

   /**
    * 出隊,執行刪除操作,返回隊頭元素,若隊列爲空,返回null
    * @return
    */
   T poll();

   /**
    * 出隊,執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
    * @return
    */
   T remove();

   /**
    * 清空隊列
    */
   void clearQueue();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

下面我們就來分別實現順序隊列和鏈式隊列

順序隊列的設計與實現

  關於順序隊列(底層都是利用數組作爲容器)的實現,我們將採用順序循環隊列的結構來實現,在給出實現方案前先來分析一下爲什麼不直接使用順序表作爲底層容器來實現。實際上採用順序表實現隊列時,入隊操作直接執行順序表尾部插入操作,其時間複雜度爲O(1),出隊操作直接執行順序表頭部刪除操作,其時間複雜度爲O(n),主要用於移動元素,效率低,既然如此,我們就把出隊的時間複雜度降爲O(1)即可,爲此在順序表中添加一個頭指向下標front和尾指向下標,出隊和入隊時只要改變front、rear的下標指向取值即可,此時無需移動元素,因此出隊的時間複雜度也就變爲O(1)。其過程如下圖所示

  從圖的演示過程,(a)操作時,是空隊列此時front和rear都爲-1,同時可以發現雖然我們通過給順序表添加front和rear變量記錄下標後使用得出隊操作的時間複雜度降爲O(1),但是卻出現了另外一個嚴重的問題,那就是空間浪費,從圖中的(d)和(e)操作可以發現,20和30出隊後,遺留下來的空間並沒有被重新利用,反而是空着,所以導致執行(f)操作時,出現隊列已滿的假現象,這種假現象我們稱之爲假溢出,之所以出現這樣假溢出的現象是因爲順序表隊列的存儲單元沒有重複利用機制,而解決該問題的最合適的方式就是將順序隊列設計爲循環結構,接下來我們就通過循環順序表來實現順序隊列。
  順序循環隊列就是將順序隊列設計爲在邏輯結構上收尾相接的循環結構,這樣我們就可以重複利用存儲單元,其過程如下所示:

簡單分析一下:
其中採用循環結構的順序表,可以循環利用存儲單元,因此有如下計算關係(其中size爲隊列長度):

//其中front、rear的下標的取值範圍是0~size-1,不會造成假溢出。
front=(front+1)%size;//隊頭下標
rear=(rear+1)%size;
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  1. front爲隊頭元素的下標,rear則指向下一個入隊元素的下標
  2. 當front=rear時,我們約定隊列爲空。
  3. 出隊操作改變front下標指向,入隊操作改變rear下標指向,size代表隊列容量。
  4. 約定隊列滿的條件爲front=(rear+1)%size,注意此時隊列中仍有一個空的位置,此處留一個空位主要用於避免與隊列空的條件front=rear相同。
  5. 隊列內部的數組可擴容,並按照原來隊列的次序複製元素數組
    瞭解了隊列的實現規則後,我們重點分析一下入隊add方法和出隊poll方法,其中入隊add方法實現如下:
/**
  * data 入隊,添加成功返回true,否則返回false,可擴容
  * @param data
  * @return
  */
 @Override
 public boolean add(T data) {
     //判斷是否滿隊
     if (this.front==(this.rear+1)%this.elementData.length){
         ensureCapacity(elementData.length*2+1);
     }
     //添加data
     elementData[this.rear]=data;
     //更新rear指向下一個空元素的位置
     this.rear=(this.rear+1)%elementData.length;
     size++;
     return true;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在add方法中我們先通過this.front==(this.rear+1)%this.elementData.length判斷隊列是否滿,在前面我們約定過隊列滿的條件爲front=(rear+1)%size,如果隊列滿,則先通過ensureCapacity(elementData.length*2+1)擴容,該方法實現如下:

/**
  * 擴容的方法
  * @param capacity
  */
 public void ensureCapacity(int capacity) {
     //如果需要拓展的容量比現在數組的容量還小,則無需擴容
     if (capacity<size)
         return;

     T[] old = elementData;
     elementData= (T[]) new Object[capacity];
     int j=0;
     //複製元素
     for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) {
         elementData[j++] = old[i];
     }
     //恢復front,rear指向
     this.front=0;
     this.rear=j;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

這個方法比較簡單,主要創建一個新容量的數組,並把舊數組中的元素複製到新的數組中,這裏唯一的要注意的是,判斷久數組是否複製完成的條件爲i!=this.rear,同時循環的自增條件爲i=(i+1)%old.length。擴容後直接通過rear添加新元素,最後更新rear指向下一個入隊新元素。對於出隊操作poll的實現如下:

/**
 * 出隊,執行刪除操作,返回隊頭元素,若隊列爲空,返回null
 * @return
 */
@Override
public T poll() {
    T temp=this.elementData[this.front];
    this.front=(this.front+1)%this.elementData.length;
    size--;
    return temp;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

出隊操作相對簡單些,直接存儲要刪除元素的數據,並更新隊頭front的值,最後返回刪除元素的數據。ok~,關於循環結構的順序隊列,我們就分析到此,最後給出循環順序隊列的實現源碼,其他方法比較簡單,註釋也很清楚,就不過多分析了:

package com.zejian.structures.Queue;

import java.io.Serializable;
import java.util.NoSuchElementException;

/**
 * Created by zejian on 2016/11/28.
 * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
 * 順序隊列的實現
 */
public class SeqQueue<T> implements Queue<T> ,Serializable {


    private static final long serialVersionUID = -1664818681270068094L;
    private static final int  DEFAULT_SIZE = 10;

    private T elementData[];

    private int front,rear;

    private int size;


    public SeqQueue(){
        elementData= (T[]) new Object[DEFAULT_SIZE];
        front=rear=0;
    }

    public SeqQueue(int capacity){
        elementData= (T[]) new Object[capacity];
        front=rear=0;
    }

    @Override
    public int size() {
//        LinkedList
        return size;
    }

    @Override
    public boolean isEmpty() {
        return front==rear;
    }

    /**
     * data 入隊,添加成功返回true,否則返回false,可擴容
     * @param data
     * @return
     */
    @Override
    public boolean add(T data) {
        //判斷是否滿隊
        if (this.front==(this.rear+1)%this.elementData.length){
            ensureCapacity(elementData.length*2+1);
        }
        //添加data
        elementData[this.rear]=data;
        //更新rear指向下一個空元素的位置
        this.rear=(this.rear+1)%elementData.length;
        size++;
        return true;
    }

    /**
     * offer 方法可插入一個元素,這與add 方法不同,
     * 該方法只能通過拋出未經檢查的異常使添加元素失敗。
     * 而不是出現異常的情況,例如在容量固定(有界)的隊列中
     * NullPointerException:data==null時拋出
     * IllegalArgumentException:隊滿,使用該方法可以使Queue的容量固定
     * @param data
     * @return
     */
    @Override
    public boolean offer(T data) {
        if (data==null)
            throw new NullPointerException("The data can\'t be null");
        //隊滿拋出異常
        if (this.front==(this.rear+1)%this.elementData.length){
            throw new IllegalArgumentException("The capacity of SeqQueue has reached its maximum");
        }

        //添加data
        elementData[this.rear]=data;
        //更新rear指向下一個空元素的位置
        this.rear=(this.rear+1)%elementData.length;
        size++;

        return true;
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,返回null
     * @return
     */
    @Override
    public T peek() {
        return elementData[front];
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T element() {
        if(isEmpty()){
            throw new NoSuchElementException("The SeqQueue is empty");
        }
        return peek();
    }

    /**
     * 出隊,執行刪除操作,返回隊頭元素,若隊列爲空,返回null
     * @return
     */
    @Override
    public T poll() {
        T temp=this.elementData[this.front];
        this.front=(this.front+1)%this.elementData.length;
        size--;
        return temp;
    }

    /**
     * 出隊,執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T remove() {
        if (isEmpty()){
            throw new NoSuchElementException("The SeqQueue is empty");
        }
        return poll();
    }

    @Override
    public void clearQueue() {
        for (int i=this.front; i!=this.rear ; i=(i+1)%elementData.length) {
            elementData[i] = null;
        }
        //復位
        this.front=this.rear=0;
        size=0;
    }

    /**
     * 擴容的方法
     * @param capacity
     */
    public void ensureCapacity(int capacity) {
        //如果需要拓展的容量比現在數組的容量還小,則無需擴容
        if (capacity<size)
            return;

        T[] old = elementData;
        elementData= (T[]) new Object[capacity];
        int j=0;
        //複製元素
        for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) {
            elementData[j++] = old[i];
        }
        //恢復front,rear指向
        this.front=0;
        this.rear=j;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

鏈式隊列的設計與實現

  分析完順序隊列,我們接着看看鏈式隊列的設計與實現,對於鏈式隊列,將使用帶頭指針front和尾指針rear的單鏈表實現,front直接指向隊頭的第一個元素,rear指向隊尾的最後一個元素,其結構如下:

  之所以選擇單鏈表(帶頭尾指針)而不採用循環雙鏈表或者雙鏈表主要是雙鏈表的空間開銷(空間複雜度,多前繼指針)相對單鏈表來說大了不少,而單鏈表只要新增頭指針和尾指針就可以輕鬆實現常數時間內(時間複雜度爲O(1))訪問頭尾結點。下面我們來看看如何設計鏈式隊列:

  1. 以上述的圖爲例分別設置front和rear指向隊頭結點和隊尾結點,使用單鏈表的頭尾訪問時間複雜度爲O(1)。
  2. 設置初始化空隊列,使用front=rear=null,並且約定條件front==null&&rear==null成立時,隊列爲空。
  3. 出隊操作時,若隊列不爲空獲取隊頭結點元素,並刪除隊頭結點元素,更新front指針的指向爲front=front.next
  4. 入隊操作時,使插入元素的結點在rear之後並更新rear指針指向新插入元素。
  5. 當第一個元素入隊或者最後一個元素出隊時,同時更新front指針和rear指針的指向。
    這一系列過程如下圖所示:

鏈式隊列

ok~,關於鏈式隊列的設計都分析完,至於實現就比較簡單了,和之前分析過的單鏈表區別不大,因此這裏我們直接給出實現代碼即可:

package com.zejian.structures.Queue;

import com.zejian.structures.LinkedList.singleLinked.Node;

import java.io.Serializable;
import java.util.*;

/**
 * Created by zejian on 2016/11/28.
 * Blog : http://blog.csdn.net/javazejian/article/details/53375004 [原文地址,請尊重原創]
 * 鏈式隊列的實現
 */
public class LinkedQueue<T> implements Queue<T> ,Serializable{
    private static final long serialVersionUID = 1406881264853111039L;
    /**
     * 指向隊頭和隊尾的結點
     * front==null&&rear==null時,隊列爲空
     */
    private Node<T> front,rear;

    private int size;
    /**
     * 用於控制最大容量,默認128,offer方法使用
     */
    private int maxSize=128;

    public LinkedQueue(){
        //初始化隊列
        this.front=this.rear=null;
    }

    @Override
    public int size() {
        return size;
    }

    public void setMaxSize(int maxSize){
        this.maxSize=maxSize;
    }

    @Override
    public boolean isEmpty() {
        return front==null&&rear==null;
    }

    /**
     * data 入隊,添加成功返回true,否則返回false,可擴容
     * @param data
     * @return
     */
    @Override
    public boolean add(T data) {
        Node<T> q=new Node<>(data,null);
        if (this.front==null) {//空隊列插入
            this.front = q;
        } else {//非空隊列,尾部插入
            this.rear.next=q;
        }
        this.rear=q;
        size++;
        return true;
    }

    /**
     * offer 方法可插入一個元素,這與add 方法不同,
     * 該方法只能通過拋出未經檢查的異常使添加元素失敗。
     * 而不是出現異常的情況,例如在容量固定(有界)的隊列中
     * NullPointerException:data==null時拋出
     * IllegalArgumentException:隊滿,使用該方法可以使Queue的容量固定
     * @param data
     * @return
     */
    @Override
    public boolean offer(T data) {
        if (data==null)
            throw new NullPointerException("The data can\'t be null");
        if (size>=maxSize)
            throw new IllegalArgumentException("The capacity of LinkedQueue has reached its maxSize:128");

        Node<T> q=new Node<>(data,null);
        if (this.front==null) {//空隊列插入
            this.front = q;
        } else {//非空隊列,尾部插入
            this.rear.next=q;
        }
        this.rear=q;
        size++;
        return false;
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,返回null
     * @return
     */
    @Override
    public T peek() {
        return this.isEmpty()? null:this.front.data;
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T element() {
        if(isEmpty()){
            throw new NoSuchElementException("The LinkedQueue is empty");
        }
        return this.front.data;
    }

    /**
     * 出隊,執行刪除操作,返回隊頭元素,若隊列爲空,返回null
     * @return
     */
    @Override
    public T poll() {
        if (this.isEmpty())
            return null;
        T x=this.front.data;
        this.front=this.front.next;
        if (this.front==null)
            this.rear=null;
        size--;
        return x;
    }

    /**
     * 出隊,執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T remove() {
        if (isEmpty()){
            throw new NoSuchElementException("The LinkedQueue is empty");
        }
        T x=this.front.data;
        this.front=this.front.next;
        if (this.front==null)
            this.rear=null;
        size--;
        return x;
    }

    @Override
    public void clearQueue() {
        this.front= this.rear=null;
        size=0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

隊列應用的簡單舉例

  1. 模擬現實世界中的隊列,如售票櫃檯的隊列以及其他先到先服務的場景。
  2. 計算客戶在呼叫中心等待的時間。
  3. 異步數據的傳輸(文件輸入輸出、管道、嵌套字)。
  4. 操作系統中的優先級任務執行。
  5. 短信羣體發送 應用的發佈訂閱模式

優先隊列的設置與實現(雙鏈表實現)

  瞭解完循環順序隊列和鏈式隊列的實現後,我們最後再來了解一個特殊的隊列,也就是優先隊列,在某些情況下,有些應用系統要求不僅需要按照“先來先服務”的原則進行,而且還需按照任務的重要或緊急程度進行排隊處理,此時就需要使用到優先隊列。比如在操作系統中進行進程調度管理,每個進程都具備一個優先級值以表示進程的緊急程度,優先級高的進行先執行,同等級進程按照先進先出的原則排隊處理,此時操作系統使用的便是優先隊列管理和調度進程。
  優先級隊列也是一種特殊的數據結構,隊列中的每個元素都有一個優先級,若每次出隊的是具有最高優先級的元素,則稱爲降序優先級隊列(總是先刪除最大的元素)。若每次出隊的是值最小的元素,則稱爲升序優先級隊列(總是先刪除最小的元素),通常情況下我們所說的優先隊列,一般是指降序優先級隊列。關於優先隊列的實現,可以使用有序數組或者有序鏈表,也可以使用二叉樹(二叉堆)實現,這裏我們僅給出有序鏈表的簡單實現方案。而二叉樹的實現,留着後面我們分析完樹時再給出。好~,這裏使用之前分析過的MyLikedList作爲基底,實現一個排序的SortLinkedList繼承自MyLinkedList,這裏需要注意的是排序鏈表中的T類型必須是實現了Comparable接口的類型,在SortLinkedList中主要重寫添加的add方法,插入邏輯是,通過比較元素的大小加入,而非簡單下標或尾部插入,其實現如下:

package com.zejian.structures.LinkedList.MyCollection;

import java.io.Serializable;
import java.util.Iterator;
import java.util.ListIterator;

/**
 * Created by zejian on 2016/12/3.
 * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
 * 排序list的簡單實現
 */
public class SortMyLinkedList<T extends Comparable<? extends T>> extends MylinkeList<T> implements Serializable {

    private static final long serialVersionUID = -4783131709270334156L;

    @Override
    public boolean add(T data) {
        if(data==null)
            throw new NullPointerException("data can\'t be null");

        Comparable cmp =data;//這裏需要轉一下類型,否則idea編輯器上檢驗不通過.

        if(this.isEmpty() || cmp.compareTo(this.last.prev.data) > 0){
             return super.add(data);//直接尾部添加,last不帶數據的尾結點
        }

        Node<T> p=this.first.next;
        //查找插入點
        while (p!=null&&cmp.compareTo(p.data)>0)
            p=p.next;

        Node<T> q=new Node<>(p.prev,data,p);
        p.prev.next=q;
        p.prev=q;

        size++;
        //記錄修改
        modCount++;

        return true;
    }

    /**
     * 不根據下標插入,只根據比較大小插入
     * @param index
     * @param data
     */
    @Override
    public void add(int index, T data) {
        this.add(data);
    }


    /**
     * 未實現
     * @param index
     * @return
     */
    @Override
    public ListIterator<T> listIterator(int index) {
        return null;
    }

    /**
     * 未實現
     * @return
     */
    @Override
    public Iterator<T> iterator() {
        return null;
    }

    //測試
    public static void main(String[] args){
        SortMyLinkedList<Integer> list=new SortMyLinkedList<>();
        list.add(50);
        list.add(40);
        list.add(80);
        list.add(20);
        print(list);
    }

    public static void print(SortMyLinkedList mylinkeList){
        for (int i=0;i<mylinkeList.size();i++) {
            System.out.println("i->"+mylinkeList.get(i));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

接着以SortMyLinkedList爲基底實現優先隊列PriorityQueue,實現源碼如下,實現比較簡單,感覺沒啥好說的:

package com.zejian.structures.Queue;

import com.zejian.structures.LinkedList.MyCollection.SortMyLinkedList;

import java.io.Serializable;
import java.util.NoSuchElementException;

/**
 * Created by zejian on 2016/11/30.
 * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
 * 優先隊列的簡單實現,採用排序雙鏈表,T必須實現Comparable接口
 */
public class PriorityQueue<T extends Comparable<? extends T>> implements Queue<T> ,Serializable {

    private static final long serialVersionUID = 8050142086009260625L;

    private SortMyLinkedList<T> list;//排序循環雙鏈表

    private boolean asc;//true表示升序,false表示降序

    /**
     * 用於控制最大容量,默認128,offer方法使用
     */
    private int maxSize=128;
    /**
     * 初始化隊列
     * @param asc
     */
    public PriorityQueue(boolean asc){
        this.list=new SortMyLinkedList<>();
        this.asc=asc;//默認升序
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    /**
     * data 入隊,添加成功返回true,否則返回false
     * @param data
     * @return
     */
    @Override
    public boolean add(T data) {
        return list.add(data);
    }

    /**
     * offer 方法可插入一個元素,這與add 方法不同,
     * 該方法只能通過拋出未經檢查的異常使添加元素失敗。
     * 而不是出現異常的情況,例如在容量固定(有界)的隊列中
     * NullPointerException:data==null時拋出
     * IllegalArgumentException:隊滿,使用該方法可以使Queue的容量固定
     * @param data
     * @return
     */
    @Override
    public boolean offer(T data) {
        if (data==null)
            throw new NullPointerException("The data can\'t be null");
        if (list.size()>=maxSize)
            throw new IllegalArgumentException("The capacity of PriorityQueue has reached its maxSize:128");

        return add(data);
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,返回null
     * @return
     */
    @Override
    public T peek() {
        if(isEmpty()){
            return null;
        }
        return this.asc ? this.list.get(0):this.list.get(size()-1);
    }

    /**
     * 返回隊頭元素,不執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T element() {
        if(isEmpty()){
            throw new NoSuchElementException("The PriorityQueue is empty");
        }
        return peek();
    }

    /**
     * 出隊,執行刪除操作,返回隊頭元素,若隊列爲空,返回null
     * @return
     */
    @Override
    public T poll() {
        if(isEmpty()){
            return null;
        }
        return this.asc ? this.list.remove(0): this.list.remove(list.size()-1);
    }

    /**
     * 出隊,執行刪除操作,若隊列爲空,拋出異常:NoSuchElementException
     * @return
     */
    @Override
    public T remove() {
        if (isEmpty()){
            throw new NoSuchElementException("The PriorityQueue is empty");
        }
        return poll();
    }

    @Override
    public void clearQueue() {
        this.list.clear();
    }

    //測試
    public static void main(String[] args){
        PriorityQueue<Process> priorityQueue=new PriorityQueue<>(false);

        System.out.println("初始化隊列");
        priorityQueue.add(new Process("進程1",10));
        priorityQueue.add(new Process("進程2",1));
        priorityQueue.add(new Process("進程3",8));
        priorityQueue.add(new Process("進程4",3));
        priorityQueue.add(new Process("進程5"));
        System.out.println("隊列中的進程執行優先級:");
        while (!priorityQueue.isEmpty()){
            System.out.println("process:"+priorityQueue.poll().toString());
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151

ok~,就到這吧,下一篇我們聊聊遞歸算法,源碼下載地址:
github源碼下載(含文章列表,持續更新)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章