簡介
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/
最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!