重學資料結構(三、佇列)
@目錄
1、佇列的定義和特點
和上一篇的棧相反,佇列(queue)是一種先進先出(First In First Out, FIFO)的線性表。
它只允許在表的一端進行插入,而在另一端刪除元素。這和日常生活中的排隊是一致的,最早進入佇列的元素最早離開。
在佇列中,允許插入的一端稱為隊尾(rear), 允許 刪除的一端則稱為隊頭(front)。出佇列和入佇列示意圖如下:
2、佇列的基本操作
佇列的基本運算和堆疊類似,包含判空、獲取長度、入隊、出隊、出隊、取隊頭(不刪除隊頭)等。
我們這裡定義一個佇列的介面。
/** * @Author 三分惡 * @Date 2020/8/27 * @Description 佇列 */ public interface Queue { public boolean isEmpty(); //判空 public int size(); //獲取佇列容量 public void enQueue(Object element); //入隊 public Object deQueue(); //出隊 public Object getHead(); //取隊首元素 }
佇列是一種受限的線性表,同樣有順序和鏈式兩種實現。
3、順序佇列
這裡順序佇列通過可擴容陣列來實現。
在類裡標記了隊頭和對尾的下標。
入隊時,隊尾往後移動,隊頭保持不變,出隊是隊頭往後移動,隊尾保持不變。
為什麼不保持隊頭指向0?因為如果隊首指向0,那麼出隊的時候需要將陣列前移,時間複雜度為O(n)。使用了隊頭和隊尾標記之後,出隊時隊頭往後移動一位,這樣避免了元素的移動。
/** * @Author 三分惡 * @Date 2020/8/27 * @Description 順序佇列 */ public class ArrayQueue implements Queue{ private static int defaultSize=15; //預設容量 private int size; //實際容量:實際儲存元素個數 private Object[] data; //存放元素的陣列 private int front=0; //隊頭(下標) private int rear=0; //隊尾(下標) /** * 無參構造方法:按預設容量構造元素陣列 */ public ArrayQueue(){ data=new Object[defaultSize]; } /** * 有參構造方法:指定元素陣列容量 * @param capacity */ public ArrayQueue(int capacity){ data=new Object[capacity]; } /** * 判空 * @return */ public boolean isEmpty() { return size==0; } /** * 獲取佇列元素個數 * @return */ public int size() { return size; } /** * 入隊 * @param element */ public void enQueue(Object element) { //如果隊滿 if (size==data.length&&front==0){ //真隊滿,擴容 if (front==0){ //擴容兩倍的新陣列 Object [] newData=new Object[size<<1]; //拷貝陣列 System.arraycopy(data,0,newData,0,size); data=newData; }else{ //假隊滿:前移元素 //所有資料前移front位 for (int i=front;i<size;i++){ data[i-front] = data[i]; } //隊尾前移front位 rear-=front; //隊頭指向0 front=0; } } //隊尾插入元素 data[rear]=element; rear++; size++; } /** * 出隊 * @return */ public Object deQueue() { if (isEmpty()){ throw new RuntimeException("隊空"); } //取隊頭元素 Object f=data[front]; //隊頭陣列元素指向null,幫助gc data[front]=null; //隊首指向下一元素 front++; //元素個數減1 size--; //返回隊首元素 return f; } /** * 取隊首元素(不刪除隊首元素) * @return */ public Object getHead() { if (isEmpty()){ throw new RuntimeException("隊空"); } return data[front]; } }
時間複雜度分析:
- 入隊:平均O(1),最壞情況(擴容)O(n)
- 出隊:O(1)
- 取隊首:O(1)
3、鏈式佇列
這裡使用單向連結串列來實現鏈式佇列。
入隊是將隊尾指向插入的新元素,出隊是將隊頭指向隊頭的下一個元素。
/**
* @Author 三分惡
* @Date 2020/8/27
* @Description
*/
public class LinkedQueue implements Queue{
/**
* 節點類
* @param <T>
*/
class Node<T>{
private Object data; //資料
private Node next; //下一節點
Node(Object it, Node nextVal){
this.data=it;
this.next=nextVal;
}
}
private Node front; //隊頭
private Node rear; //隊尾
private int size; //佇列元素個數
/**
* 判空
* @return
*/
public boolean isEmpty() {
return size==0;
}
public int size() {
return size;
}
/**
* 入棧
* @param element
*/
public void enQueue(Object element) {
Node node=new Node(element,null);
//如果佇列為空
if (rear==null){
rear=node;
front=node;
}else{
rear.next=node;
}
size++;
}
/**
* 出隊
* @return
*/
public Object deQueue() {
//佇列為空
if (front==null){
throw new RuntimeException("佇列為空");
}
//隊頭
Node node=front;
front=front.next;
size--;
return node.data;
}
/**
* 取隊頭元素
*/
public Object getHead() {
//佇列為空
if (front==null){
throw new RuntimeException("佇列為空");
}
return front.data;
}
}
時間複雜度分析:
- 入隊:O(1)
- 出隊:O(1)
- 取隊首:O(1)
除此之外,順序佇列有變種迴圈佇列,當rear到達陣列的最大下標時,重新指回陣列下標為0的位置;
鏈式佇列有雙端佇列,隊頭、隊尾都可以進行入隊、出隊操作的佇列,可以通過雙向連結串列實現;
4、java中的佇列
java中有一個佇列介面java.util.Queue,定義了佇列的一些方法。
它有一個子介面,java.util.Deque,定義了雙端佇列的方法。
LinkedList實現了java.util.Deque介面,所以LinkedList能作為佇列也能作為雙端佇列使用。詳見LinkedList原始碼閱讀筆記。
原始碼地址:https://gitee.com/LaughterYoung/data-structure-learn.git
上一篇:重學資料結構(二、棧)
本文為學習筆記類部落格,主要資料來源如下!
參考:
【1】:鄧俊輝 編著. 《資料結構與演算法》
【2】:王世民 等編著 . 《資料結構與演算法分析》
【3】: Michael T. Goodrich 等編著.《Data-Structures-and-Algorithms-in-Java-6th-Edition》
【4】:嚴蔚敏、吳偉民 編著 . 《資料結構》
【5】:程傑 編著 . 《大話資料結構》
【6】:看完這篇你還不知道這些佇列,我這些圖白作了