什么是隊列(Queue)
之前總結過棧相關的知識,隊列可以類比棧來看。棧只能在一端進行操作(棧頂),添加元素或者刪除等都只能在棧頂;而隊列有兩端可以操作,在一端添加元素,在另一端刪除元素。
我們把添加元素的一端稱為隊尾;刪除元素的一端稱為隊首。
比如生活中的排隊:城市中基本哪里都有,這就是一個隊列。在隊伍最前面就是隊首,也是最先完成離開隊伍的。新來的只能在隊尾加入。
隊列的特點:
- [ ] 相同數據類型元素構成的有序表(和棧一樣)。
- [ ] 先進先出(FIFO:First In First Out)
- [ ] 隊首刪除元素,隊尾插入元素。
隊列操作及實現概述
實現
用鏈表、數組都比較容易實現隊列。
鏈表實現:比較簡單,在鏈表首部刪除,鏈表尾部插入即可。
鏈表沒有長度限制,不會造成溢出,而且插入刪除也很簡單,是很適合的實現方式。
數組實現:普通數組方式,有着明顯的缺陷,長度固定、出隊時剩下所有元素都要向前挪動一位,這種出隊方式時間復雜度就是O(n)了。在此基礎上進行一些處理,形成一個環,成為一個循環隊列。
所以下面主要介紹的就是兩種隊列實現,用鏈表實現的隊列 和 用數組實現的循環隊列。
操作
隊列主要兩個操作,入隊和出隊。
入隊(enQueue): 即在隊尾插入新元素。
出隊(deQueue): 即在隊首刪除一個元素。
如果自己實現,可以添加更多公共操作,如隊長、是否隊空或隊滿、查找等等。
下面實現中,為了更直觀的顯示出隊、入隊操作,都添加了一個displayQueue()的方法。
單鏈表隊列實現
單鏈表隊列實現很簡單,鏈表首部即隊首,鏈表尾部即隊尾,定義兩個引用分別指向隊首和隊尾,出隊、入隊直接刪除和插入即可。時間復雜度為O(1)。
優勢,沒有額外的操作,沒有長度的限制 不需擔心溢出。
示意圖大致如下:
具體代碼如下(可結合注釋,應該比較清晰):
public class QueueLinkedList<E> {
public static void main(String[] args) {
//入隊1,2,3,4,5;然后出隊2次
QueueLinkedList<Integer> queueLinkedList = new QueueLinkedList<Integer>();
queueLinkedList.displayQueue();
for (int i = 1; i < 6; i++) {
queueLinkedList.enQueue(Integer.valueOf(i));
}
queueLinkedList.displayQueue();
queueLinkedList.deQueue();
queueLinkedList.displayQueue();
queueLinkedList.deQueue();
queueLinkedList.displayQueue();
}
//定義隊首引用,指向鏈表首部
private QueueNode<E> front;
//定義隊尾引用,指向鏈表尾部
private QueueNode<E> rear;
//節點定義
static class QueueNode<E> {
E data;
QueueNode<E> next;
public QueueNode(E data) {
this.data = data;
}
}
//初始化,空的隊列。
public QueueLinkedList() {
this.front = this.rear = null;
}
//入隊,鏈表尾部插入
public void enQueue(E value) {
QueueNode<E> newNode = new QueueNode<E>(value);
//空隊列時
if (this.rear == null) {
this.front = this.rear = newNode;
return;
}
this.rear.next = newNode;
this.rear = newNode;
}
//出隊,鏈表首部刪除
public E deQueue() {
//隊列為空時
if (this.front == null)
return null;
E result = this.front.data;
this.front = this.front.next;
//隊列中只有一個元素時
if (this.front == null)
this.rear = null;
return result;
}
//打印隊列所有元素,以及隊首、隊尾信息
public void displayQueue() {
if (this.front == null) {
System.out.println("front:null<-...<-rear:null");
System.out.println("This is an empty queue!");
System.out.println();
return;
}
System.out.println("front:" + this.front.data +"<-...<-rear:" + this.rear.data);
QueueNode<E> tmpNode = this.front;
while (tmpNode != null) {
System.out.print(tmpNode.data + "<-");
tmpNode = tmpNode.next;
}
System.out.println();
System.out.println();
}
}
實驗結果為:
front:null<-...<-rear:null
This is an empty queue!
front:1<-...<-rear:5
1<-2<-3<-4<-5<-
front:2<-...<-rear:5
2<-3<-4<-5<-
front:3<-...<-rear:5
3<-4<-5<-
數組循環隊列實現
若用普通數組實現隊列,就會發現每次刪除隊首時所有元素都需要向前移動一位,這樣實現肯定是不合適的。
於是,在上述基礎上做一些處理,便形成了循環隊列:將數組隊列看出一個首尾相連的環形。用front和rear表示隊首和隊尾相關元素的下標(不一定是front是隊首下標,rear是隊尾下表),這樣就能很容易確定隊列的元素是哪些了。出隊、入隊改變這兩個值即可而不需要移動數組中元素了。
下面實現用變量front和rear標識隊首、隊尾的下標,入隊和出隊的方式基本一致:數組大小為capacity,即front=(font+1)%capacity 或 rear=(rear+1)%capacity。
也可以用front標識隊首,rear標識隊尾的下一個位置,只是不同的標識含義, 初始化 和 隊列為空 及 隊列為滿 的判斷和處理需要注意,是不同的。 (rear標識隊尾下一個位置時,初始化front=rear=0,空即front==rear。而rear標識隊尾即下面的實現,可以具體看下)
大致示意圖如下(front和rear標識隊首、隊尾的下標):
實現代碼(可結合注釋查看):
注:實現中的判空與判滿 使用了另一個變量queueCount表示隊列元素個數,便於返回隊列大小。也可以使用front和rear之間關系進行判斷
public class QueueCircleArray {
public static void main(String[] args) {
//隊列入隊1,2,3,4;然后兩次出隊;然后入隊10,20
QueueCircleArray queueCircleArray = new QueueCircleArray(4);
queueCircleArray.displayQueue();
for (int i = 1; i < 5; i++) {
queueCircleArray.enQueue(Integer.valueOf(i));
}
queueCircleArray.displayQueue();
queueCircleArray.deQueue();
queueCircleArray.deQueue();
queueCircleArray.displayQueue();
queueCircleArray.enQueue(Integer.valueOf(10));
queueCircleArray.enQueue(Integer.valueOf(20));
queueCircleArray.displayQueue();
}
//數組及大小
private Object[] array;
private int capacity;
//隊首 隊尾對應數組中的下標
private int front, rear;
//隊列的大小
private int queueCount;
public QueueCircleArray() {
this(10);
}
public QueueCircleArray(int capacity) {
this.capacity = capacity;
this.front = queueCount = 0;
//注意這里初始化
this.rear = this.capacity - 1;
this.array = new Object[this.capacity];
}
public boolean isFull() {
return this.queueCount == this.capacity;
}
public boolean isEmpty() {
return this.queueCount == 0;
}
public void enQueue(Object value) {
if (isFull())
throw new RuntimeException("Queue is Full!");
//隊尾向后移動一位,即將插入數據的位置
this.rear = (this.rear+1)%this.capacity;
//插入數據,入隊
this.array[this.rear] = value;
//隊列大小加1
this.queueCount++;
}
public Object deQueue() {
if (isEmpty())
throw new RuntimeException("Queue is Empty!");
Object tmpObject = this.array[this.front];
//隊首向后移動一位,之前的位置即出隊的元素。
this.front = (this.front+1)%this.capacity;
//隊列大小減1
this.queueCount--;
return tmpObject;
}
public void displayQueue() {
if (isEmpty()) {
System.out.println("front:null<-...<-rear:null");
System.out.println("This is an empty queue!");
System.out.println();
return;
}
int tmp = this.front;
System.out.println("front:["+ this.front + "]" + this.array[this.front]
+"<-...<-rear:[" + this.rear + "]" + this.array[this.rear]);
while(tmp != this.rear) {
System.out.print(this.array[tmp] + "<-");
tmp = (tmp + 1)%this.capacity;
}
if (tmp == this.rear)
System.out.println(this.array[tmp]);
System.out.println();
}
}
實驗結果:
front:null<-...<-rear:null
This is an empty queue!
front:[0]1<-...<-rear:[3]4
1<-2<-3<-4
front:[2]3<-...<-rear:[3]4
3<-4
front:[2]3<-...<-rear:[1]20
3<-4<-10<-20