簡介
dequeue指的是雙向佇列,可以分別從佇列的頭部插入和獲取資料,也可以從佇列的尾部插入和獲取資料。
本文將會介紹一下怎麼建立dequeue和dequeue的一些基本操作。
雙向佇列的實現
和普通佇列專案,雙向佇列可以分別在頭部和尾部進行插入和刪除工作,所以一個dequeue需要實現這4個方法:
- insertFront(): 從dequeue頭部插入資料
- insertLast(): 從dequeue尾部插入資料
- deleteFront(): 從dequeue頭部刪除資料
- deleteLast(): 從dequeue尾部刪除資料
同樣的我們也需要一個head和一個rear來指向佇列的頭部和尾部節點。
也就是說實現了這四個方法的佇列就是雙向佇列。我們不管它內部是怎麼實現的。
接下來我們來直觀的感受一下dequeue的插入和刪除操作:
- 在頭部插入
- 在尾部插入
- 在頭部刪除
- 在尾部刪除
雙向佇列也可以有很多種實現方式,比如迴圈陣列和連結串列。
雙向佇列的陣列實現
因為陣列本身已經有前後關係,也就是說知道head可以拿到它後面一個資料,知道rear也可以拿到它前面一個資料。
所以陣列的實現中,儲存head和rear的index值已經夠了。
我們只需要新增向頭部插入資料和向尾部刪除資料的方法即可:
//從頭部入佇列
public void insertFront(int data){
if(isFull()){
System.out.println("Queue is full");
}else{
//從頭部插入ArrayDeque
head = (head + capacity - 1) % capacity;
array[head]= data;
//如果插入之前佇列為空,將real指向head
if(rear == -1 ){
rear = head;
}
}
}
//從尾部取資料
public int deleteLast(){
int data;
if(isEmpty()){
System.out.println("Queue is empty");
return -1;
}else{
data= array[rear];
//如果只有一個元素,則重置head和real
if(head == rear){
head= -1;
rear = -1;
}else{
rear = (rear + capacity - 1)%capacity;
}
return data;
}
}
雙向佇列的動態陣列實現
動態陣列可以動態改變陣列大小,這裡我們使用倍增的方式來擴充套件陣列。
看下擴充套件方法怎麼實現:
//因為是迴圈陣列,這裡不能做簡單的陣列拷貝
private void extendQueue(){
int newCapacity= capacity*2;
int[] newArray= new int[newCapacity];
//先全部拷貝
System.arraycopy(array,0,newArray,0,array.length);
//如果rear<head,表示已經進行迴圈了,需要將0-head之間的資料置空,並將資料拷貝到新陣列的相應位置
if(rear < head){
for(int i=0; i< head; i++){
//重置0-head的資料
newArray[i]= -1;
//拷貝到新的位置
newArray[i+capacity]=array[i];
}
//重置rear的位置
rear = rear +capacity;
//重置capacity和array
capacity=newCapacity;
array=newArray;
}
}
因為是迴圈陣列,這裡不能做簡單的陣列拷貝,我們需要判斷rear和head的位置來判斷是否進入到了迴圈結構。
如果進入到了迴圈結構,我們需要重置相應的欄位資料,並拷貝到新陣列中。
向頭部插入資料和向尾部刪除資料的方法和基本佇列的實現是一致的,這裡就不列出來了。
雙向佇列的連結串列實現
如果使用連結串列來實現雙向佇列會有什麼問題呢?
在頭部插入和在尾部插入都可以快速定位到目標節點。但是我們考慮一下尾部刪除的問題。
尾部刪除我們需要找到尾部節點的前一個節點,將這個節點置位rear節點。這就需要我們能夠通過rear節點找到它的前一個節點。
所以基本的連結串列已經滿足不了我們的需求了。 這裡我們需要使用雙向連結串列。
public class LinkedListDeQueue {
//head節點
private Node headNode;
//rear節點
private Node rearNode;
class Node {
int data;
Node next;
Node prev;
//Node的建構函式
Node(int d) {
data = d;
}
}
public boolean isEmpty(){
return headNode==null;
}
//從隊尾插入
public void insertLast(int data){
Node newNode= new Node(data);
//將rearNode的next指向新插入的節點
if(rearNode !=null){
rearNode.next=newNode;
newNode.prev=rearNode;
}
rearNode=newNode;
if(headNode == null){
headNode=newNode;
}
}
//從隊首插入
public void insertFront(int data){
if(headNode == null){
headNode= new Node(data);
}else{
Node newNode= new Node(data);
newNode.next= headNode;
headNode.prev= newNode;
headNode= newNode;
}
}
//從隊首刪除
public int deleteFront(){
int data;
if(isEmpty()){
System.out.println("Queue is empty");
return -1;
}else{
data=headNode.data;
headNode=headNode.next;
headNode.prev=null;
}
return data;
}
//從隊尾刪除
public int deleteLast(){
int data;
if(isEmpty()){
System.out.println("Queue is empty");
return -1;
}else{
data=rearNode.data;
rearNode=rearNode.prev;
rearNode.next=null;
}
return data;
}
}
雙向連結串列中的每一個節點都有next和prev兩個指標。通過這兩個指標,我們可以快速定位到他們的後一個節點和前一個節點。
雙向連結串列的時間複雜度
上面的3種實現的enQueue和deQueue方法,基本上都可以立馬定位到要入佇列或者出佇列的位置,所以他們的時間複雜度是O(1)。
本文的程式碼地址:
本文已收錄於 http://www.flydean.com/13-algorithm-dequeue/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!