數據結構 -- 棧和隊列的實現及應用

轉載請標明出處:
https://blog.csdn.net/xmxkf/article/details/82465726
本文出自:【openXu的博客】

  從數據結構的定義看,棧和隊列也是一種線性表。其不同之處在於棧和隊列的相關運算具有特殊性,只是線性表相關運算的一個子集。更準確的說,一般線性表的插入、刪除運算不受限制,而棧和隊列上的插入刪除運算均受某種特殊限制。因此,棧和隊列也稱作操作受限的線性表。

1、棧

1.1 棧的定義

  棧(Stack)是一種只能在一端進行插入或刪除操作的線性表。表中允許進行插入、刪除操作的一端稱爲棧頂(Top)。棧頂的當前位置是動態的,棧頂的當前位置是由一個稱爲棧頂指針的位置指示器指示。表的另一端稱爲棧底(Bottom)。當棧中沒有數據元素時稱爲空棧。棧的插入操作稱爲進棧或入棧(Push),刪除操作稱爲退棧或出棧(Pop)。

  棧的主要特點是“後進先出”,即後進棧的元素先彈出。每次進棧的數據元素都放在原當前棧頂元素之前成爲新的棧頂元素,每次出棧的數據元素都是當前棧頂元素。棧也稱爲後進先出表。

1.2 棧的順序存儲結構實現

  通常棧可以用順序的方式存儲,分配一塊連續的存儲區域存放棧中的元素,並用一個變量指向當前的棧頂。採用順序存儲的棧稱爲順序棧,Java util包下的Stack就是順序棧。

順序棧的操作示意圖如下:

    這裏寫圖片描述

順序棧的實現如下:

public class StackByArray<T>{

    private int top = -1; //棧頂指針,-1代表空棧
    private int capacity = 10;  //默認容量
    private int capacityIncrement = 5;  //容量增量
    private T[] datas;    //元素容器

    public StackByArray(int capacity){
        datas = (T[])new Object[capacity];
    }
    public StackByArray(){
        datas = (T[])new Object[capacity];
    }

    /**溢出擴容 */
    private void ensureCapacity() {
        int oldCapacity = datas.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                capacityIncrement : oldCapacity);
        datas = Arrays.copyOf(datas, newCapacity);
    }

    /**進棧,將元素添加到棧頂*/
    public synchronized void push(T item) {
        //容量不足時擴容
        if(top>=datas.length-1)
            ensureCapacity();
        datas[++top] = item;
    }

    /**出棧,將棧頂的元素移除並返回該元素*/
    public synchronized T pop() {
        if(top<0)
            new EmptyStackException();
        T t = datas[top];
        datas[top] = null;
        top--;
        return t;
    }
    /**清空棧*/
    public synchronized void clear() {
        if(top<0)
            return;
        for(int i = top; i>=0; i--)
            datas[i] = null;
        top = -1;
    }
    /**返回棧頂元素*/
    public T peek() {
        if(top<0)
            new EmptyStackException();
        return datas[top];
    }
    /**獲取棧中元素個數*/
    public int getLength() {
        return top+1;
    }
    /**棧是否爲空棧*/
    public boolean empty() {
        return top<0;
    }

    /**獲取元素到棧頂的距離 */
    public int search(T data) {
        int index = -1;
        if(empty())
            return index;
        if (data == null) {
            for (int i = top; i >= 0; i--)
                if (datas[i]==null){
                    index = i;
                    break;
                }
        } else {
            for (int i = top; i >= 0; i--)
                if (data.equals(datas[i])) {
                    index = i;
                    break;
                }
        }
        if (index >= 0) {
            return top - index;
        }
        return index;
    }

    @Override
    public String toString() {
        if(empty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        for(int i = 0; i<=top; i++){
            buffer.append(datas[i]+", ");
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

1.3 棧的鏈式存儲結構實現

  採用鏈式存儲結構的棧稱爲鏈棧,在鏈棧中規定所有操作都是在鏈表表頭進行的,鏈棧的優點是不存在棧滿上溢的情況。上述順序棧的實現過程中增加了擴容的功能,所以也能實現不上溢。

鏈棧的操作示意圖如下:

    這裏寫圖片描述

鏈棧的實現如下:

public class StackByLink<T>{

    public LNode<T> top;   //棧頂指針,指向棧頂結點
    private int size = 0;

    /**進棧,將元素添加到棧頂*/
    public synchronized void push(T item) {
        LNode temp = new LNode();
        temp.next = top;   //添加的結點的指針域指向當前棧頂結點
        top = temp;        //更新棧頂結點
        size ++;
    }

    /**出棧,將棧頂的元素移除並返回該元素*/
    public synchronized T pop() {
        if(top==null)
            new EmptyStackException();
        LNode < T> next = top.next;
        T data = top.data;
        top.next = null;
        top.data=null;
        top = next;
        size--;
        return data;
    }
    /**清空棧*/
    public synchronized void clear() {
        LNode<T> next;
        while(top!=null){
            next = top.next;
            top.data = null;
            top.next = null;
            top = next;
        }
        size = 0;
    }
    /**返回棧頂元素*/
    public T peek() {
        if(top==null)
            new EmptyStackException();
        return top.data;
    }
    /**獲取棧中元素個數*/
    public int getLength() {
        return size;
    }
    /**棧是否爲空棧*/
    public boolean empty() {
        return top==null;
    }

    /**獲取元素到棧頂的距離 */
    public int search(T data) {
        int dis = -1;
        if(empty())
            return dis;
        LNode<T> node = top;
        if (data == null) {
            while(node!=null){
                dis ++;
                if (node.data == null)
                    return dis;
            }
        } else {
            while(node!=null){
                dis ++;
                if (node.data.equals(data))
                    return dis;
            }
        }
        return dis;
    }

    @Override
    public String toString() {
        if(empty())
            return "[]";
        LNode node = top;
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        while(node != null){
            buffer.append(node.data+", ");
            node = node.next;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

1.4 兩種棧的效率分析

  順序棧和鏈式棧中,主要的操作算法push(T item)、pop()、 peek()的時間複雜度和空間複雜度都是O(1),從效率上都是一樣的,因爲棧只對一端進行操作。順序棧和鏈式棧實現起來最大的區別就是鏈式棧不用考慮容量問題,而順序棧會有上溢的情況,如果加上自動擴容,則push(T item)的時間複雜度將變爲O(n)。所以鏈式棧相比會更加靈活一點。

1.5 棧的應用

  棧是一種最常用也是最重要的數據結構質疑,用途十分廣泛。在二叉樹的各種算法中大量地使用棧,將遞歸算法轉換成非遞歸算法時也常常用到棧;接下來我們用破解迷宮的算法示例棧的應用。

破解迷宮

  給定一個M * N的迷宮圖,求一條從指定入口到出口的路徑。假設迷宮圖如下圖所示,白色表示通道,深色表示牆。所求路徑必須是簡單路徑,即路徑中不能重複出現同一通道塊。爲了表示迷宮,設置一個二維數組maze,其中每個元素表示一個方塊的狀態,0表示是通道,1表示該方塊不可走。

    這裏寫圖片描述

  在求解時,通常使用“窮舉求解”的方法,即從入口出發,順某一方向向前試探,若能走通,則繼續往前走,否則眼原路退回,換一個方向在繼續試探,直到所有可能的通道都試探完爲止。

  爲了保證在任何爲止上都能沿原路退回,需要用一個後進先出的棧來保存從入口到當前位置的路徑。每次取棧頂的方塊,試探這個方塊下一個可走的方向(上方、右方、下方、左方),如果找到可走的方向,則將該方向下一個方塊入棧,如果沒有可走的下一個方塊,說明該路死了,需要將當前棧頂元素出棧。爲了保證試探的可走相鄰方塊不是已走路徑上的方塊,比如(i,j)已經入棧,在試探(i+1, j)的下一可走方塊時又試探到(i,j),這可能會引起死循環,爲此,在一個方塊入棧後,將對應數組元素值改爲-1(只有0纔是可走),當出棧時,再將其恢復至0。

算法實現如下:

 public static StackByArray<MazeElem> getMazePathByArrayStack(int[][] maze, int startX,
                                                     int startY, int endX, int endY){
        int x = startX, y = startY;       //用於查找下一個可走方位的座標
        boolean find;   //是否找到棧頂元素下一個可走 的 方塊
        StackByArray<MazeElem> stack = new StackByArray();
        //入口元素入棧
        MazeElem elem = new MazeElem(startX, startY, -1);
        stack.push(elem);
        maze[x][y] = -1;  //將入口元素置爲-1,避免死循環
        //棧不爲空時循環
        while(!stack.empty()){
            elem = stack.peek();   //取棧頂元素
            if(elem.x == endX && elem.y == endY){
                //棧頂元素與出口座標一樣時,說明找到出口了
                Log.e(TAG, "迷宮路徑如下:\n"+stack);
                return stack;
            }

            find = false;

            //遍歷棧頂元素的四個方向 ,找 棧頂 元素 下一個可走 方塊
            while(elem.di < 4 && !find){
                elem.di++;
                switch ((elem.di)){
                    case 0:   //上方
                        x = elem.x-1; y = elem.y;
                        break;
                    case 1:   //右方
                        x = elem.x; y = elem.y+1;
                        break;
                    case 2:   //下方
                        x = elem.x+1; y = elem.y;
                        break;
                    case 3:   //左方
                        x = elem.x; y = elem.y-1;
                        break;
                }
                //避免OutOfIndex
                if(x>=0 && y>=0 && x<maze.length && y<maze[0].length)
                    find = maze[x][y]==0;
            }

            if(find){  //找到了下一個可走方向
                stack.push(new MazeElem(x, y, -1));  //下一個可走方向入棧
                maze[x][y] = -1;      //入棧後置爲-1,避免死循環
            }else{
                //棧頂元素沒有下一個可走結點,則出棧,並將該方塊置爲0(可走)
                elem = stack.pop();
                maze[elem.x][elem.y] = 0;
            }
        }
        return null;
    }

2、隊列

2.1 隊列的定義

  隊列簡稱隊,它也是一種操作受限的線性表,其限制爲僅允許在表的一端進行插入,而在表的另一端進行刪除。把進行插入的一端稱做隊尾(rear),進行刪除的一端稱做隊首或隊頭(front)。向隊列中插入新元素稱爲進隊或入隊,新元素進入後就成爲新的隊尾元素;從隊列中刪除元素稱爲出隊或離隊,元素出隊後,其直接後繼元素就成爲隊首元素。

  由於隊列的插入和刪除操作分別是在各自的一端進行的,每個元素必然按照進入的次序出隊,所以又把隊列稱爲先進先出表。

2.2 隊的順序存儲結構實現

  隊列的順序存儲結構需要使用一個數組和兩個整數型變量來實現, 利用數組順序存儲隊列中的所有元素,利用兩個整數變量分別存儲隊首元素和隊尾元素的下標位置,分別稱爲隊首指針和隊尾指針。

    這裏寫圖片描述

  假設隊列元素個數最大不超過MaxSize,當隊列爲初始狀態有front==rear,該條件可作爲隊列空的條件。那麼能不能用rear==MaxSize-1作爲隊滿的條件呢?顯然不能,如果繼續往上圖的隊中添加元素時出現“上溢出”,這種溢出並不是真正的溢出,因爲數組中還存在空位置,所以這是一種假溢出。

  爲了能充分的使用數組中存儲空間,把數組的前端和後端連接起來,形成一個環形的順序表,即把存儲隊列元素的表從邏輯上看成一個環,稱爲環形隊列。如下如所示:
    這裏寫圖片描述

上圖對應步驟解析如下:

  • 初始化隊列時,隊首front和隊尾rear都指向0;
  • a入隊時,隊尾rear=rear+1指向1;
  • bc入隊後,rear=3,此時隊已滿。其實此時隊中還有1個空位,如果這個空位繼續放入一個元素,則rear=front=0,這和rear=front時隊爲空衝突,所以爲了算法設計方便,此處空出一個位。所以判斷隊滿的條件是(rear+1)%MaxSize == front
  • ab出隊,隊首front=2;
  • de入隊,此時rear=1;滿足(rear+1)%MaxSize == front,所以隊滿
  • cde出隊,此時rear=front=1,隊空

  通過上述分析,我們可以得出,出隊和入隊操作會使得隊首front隊尾rear指向新的索引,由於數組爲環狀,可通過求餘%運算來實現:

入隊(隊尾指針進1):rear = (rear+1)%MaxSize
出隊(隊首指針進1):front = (front+1)%MaxSize

  當滿足 rear==front時,隊列爲空,我們可以在出隊操作後,判斷此條件,如果滿足則說明隊列爲空了,可以將rear和front重新指向0;

  當需要入隊操作時,首先通過(rear+1)%MaxSize == front判斷是否隊滿,如果隊滿,則需要空充容量,否則會溢出。對於環形隊列的講解就到這裏,下面是實現代碼:

public class QueueByArray<T>{

    private int front = 0; //隊首指針(出隊)
    private int rear = 0;  //隊尾指針(入隊)
    private int size;      //元素個數

    private int capacity = 10;  //默認容量
    private int capacityIncrement = 5;  //容量增量
    private T[] datas;    //元素容器

    public QueueByArray(int capacity){
        datas = (T[])new Object[capacity];
    }
    public QueueByArray(){
        datas = (T[])new Object[capacity];
    }

    public int getFront() {
        return front;
    }

    public void setFront(int front) {
        this.front = front;
    }

    public int getRear() {
        return rear;
    }

    public void setRear(int rear) {
        this.rear = rear;
    }

    /**獲取隊中元素個數*/
    public int getSize() {
        return rear - front;
//        return size;
    }

    /**是否爲空隊*/
    public boolean isEmpty() {
        return rear==front;
    }

    /**入隊*/
    public synchronized boolean enQueue(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        //判斷是否滿隊
        if((rear+1) % datas.length == front){
            //滿隊時擴容
            ensureCapacity();
        }
        //添加data
        datas[rear] = item;
        //更新rear指向下一個空元素的位置
        rear = (rear+1) % datas.length;
        size++;
        return true;
    }

    /**雙端隊列  從隊首入隊*/
    public synchronized boolean enQueueFront(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        //判斷是否滿隊
        if((rear+1) % datas.length == front){
            //滿隊時擴容
            ensureCapacity();
        }
        //使隊首指針指向上一個空位
        front = (front-1+datas.length)%datas.length;
        //添加data
        datas[front] = item;
        size++;
        return true;
    }

    /**溢出擴容 */
    private void ensureCapacity() {
        int oldCapacity = datas.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                capacityIncrement : oldCapacity);
        T[] oldDatas = datas;
        datas = (T[])new Object[newCapacity];
        int j = 0;
        //將原數組中元素拷貝到新數組
        for (int i = front; i!=this.rear ; i = (i+1) % datas.length) {
            datas[j++] = oldDatas[i];
        }
        //front指向新數組0的位置
        front = 0;
        //rear指向新數組最後一個元素位置
        rear = j;
    }

    /**出隊*/
    public synchronized T deQueue() {
        if(isEmpty())
            return null;
        T t = datas[front];
        datas[front] = null;
        //front指向新的隊首元素
        front = (front+1) % datas.length;
        size --;
        return t;
    }

    /**雙端隊列  從隊尾出隊*/
    public synchronized T deQueueRear() {
        if(isEmpty())
            return null;
        //隊尾指針指向上一個元素位置
        rear = (rear-1+datas.length)%datas.length;
        T t = datas[rear];
        datas[rear] = null;
        size --;
        return t;
    }

    /**清空隊列*/
    public synchronized void clear() {
        while(!isEmpty()){
            deQueue();
        }
        front = rear = 0;
    }

    /**返回隊首元素*/
    public T peek() {
        if(isEmpty())
            return null;
        return datas[front];
    }
    public T getElement(int index) {
        if(isEmpty() || index>=datas.length)
            return null;
        T t = datas[index];
        return t;
    }
    @Override
    public String toString() {
        if(isEmpty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        int mFront = front;
        while(mFront!=rear){
            buffer.append(datas[mFront]+", ");
            mFront = (mFront+1) % datas.length;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

2.3 雙端隊列

  如果允許在環形隊列的兩端都可以進行插入和刪除操作,這樣的隊列稱爲雙端隊列。上面的環形隊列中,只能從隊尾入隊,隊首出隊,在雙端隊列中,隊首和隊尾都能出隊入隊。相關算法如下:

    這裏寫圖片描述

    /**雙端隊列  從隊首入隊*/
    public synchronized boolean enQueueFront(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        //判斷是否滿隊
        if((rear+1) % datas.length == front){
            //滿隊時擴容
            ensureCapacity();
        }
        //使隊首指針指向上一個空位
        front = (front-1+datas.length)%datas.length;
        //添加data
        datas[front] = item;
        size++;
        return true;
    }
 /**雙端隊列  從隊尾出隊*/
    public synchronized T deQueueRear() {
        if(isEmpty())
            return null;
        //隊尾指針指向上一個元素位置
        rear = (rear-1+datas.length)%datas.length;
        T t = datas[rear];
        datas[rear] = null;
        size --;
        return t;
    }

2.4 隊列的鏈式存儲結構實現

   鏈式存儲結構有單鏈表和雙鏈表,對於鏈隊的實現,使用單鏈表就足以滿足需求(雙鏈表由於多了前驅指針,存儲密度不如單鏈表,造成空間浪費)。下面我們使用單鏈表實現鏈隊,操作示意圖如下:
      這裏寫圖片描述

從上圖中,我們可以知道如下幾點:

  • 使用front指向隊首結點,rear指向隊尾結點;
  • 當隊爲空時,front = rear = null,並約定以此作爲隊列爲空的判斷條件;
  • 入隊時,使當前隊尾結點的後繼指針指向新結點rear.next = newNode,然後使隊尾指針rear指向新結點
  • 出隊時,刪除隊首結點,使front = front.next
  • 鏈隊不會出現溢出的情況,這比使用數組實現的順序隊列更加靈活

  鏈隊的分析就到這裏,接下來我們實現鏈隊的基本操作算法:

public class QueueByLink<T>{

    private LNode<T> front; //隊首指針
    private LNode<T> rear;  //隊尾指針

    private int size;      //元素個數

    /**獲取隊中元素個數*/
    public int getSize() {
        return size;
    }

    /**隊列是否爲空*/
    public boolean isEmpty() {
        return front==null && rear==null;
    }

    /**入隊*/
    public synchronized boolean enQueue(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        LNode<T> newNode = new LNode();
        newNode.data = item;
        if (front == null) {
            //向空隊中插入,需要將front指針指向第一個結點
            front = newNode;
        } else {
            //非空隊列,隊尾結點的後繼指針指向新結點
            rear.next = newNode;
        }
        rear = newNode;   //隊尾指針指向新結點
        size ++;
        return true;
    }

    /**出隊*/
    public synchronized T deQueue() {
        if(isEmpty())
            return null;
        T t = front.data;
        front = front.next;
        if (front == null)   //如果出隊後隊列爲空,重置rear
            rear = null;
        size--;
        return t;
    }

    /**返回隊首元素*/
    public T peek() {
        return isEmpty()? null:front.data;
    }

    /**清空隊列*/
    public synchronized void clear() {
        while(!isEmpty()){
            deQueue();
        }
        front = rear = null;
        size = 0;
    }

    @Override
    public String toString() {
        if(isEmpty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        LNode mFront = front;
        while(mFront!=null){
            buffer.append(mFront.data+", ");
            mFront = mFront.next;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }

}

2.5 隊列的應用

   隊列的使用也是非常廣泛的,比如android中MessageQueue就是利用隊列實現的。這裏我們繼續使用1.5中迷宮破解問題示例隊列的應用。

破解迷宮

   在1.5中使用棧來破解迷宮,是利用窮舉的思想,嘗試沿着一條路走下去,走不通回退,繼續嘗試下一條路,直到找到出口。通過這種方式求出的路徑不一定是最短路徑,這跟嘗試的方向有關係(上左下右),如果向上走時能一直走到出口,則會忽略掉左下右等方向的更短的路徑。
  這裏我們使用隊列來求解,假設當前點位爲(x, y),在隊列中的索引爲front,遍歷該位置的四個方位,如果方位可走則入隊,並記錄這個方位元素的前驅爲front。如下圖所示,當前點位上方的點位不可走,不入隊;右方可走,入隊;下方可走入隊;左方可走入隊;然後將front++,這時候當前點位變成(x, y+1),繼續遍歷它的四個方位,淘汰掉不可走的,可走的方位都會入隊……。這樣一層一層向外擴展可走的點,所有可走的點位各個方向都會嘗試,而且機會相等,直到找到出口爲止,這個方法稱爲“廣度優先搜索方法”。然後我們從出口反向找其上一個方塊的下標,直到下標爲0,這個反向過程就能找到最短路徑。由於此處需要通過索引獲取隊列元素,所以使用順序隊列來實現,因爲鏈式存儲結構查找不方便。
    這裏寫圖片描述

  當然最短路徑可能不止一條,具體求得的是那一條也是跟方位遍歷順序有關係的,這裏就不做討論。下面看看算法的實現:

 public static QueueByArray<MazeElem> getMazePathByArrayQueue(int[][] maze, int startX,
                                                                 int startY, int endX, int endY){
        int x, y, di;

        QueueByArray<MazeElem> queue = new QueueByArray();
        //入口元素進隊
        MazeElem elem = new MazeElem(startX, startY, 0,-1);
        queue.enQueue(elem);
        maze[startX][startY] = -1;  //將入口元素置爲-1,避免回過來重複搜索

        int front = 0;  //記錄當前操作的可走方塊在隊列中的索引
        //隊列不爲空且未找到路徑時循環
        while(front<=queue.getRear()){
            x = queue.getElement(front).x;
            y = queue.getElement(front).y;
            if(x == endX && y == endY){  //找到了出口
                int k = front, j;
                //反向找到最短路徑,將該路徑上的方塊的pre設置爲-1
                do{
                    j = k;
                    k = queue.getElement(k).pre;   //上一個可走方塊索引
                    queue.getElement(j).pre = -1;  //將路徑上的元素的pre值爲-1
                }while(k!=0);
                //返回隊列,隊列中pre爲-1的元素,構成最短路徑
                return queue;
            }
            for(di = 0; di<4; di++){   //循環掃描每個方位,把每個可走的方塊插入隊列中
                switch (di){
                    case 0:     //上
                        x = queue.getElement(front).x-1;
                        y = queue.getElement(front).y;
                        break;
                    case 1:     //右
                        x = queue.getElement(front).x;
                        y = queue.getElement(front).y+1;
                        break;
                    case 2:     //下
                        x = queue.getElement(front).x+1;
                        y = queue.getElement(front).y;
                        break;
                    case 3:     //左
                        x = queue.getElement(front).x;
                        y = queue.getElement(front).y-1;
                        break;
                }
                if(x>=0 && y>=0 && x<maze.length && y<maze[0].length){
                    if(maze[x][y] == 0){
                        //將該相鄰方塊插入隊列
                        queue.enQueue(new MazeElem(x, y, 0, front));
                        maze[x][y] = -1;   //賦值爲-1,避免回過來重複搜索
                    }
                }
            }
            front ++;
        }
        return null;
    }

3、優先級隊列

  優先級隊列是一種特殊的隊列,其元素具有優先級別,在遵循普通隊列“先進先出”原則的同時,還有優先級高的元素先出隊的調度機制。比如操作系統中進程調度管理就是利用優先隊列的思想,在同級進程中按照“先來先處理”的原則,對於某些高優先級別的特殊進程則可插隊,優先級高的先處理。這就像火車站取票一樣,首先得排隊,先來的先取票,但是某些人的票離發車時間很近了,他的緊急程度很高,即使來的晚,他們可以說說好話插個隊。

  優先級隊列可分爲“降序優先級隊列”和“升序優先級隊列”。“降序優先級隊列”優先級高的先出隊;“升序優先級隊列”則是優先級底的先出隊。像進程調度管理、火車站取票等都是降序優先級隊列,一般情況下我們所說的優先隊列都是指降序優先級隊列。

  優先級隊列的實現和普通隊列一樣,可以使用順序存儲結構實現,也可以使用鏈式存儲結構實現等等。我們只需要簡單的修改普通隊列的算法就能實現優先級隊列,比如在入隊的時候,將新元素插入到對應優先級的位置;或者在出隊的時候,查找隊列中優先級高的先出。對比兩種方案,發現使用第一種方案入隊時就將結點插入到合適的位置更加方便,由於需要插入,採用鏈式存儲結構將會更加方便。下面我們簡單的利用鏈式存儲結構實現優先級隊列。

/**
 * autour : openXu
 * date : 2018/7/31 10:03
 * className : PriorityQueue
 * version : 1.0
 * description : 優先級隊列
 *
 *                         ———————————————
 *     front(隊首)<<<        高    (優先級)    低          <<<   (隊尾)rear
 *                         ———————————————
 *
 */
public class PriorityQueue<T extends Comparable<T>> extends DLinkList<T> {

    private ODER_TYPE oderType = ODER_TYPE.desc;   //默認降序優先隊列

    public enum ODER_TYPE{
        desc,     //降序
        asc,      //升序
    }

    public PriorityQueue(ODER_TYPE oderType) {
        this.oderType = oderType;
    }

    /**入隊*/
    public synchronized boolean enQueue(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");

        DNode<T> newNode = new DNode<>();
        newNode.data = item;

        //如果隊列爲空,或者添加的元素優先級比隊尾元素還低,則直接添加到隊尾
        if(isEmpty() || item.compareTo(last.data) <= 0) {
            if (last == null)            //空表
                first = newNode;
            else {
                last.next = newNode;
                newNode.prior = last;
            }
            last = newNode;
            size++;
            return true;
        }

        DNode<T> p = first;
        //查找插入點 , p爲從隊首開始第一個小於插入值的元素,需要插入到p前面
        while (p != null && item.compareTo(p.data) <= 0)
            p = p.next;

        newNode.next = p;
        newNode.prior = p.prior;
        if(p.prior==null){   //p爲隊首結點
            first = newNode;
        }else{
            p.prior.next = newNode;
        }
        p.prior = newNode;

        size++;
        return true;
    }

    /**出隊*/
    public synchronized T deQueue() {
        if(isEmpty())
            return null;
        return oderType == ODER_TYPE.desc ? remove(0):remove(length()-1);
    }

    /**返回隊首元素,不執行刪除操作*/
    public T peek() {
        return isEmpty()? null:
                oderType == ODER_TYPE.desc ? get(0):get(length()-1);
    }

    @Override
    public String toString() {
        if(isEmpty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        DNode mFirst = first;
        while(mFirst!=null){
            buffer.append(mFirst.data+", ");
            mFirst = mFirst.next;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

源碼下載

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