1. 程式人生 > >資料結構與算法系列七(佇列)

資料結構與算法系列七(佇列)

1.引子

1.1.為什麼要學習資料結構與演算法?

有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!

有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎?

於是問題來了:為什麼還要學習資料結構與演算法呢?

#理由一:
    面試的時候,千萬不要被資料結構與演算法拖了後腿
#理由二:
    你真的願意做一輩子CRUD Boy嗎
#理由三:
    不想寫出開源框架,中介軟體的工程師,不是好廚子

1.2.如何系統化學習資料結構與演算法?

我想好了,還是需要學習資料結構與演算法。但是我有兩個困惑:

1.如何著手學習呢?

2.有哪些內容要學習呢?

學習方法推薦:

#學習方法
1.從基礎開始,系統化學習
2.多動手,每一種資料結構與演算法,都自己用程式碼實現出來
3.思路更重要:理解實現思想,不要背程式碼
4.與日常開發結合,對應應用場景

學習內容推薦:

資料結構與演算法內容比較多,我們本著實用原則,學習經典的、常用的資料結構、與常用演算法

#學習內容:
1.資料結構的定義
2.演算法的定義
3.複雜度分析
4.常用資料結構
    陣列、連結串列、棧、佇列
    散列表、二叉樹、堆
    跳錶、圖
5.常用演算法
    遞迴、排序、二分查詢
    搜尋、雜湊、貪心、分治
    動態規劃、字串匹配

2.考考你

你還記得在陣列那一篇中,我們說過基於線性表的資料結構有哪些嗎?它們是:陣列、連結串列、棧、佇列。

上一篇【資料結構與算法系列六(棧)】中,我們已經詳細瞭解了棧這種資料結構:棧是一種操作受限的資料結構。佇列是基於線性表的資料結構中,最後一種了,很巧!它也是一種操作受限的資料結構。

佇列同樣可以基於陣列實現:順序佇列;也可以基於連結串列實現:鏈式佇列。

那麼問題來了:具體如何實現一個佇列呢?它都有哪些應用場景呢?

#考考你:
1.你能用自己的話描述佇列嗎?
2.你知道常見的佇列分類嗎?
3.你知道佇列程式碼實現的關鍵嗎?
4.你知道如何實現一個迴圈佇列嗎?
5.你知道佇列的常見的應用場景嗎?

3.案例

3.1.佇列的定義

佇列是一種基於線性表的資料結構,與棧一樣,都是操作受限的資料結構。棧的特點是後進先出,而佇列的特點是先進先出(FIFO),就像我們平常在火車站排隊候車一樣。

佇列有兩頭:隊頭,和隊尾。從隊頭出隊元素,在隊尾入隊新的元素。

 

 

3.2.程式碼實現

順序佇列程式碼:

package com.anan.struct.linetable;

/**
 * 順序佇列:基於陣列實現
 * @param <E>
 */
public class ArrayQueue<E> {
    private Object[] items;
    private  int n;

    // 佇列需要兩個下標:對頭下標索引、隊尾下標索引
    private int head;
    private int tail;

    public ArrayQueue(int capacity){
        this.items = new Object[capacity];
        this.n = capacity;
    }

    /**
     * 入隊操作:
     */
    public boolean enqueue(E e){
        // 檢查佇列是否滿
        // 佇列滿條件 tail==n && head == 0
        if(tail == n){

            // 檢查對頭是否沒有出隊
            if(head == 0){
                return false;
            }

            // 如果已經有元素出隊,則向對頭移動資料
            for (int i = head; i < tail ; i++) {
                items[i - head] = items[i];
            }

            tail = tail - head;
            head = 0;
        }

        // 入隊
        items[tail] = e;
        tail ++;

        return true;
    }

    /**
     * 出隊操作:
     */
    public E dequeue(){
        // 檢查佇列是否空
        // 佇列空條件:head == tail
        if(head == tail){
            return null;
        }

        // 出隊
        E e = (E)items[head];
        head ++;

        return e;
    }

}

測試程式碼:

package com.anan.struct.linetable;

/**
 * 測試佇列
 */
public class ArrayQueueTest {

    public static void main(String[] args) {
        // 1.建立佇列
        int capacity = 10;
        ArrayQueue<Integer> queue = new ArrayQueue<Integer>(capacity);
        System.out.println("1.建立佇列---------佇列容量:" + capacity);

        // 2.入隊操作
        System.out.println("2.入隊操作---------");
        int count = 5;
        for (int i = 0; i < count; i++) {
            queue.enqueue(i);
            System.out.println("入隊元素:" + i);
        }


        // 3.出隊操作
        System.out.println("3.出隊操作---------");
        for (int i = 0; i < count; i++) {
            System.out.println("出隊元素:" + queue.dequeue());
        }

    }
}

測試結果:

D:\02teach\01soft\jdk8\bin\java com.anan.struct.linetable.ArrayQueueTest
1.建立佇列---------佇列容量:10
2.入隊操作---------
入隊元素:0
入隊元素:1
入隊元素:2
入隊元素:3
入隊元素:4
3.出隊操作---------
出隊元素:0
出隊元素:1
出隊元素:2
出隊元素:3
出隊元素:4

Process finished with exit code 0

3.3.迴圈佇列程式碼實現

package com.anan.struct.linetable;

/**
 * 迴圈佇列
 */
public class CircularQueue<E> {

    private Object[] items;
    private int n;

    // 隊頭、對尾指標
    private int head;
    private int tail;

    public CircularQueue(int capacity){
        items = new Object[capacity];
        n = capacity;
    }

    /**
     * 入隊操作
     */
    public boolean enqueue(E e){
        // 判斷佇列是否滿
        // 佇列滿條件:(tail + 1) % n == head
        if((tail + 1) % n == head){
            return false;
        }

        items[tail] = e;
        tail = (tail + 1) % n;

        return true;
    }

    /**
     * 出隊操作
     */
    public E dequeue(){
        // 判斷佇列是否空
        // 佇列空條件:tail == head
        if(tail == head){
            return null;
        }

        E e = (E)items[head];
        head = (head + 1) % n;

        return e;
    }
}

4.討論分享

#考考你答案:
1.你能用自己的話描述佇列嗎?
  1.1.佇列是基於線性表的資料結構
  1.2.佇列是一種操作受限的資料結構
  1.3.佇列滿足先進先出(FIFO)的特點
  1.4.佇列在隊頭出隊元素,在隊尾入隊元素
  
2.你知道常見的佇列分類嗎?
  2.1.從底層資料結構分類有:順序佇列、鏈式佇列
  2.2.從實現特點分類有:迴圈佇列、阻塞佇列、併發佇列
  
3.你知道佇列程式碼實現的關鍵嗎?
  3.1.佇列滿足先進先出(FIFO)特點
  3.2.佇列在隊頭出隊元素,在隊尾入隊元素
  3.3.實現佇列的關鍵:
    a.需要兩個指標:head、tail分別指向隊頭和隊尾
    b.入隊時,判斷佇列滿條件:tail == n && head == 0
    c.出隊時,判斷佇列空條件:tail == head
    
4.你知道如何實現一個迴圈佇列嗎?
  4.1.在案例中,基於陣列實現了一個普通的佇列
  4.2.入隊操作的時候,如果佇列滿,需要移動資料
  // 如果佇列滿,且已經有元素出隊,則向對頭移動資料
   for (int i = head; i < tail ; i++) {
          items[i - head] = items[i];
   }
  4.3.這樣會將入隊操作,時間複雜度從O(1),轉變成O(n),執行效率下降
  4.4.有沒有更好的方式,保持入隊操作的時間複雜度為O(1)不變呢?
  4.5.答案是:通過迴圈佇列來實現
  4.6.關於迴圈佇列的程式碼,你可以參考【3.3】迴圈佇列實現
  4.7.重點關注佇列滿的條件:(tail + 1) % n == head
  4.8.看你是否能理解,歡迎留言我們一起討論
  
5.你知道佇列的常見的應用場景嗎?
  5.1.佇列主要針對有限資源控制的應用場景
  5.2.比如資料庫連線池的應用
  5.3.比如執行緒池的應用
  5.4.如果你有興趣,可以看一下JUC中執行緒池的底層實現
  5.5.JUC執行緒池的底層,應用了:阻塞佇列
  5.6.通過佇列還能實現:生產者---消費者模型

JUC建立執行緒池:

&n