數據結構之隊列(Queue)

什麼是隊列(Queue)

以前總結過相關的知識,隊列能夠類比棧來看。棧只能在一端進行操做(棧頂),添加元素或者刪除等都只能在棧頂;而隊列有兩端能夠操做,在一端添加元素,在另外一端刪除元素html

咱們把添加元素的一端稱爲隊尾;刪除元素的一端稱爲隊首java

好比生活中的排隊:城市中基本哪裏都有,這就是一個隊列。在隊伍最前面就是隊首,也是最早完成離開隊伍的。新來的只能在隊尾加入。數組

queue_overview

隊列的特色:

  • [ ] 相同數據類型元素構成的有序表(和棧同樣)。
  • [ ] 先進先出(FIFO:First In First Out)
  • [ ] 隊首刪除元素,隊尾插入元素。

隊列操做及實現概述

實現

用鏈表、數組都比較容易實現隊列。this

鏈表實現:比較簡單,在鏈表首部刪除,鏈表尾部插入便可。
鏈表沒有長度限制,不會形成溢出,並且插入刪除也很簡單,是很適合的實現方式。code

數組實現:普通數組方式,有着明顯的缺陷,長度固定、出隊時剩下全部元素都要向前挪動一位,這種出隊方式時間複雜度就是O(n)了。在此基礎上進行一些處理,造成一個環,成爲一個循環隊列htm

因此下面主要介紹的就是兩種隊列實現,用鏈表實現的隊列 和 用數組實現的循環隊列blog

操做

隊列主要兩個操做,入隊和出隊。
入隊(enQueue): 即在隊尾插入新元素。
出隊(deQueue): 即在隊首刪除一個元素。
若是本身實現,能夠添加更多公共操做,如隊長、是否隊空或隊滿、查找等等。隊列

下面實現中,爲了更直觀的顯示出隊、入隊操做,都添加了一個displayQueue()的方法。ci

單鏈表隊列實現

單鏈表隊列實現很簡單,鏈表首部即隊首,鏈表尾部即隊尾,定義兩個引用分別指向隊首和隊尾,出隊、入隊直接刪除和插入便可。時間複雜度爲O(1)。
優點,沒有額外的操做,沒有長度的限制 不需擔憂溢出。get

示意圖大體以下:
queue_linked

具體代碼以下(可結合註釋,應該比較清晰):

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標識隊首、隊尾的下標):
queue_circle_array

實現代碼(可結合註釋查看):
注:實現中的判空與判滿 使用了另外一個變量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
相關文章
相關標籤/搜索