文章目錄
(一)單端隊列(Queue)的簡介&設計
(二)單端隊列(Queue)的實現&測試
(三)練習:用棧實現隊列
(四)單端隊列(Queue)& 雙端隊列(Deque)的源碼分析
(五)雙端隊列(Deque)的簡介&設計
(六)雙端隊列(Deque)的實現&測試
(七)循環單端隊列(Circle Queue)的簡介&設計
(八)循環單端隊列(Circle Queue)的實現&測試
(九)循環單端隊列(Circle Queue):動態擴容
(十)循環單端隊列(Circle Queue):封裝索引映射
(十一)循環雙端隊列(Circle Deque)的實現
(十二)循環雙端隊列(Circle Deque)的測試
(十三)循環雙端隊列(Circle Deque)的模運算優化
(十四)循環雙端隊列(Circle Deque)的clear方法
(十五)作業:用隊列實現棧
(一)單端隊列(Queue)的簡介&設計
(二)單端隊列(Queue)的實現&測試
import com.zzq.list.LinkedList;
import com.zzq.list.List;
public class Queue<E> {
private List<E> list = new LinkedList<>();
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public void clear() {
list.clear();
}
public void enQueue(E element) {
list.add(element);
}
public E deQueue() {
return list.remove(0);
}
public E front() {
return list.get(0);
}
}
測試結果如下:
(三)練習:用棧實現隊列
https://leetcode-cn.com/problems/implement-queue-using-stacks/
準備2個棧: inStack、outStack
入隊:push到inStack中
出隊:如果outStack爲空,將inStack所有元素逐一彈出,push到outStack,outStack彈出棧頂元素
出隊:如果outStack不爲空,outStack彈出棧頂元素
假設如下操作:11入隊、 22入隊、出隊、 33入隊、 出隊
代碼實現如下:
public class _232_用棧實現隊列 {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
/**
* Initialize your data structure here.
*/
public _232_用棧實現隊列() {
inStack = new Stack<>();
outStack = new Stack<>();
}
/**
* 入隊
*/
public void push(int x) {
inStack.push(x);
}
/**
* 出隊
*/
public int pop() {
checkOutStack();
return outStack.pop();
}
/**
* 獲取隊頭元素
*/
public int peek() {
checkOutStack();
return outStack.peek();
}
/**
* 是否爲空
*/
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void checkOutStack() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
}
(四)單端隊列(Queue)& 雙端隊列(Deque)的源碼分析
其實JDK官方提供的LinkedList就間接實現了Queue接口
(五)雙端隊列(Deque)的簡介&設計
(六)雙端隊列(Deque)的實現&測試
代碼實現如下:
import com.zzq.list.LinkedList;
import com.zzq.list.List;
public class Deque<E> {
private List<E> list = new LinkedList<>();
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public void clear() {
list.clear();
}
public void enQueueRear(E element) {
list.add(element);
}
public E deQueueFront() {
return list.remove(0);
}
public void enQueueFront(E element) {
list.add(0, element);
}
public E deQueueRear() {
return list.remove(list.size() - 1);
}
public E front() {
return list.get(0);
}
public E rear() {
return list.get(list.size() - 1);
}
}
測試結果如下:
(七)循環單端隊列(Circle Queue)的簡介&設計
詳細介紹可以看:進一步優化ArrayList
當數組頭部有空位,而尾部滿了的時候,要添加節點,可以往頭部添加,通過取模運算算出真實位置,所以叫循環隊列
(八)循環單端隊列(Circle Queue)的實現&測試
代碼實現如下:
public class CircleQueue<E> {
private int front;//記錄隊頭元素的下標
private int size;
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public CircleQueue() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void enQueue(E element) {
// elements[front + size] = element; //不能直接相加,會有溢出的風險
elements[(front + size) % elements.length] = element;//要通過取模運算達到循環的目的
size++;
}
public E deQueue() {
E frontElement = elements[front];
elements[front] = null;
// front++; //出隊是指把隊頭元素彈出,所以front要指向下一個
front = (front + 1) % elements.length; //要通過取模運算達到循環的目的
size--;
return frontElement;
}
public E front() {
return elements[front];
}
}
最後加上toString()
方法如下:
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("capcacity=").append(elements.length)
.append(" size=").append(size)
.append(" front=").append(front)
.append(", [");
for (int i = 0; i < elements.length; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
測試結果如下:
(九)循環單端隊列(Circle Queue):動態擴容
擴容過程如下:
代碼實現如下:
(十)循環單端隊列(Circle Queue):封裝索引映射
private int index(int index) {
return (front + index) % elements.length;
}
其它地方的修改也是如此,不再展示了
(十一)循環雙端隊列(Circle Deque)的實現
循環雙端隊列:可以進行兩端添加、刪除操作的循環單端隊列
public class CircleDeque<E> {
private int front;
private int size;
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public CircleDeque() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
}
public void enQueueRear(E element) {
ensureCapacity(size + 1);
elements[index(size)] = element;
size++;
}
public E deQueueFront() {
E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
}
public void enQueueFront(E element) {
ensureCapacity(size + 1);
front = index(-1);// front-1 --> index(-1)
elements[front] = element;
size++;
}
public E deQueueRear() {
int rearIndex = index(size + 1);
E rear = elements[rearIndex];
elements[rearIndex] = null;
size--;
return rear;
}
public E front() {
return elements[front];
}
public E rear() {
return elements[index(size - 1)];
}
private int index(int index) {
index += front;
if (index < 0) {
// 如隊列頭部沒有空位,elements[index(-1)] = element 相當於 -1%10=-1
// 此時只要在加上數組的長度即可得到正確的位置
return index + elements.length;
}
return index % elements.length;
}
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
// 新容量爲舊容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
// newElements[i] = elements[(i + front) % elements.length];
newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("capcacity=").append(elements.length)
.append(" size=").append(size)
.append(" front=").append(front)
.append(", [");
for (int i = 0; i < elements.length; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
}
(十二)循環雙端隊列(Circle Deque)的測試
public class Main {
public static void main(String[] args) {
CircleDeque<Integer> queue = new CircleDeque<>();
// 頭5 4 3 2 1 100 101 102 103 104 105 106 8 7 6 尾
// 每次擴容,都把真正的0號節點放在0號位置,以此類推
// 頭 8 7 6 5 4 3 2 1 100 101 102 103 104 105 106 107 108 109 null null 10 9 尾
for (int i = 0; i < 10; i++) {
queue.enQueueFront(i + 1);
queue.enQueueRear(i + 100);
}
// 頭 null 7 6 5 4 3 2 1 100 101 102 103 104 105 106 null null null null null null null 尾
for (int i = 0; i < 3; i++) {
queue.deQueueFront();
queue.deQueueRear();
}
// 頭 11 7 6 5 4 3 2 1 100 101 102 103 104 105 106 null null null null null null 12 尾
queue.enQueueFront(11);
queue.enQueueFront(12);
System.out.println(queue);
while (!queue.isEmpty()) {
System.out.println(queue.deQueueFront());
}
}
}
(十三)循環雙端隊列(Circle Deque)的模運算優化
我們要儘量避免使用乘*、 除/、 模%、 浮點數運算
,效率低下
比如下面這個取模運算的例子,效率就比較底下:
優化後:
(前提條件:n < 2m , n >= 0 , m > 0
)
簡化後:
CircleQueue的index(int index)
方法優化後:
private int index(int index) {
index += front;
return index - (index >= elements.length ? elements.length : 0);
}
CircleDeque的index(int index)
方法優化後:
private int index(int index) {
index += front;
if (index < 0) {
return index + elements.length;
}
return index - (index >= elements.length ? elements.length : 0);
}
(十四)循環雙端隊列(Circle Deque)的clear方法
public void clear() {
for (int i = 0; i < size; i++) {
elements[index(i)] = null;
}
front = 0;
size = 0;
}
(十五)作業:用隊列實現棧
225. 用隊列實現棧:https://leetcode-cn.com/problems/implement-stack-using-queues/