1. 使用數組實現一個簡單的隊列

/**
* ===========================
* 隊列首部 00000000000000000000000000 隊列尾部
* ===========================
*/
public class ArrayQueue<Element> implements Queue<Element>{
// 通過內部的array來實現
private Array<Element> array;
// 構造函數
public ArrayQueue(int capacity){
this.array = new Array<>(capacity);
}
// 默認的構造函數
public ArrayQueue(){
this.array = new Array<>();
}
@Override
public int getSize() {
return this.array.getSize();
}
@Override
public boolean isEmpty() {
return this.array.isEmpty();
}
public int getCapacity(){
return this.array.getCapacity();
}
@Override
public void enqueue(Element element) {
// 進入隊列(數組的末尾來添加元素)
this.array.addLast(element);
}
@Override
public Element dequeue() {
// 出隊列(刪除最后一個元素),數組的第一個元素
return this.array.removeFirst();
}
@Override
public Element getFront() {
// 獲取第一個元素(對首部的第一個元素)
return this.array.getFirst();
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
// 使用自定義的方式實現數組的輸出
stringBuilder.append("Queue:");
// 開始實現數組元素的查詢
stringBuilder.append("front [");
for (int i = 0; i < this.array.getSize(); i++) {
stringBuilder.append(this.array.get(i));
// 開始實現數組元素的回顯(只要下表不是最后一個元素的話,就直接輸出這個元素)
if (i != this.array.getSize()-1)
stringBuilder.append(", ");
}
stringBuilder.append("] tail");
// 實現數組元素的輸出
return stringBuilder.toString();
}
}
2. 使用數組實現一個循環隊列(維護一個size變量)
/**
* 循環隊列的幾個要點:
* 1. tail = head 說明隊列就是滿的
* 2. 循環隊列需要空出來一個位置
*/
public class LoopQueue<E> implements Queue {
private E[] data;
private int head; // 首部指針
private int tail; // 尾部指針
private int size; // 可以通過head以及tail的位置情況去計算size的大小【注意是不能直接使用getCapacity的】
// 實現循環隊列
public LoopQueue() {
// 設置一個隊列的默認的大小
this(10);
}
// 循環隊列的實現
public LoopQueue(int capacity) {
this.data = (E[]) new Object[capacity + 1];
// 需要多出來一個
this.head = 0;
this.tail = 0;
this.size = 0;
}
@Override
public int getSize() {
// 計算容量大小
return this.size;
}
// capacity表示的這個隊列中最大可以容納的元素個數【這是一個固定值】
@Override
public int getCapacity() {
// 由於隊列默認占了一個空的位置,因此sizt = this.length-1
return this.data.length - 1;
}
// 當head和tail的值相同的時候隊列滿了
public boolean isEmpty() {
return head == tail;
}
@Override
public void enqueue(Object value) {
// 1. 開始判斷隊列有沒有充滿, 因為數組的下表是不會改變的,所以這里需要以數組的下標為一個參考點, 而不是this.data.length-14
if ((tail + 1) % this.data.length == head) {
// 2. 開始進行擴容, 原來的二倍, 由於默認的時候已經白白浪費了一個空間,因此這里就直接擴容為原來的二倍即可
resize(2 * getCapacity());
}
// 2. 如果沒有滿的話,就開始添加元素
this.data[tail] = (E) value;
// 3. 添加完畢之后(tail是一個循環的位置,不可以直接相加)
// this.tail = 2;
this.tail = (this.tail+1) % data.length; // data.length對於數組的下標
// int i = (this.tail + 1) % data.length;
// 4. 隊里的長度
this.size++;
}
/**
* 開始resize數組元素
*
* @param capacity
*/
private void resize(int capacity) {
// 開始進行數組的擴容
// 1. 創建一個新的容器(默認多一個位置)
E[] newData = (E[]) new Object[capacity + 1];
// 2. 開始轉移元素到新的容器
for (int i = 0; i < size; i++) {
int index = (head + i) % data.length;
newData[i] = data[index];
}
// 添加完畢之后,開始回收之前的data,data里面的數據一直是最新的數據
this.data = newData; // jvm的垃圾回收機制會自動回收內存data
// 3. 添加完畢
this.head = 0;
// 4. 指向尾部
this.tail = size;
}
@Override
public Object dequeue() {
// 1. 先來看下隊列中有沒有元素數據
if (this.size == 0)
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 2. 開始看一下內存的大小
Object ret = this.data[head];
// 3. 開始刪除數據
this.data[head] = null;
// TODO 注意點:直接使用+1而不是使用++
// 4. 開始向后移動, 為了防止出錯,盡量使用 this.head+1來進行操作,而不是使用 this.haad++, 否則可能會出現死循環的情況【特別注意!!!!!!!!!!!!】
this.head = (this.head+1) % data.length;
// 5. 計算新的容量大小
this.size--;
// 6. 開始進行縮容操作, d當容量為1, size為0的時候,就不需要縮小容量、、
if (this.size == this.getCapacity() / 4)
// 縮小為原來的一半大小的容量
resize(this.getCapacity() / 2);
// 獲取出隊列的元素
return ret;
}
@Override
public Object getFront() {
// 由於front的位置一直是隊列的第一個元素,直接返回即可
if (this.size == 0)
throw new IllegalArgumentException("Empty queue cannot get element!");
return this.data[head];
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("Array : size=%d, capacity=%d; ", size, getCapacity()));
stringBuilder.append("LoopQueue head [");
// 第一個元素在head的位置,最后一個元素在tail-1的位置
// 注意循環隊列的遍歷方式,是一個循環
for (int i = this.head; i != tail; i = (i+1) % data.length) {
stringBuilder.append(data[i]);
// 只要不是最后一個元素的話, 由於循環條件中已經規定了i!=tail, 因此這里是不能直接這樣來判斷的
if ((i+1)%data.length == this.tail) {
// 此時就是最后一個元素
} else {
stringBuilder.append(", ");
}
}
stringBuilder.append("] tail");
return stringBuilder.toString();
}
}
3.使用數組實現一個循環隊列(不維護size變量)
/**
* 使用數組實現一個循環隊列的數據結構
*/
public class WithoutSizeLoopQueue<E> implements Queue<E> {
private E[] data;
private int head;
private int tail;
public WithoutSizeLoopQueue(int capacity) {
// 泛型不能直接被實例化, 由於循環隊列默認會空出來一個位置,這里需要注意
this.data = (E[]) new Object[capacity + 1];
this.head = 0;
this.tail = 0;
}
public WithoutSizeLoopQueue() {
this(10);
}
@Override
public int getSize() {
// 計算數組的元素個數
if (tail >= head) {
return tail - head;
} else {
int offSet = head - tail;
return this.data.length - offSet;
}
}
@Override
public int getCapacity() {
return data.length - 1;
}
@Override
public void enqueue(E value) {
// 開始進入隊列
// 1. 先來看下隊列有沒有滿
if ((tail + 1) % data.length == head)
// 開始擴大容量
resize(2 * getCapacity());
// 2. 開始進入隊列
data[tail] = value;
// 3. 開始移動下標
tail++;
// 4. 開始更新容量【沒必要】
}
public void resize(int newCapacity) {
// 1. 更新為新的容量
E[] newData = (E[]) new Object[newCapacity + 1];
// 2. 開始轉移數據到新的數組中去
for (int i = 0; i < getSize(); i++) {
newData[i] = data[(i + head) % data.length];
}
// 3. 銷毀原來的數據信息
data = newData;
// 【錯誤警告】bug:移動到新的這個數組里面之后,需要重新改變head和tail的位置【bug】
tail = getSize(); // 尾部節點始終指向最后一個元素的后面一個位置, 此時如果直接使用getSize實際上獲取到的是上一次的元素的個數,而不是最新的元素的個數信息(需要放在前面,否在獲取到的size就不是同一個了,特別重要)
head = 0; // 頭節點指向的是第一個元素
// 4. 直接銷毀
newData = null;
}
@Override
public E dequeue() {
// 1.先來看下隊列是否為空
if (getSize() == 0)
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 2.開始出head位置的元素
E value = data[head];
// 3. 刪除元素
data[head] = null;
// 4. 移動head
head = (head+1) % data.length;
// 此時需要進行一次判斷
if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
// 如果數組縮小容量之后,這里的
return value;
}
@Override
public E getFront() {
return dequeue();
}
@Override
public String toString(){
// 開始遍歷輸出隊列的元素
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("LoopQueue: size = %d, capacity = %d\n", getSize(), getCapacity()));
stringBuilder.append("front ");
// 從head位置開始遍歷
for (int i = head; i != tail ; i = (i+1) % data.length) {
stringBuilder.append(data[i]);
if ((i + 1) % data.length != tail)
stringBuilder.append(", ");
}
return stringBuilder.append(" tail").toString();
}
}
4. 使用鏈表實現一個隊列
/**
* 使用鏈表實現的隊列
*/
public class LinkedListQueue<E> implements Queue<E>{
// 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的)
private class Node {
// 用於存儲元素
public E e;
// 用於存儲下一個節點
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
// 定義隊列需要的參數
private Node head, tail;
private int size;
public LinkedListQueue(){
this.head = null;
this.tail = null;
this.size = 0;
}
@Override
public int getSize() {
return this.size;
}
public boolean isEmpty(){
return this.size == 0;
}
@Override
public int getCapacity() {
return 0;
}
/**
* 入隊
* @param value
*/
@Override
public void enqueue(E value) {
// 入隊從尾部開始
if (tail == null){
// 1. 如果此時沒有元素的話
tail = new Node(value);
head = tail;
}
else {
// 2. 如果已經存在了元素,那么隊列尾部肯定是不為空的,而是指向了一個空節點
tail.next = new Node(value);
// 移動尾節點
tail = tail.next;
}
size++;
}
/**
* 出隊
* @return
*/
@Override
public E dequeue() {
if (isEmpty())
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 1. 存儲出隊的元素
Node retNode = head;
// 2.head節點下移動
head = head.next;
// 3.刪除原來的頭節點
retNode.next = null; // 實際上刪除的是這個節點的數據域
// 如果刪除了最后一個元素之后
if (head == null)
tail = null;
size--;
return retNode.e;
}
@Override
public E getFront() {
if (isEmpty())
throw new IllegalArgumentException("Empty queue cannot dequeue!");
return head.e;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
Node cur = head;
stringBuilder.append("head:");
// 1. 第一種循環遍歷的方式, 注意這里判斷的是每一次的cur是否為空, 而不是判斷cur.next, 否則第一個元素是不能打印輸出的
while (cur != null) {
stringBuilder.append(cur + "->");
// 繼續向后移動節點
cur = cur.next;
}
stringBuilder.append("NULL tail");
return stringBuilder.toString();
}
}
5. 使用一個帶有虛擬頭結點的鏈表實現一個隊列
/**
* 帶有虛擬頭結點的鏈表實現的隊列
*/
public class DummyHeadLinkedListQueue<E> implements Queue<E> {
// 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的)
private class Node {
// 用於存儲元素
public E e;
// 用於存儲下一個節點
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
// 定義一個虛擬頭結點
private Node dummyHead, tail;
private int size;
public DummyHeadLinkedListQueue(){
this.dummyHead = new Node(null, null);
this.tail = null;
this.size = 0;
}
@Override
public int getSize() {
return this.size;
}
public Boolean isEmpty(){
return this.size == 0;
}
@Override
public int getCapacity() {
throw new IllegalArgumentException("LinkedList cannot getCapacity!");
}
@Override
public void enqueue(E value) {
// 開始入隊列(從隊列的尾部進行入隊列)
// 1. 構造插入的節點
Node node = new Node(value);
// 2. 尾部插入節點 //bug : 由於默認的時候 tail為null,此時如果直接訪問tail.next 是錯誤的
if (tail == null) {
dummyHead.next = node;
// 說明此時隊列為空的
tail = node;
} else {
tail.next = node;
// 3. 尾部節點向后移動
tail = tail.next;
}
// 4. 隊列長度增加
size++;
}
@Override
public E dequeue() {
// 元素出隊列從鏈表的頭部出去
// 1. 獲取頭結點的位置
Node head = dummyHead.next;
// 緩存出隊的元素
Node retNode = head;
// 2. 移動節點
dummyHead.next = head.next;
// 4. 更新容量
size--;
head = null;
if (isEmpty())
tail = null; // bug修復
return (E) retNode;
}
@Override
public E getFront() {
return null;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Queue head:");
Node cur = dummyHead.next;
while (cur != null){
stringBuilder.append(cur + "->");
cur = cur.next;
}
return stringBuilder.append("null tail").toString();
}
}
以上就是創建隊列實現的5中方式,每種方式都有各自的特點,就做個簡單總結吧!
隊列的時間復雜度分析:
+ 隊列Queue[數組隊列]
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(n) 移出隊列首部的元素,需要其他元素向前補齊
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
+ 循環隊列
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(1)
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
以上就是對於隊列實現的幾種方式的總結了。
