我理解的資料結構(三)—— 佇列(Queue)
阿新 • • 發佈:2018-11-17
我理解的資料結構(三)—— 佇列(Queue)
一、佇列
- 佇列是一種線性結構
- 相比陣列,佇列對應的操作是陣列的子集
- 只能從一端(隊尾)新增元素,只能從另一端(隊首)取出元素
- 佇列是一種先進先出的資料結構(FIFO)
二、陣列佇列與迴圈佇列
1. 陣列佇列
如果你有看過我之前的文章 不要小看了陣列或者 棧,你就會發現,自己封裝一個數組佇列是如此的輕鬆加愉快!
(1)先定義一個介面,介面中定義佇列需要實現的方法
public interface Queue<E> { int getSize(); boolean isEmpty(); // 檢視隊首元素 E getFront(); // 入隊 void enqueue(E ele); // 出隊 E dequeue(); }
(2)實現陣列佇列
public class ArrayQueue<E> implements Queue<E> { // 這裡的陣列是在之前的文章中封裝好的,直接拿來用就好了 private ArrayNew<E> array; public ArrayQueue(int capacity) { array = new ArrayNew<>(capacity); } public ArrayQueue() { this(10); } public int getCapacity() { return array.getCapacity(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmpty() { return array.isEmpty(); } @Override public E getFront() { return array.getFirst(); } @Override public void enqueue(E ele) { array.addLast(ele); } @Override public E dequeue() { return array.removeFirst(); } @Override public String toString() { StringBuffer res = new StringBuffer(); res.append(String.format("arrayQueue: size = %d, capacity = %d\n", getSize(), getCapacity())); res.append("front ["); for (int i = 0; i < array.getSize(); i++) { res.append(array.get(i)); if (i != getSize() - 1) { res.append(", "); } } res.append("] tail"); return res.toString(); } }
(3)陣列佇列的複雜度
方法 | 複雜度 |
---|---|
enqueue |
O(1) 均攤 |
dequeue |
O(n) |
front |
O(1) |
getSize |
O(1) |
isEmpty |
O(1) |
這個時候我們會發現,在進行出隊操作的時候,陣列佇列的複雜度是0(n),如果我們頻繁的進行出隊操作,那麼其實陣列佇列的效率是很低的,如何提升陣列佇列的效能呢?這個時候我們就要用到迴圈隊列了。
2. 迴圈佇列佇列
迴圈佇列的原理:
-
dequeue
時,不要在去除隊首元素時,把整體向前移動 - 維護
front
、tail
和size
這三個屬性 -
enqueue
的時候tail++
-
dequeue
的時候front++
(1)實現迴圈佇列
public class LoopQueue<E> implements Queue<E> {
private E[] array;
private int size;
private int front;
private int tail;
public LoopQueue(int capacity) {
// 我們需要浪費一個空間去判斷佇列是否已滿,所以需要把capacity + 1
array = (E[])new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue() {
this(10);
}
// 返回使用者傳遞的佇列大小
public int getCapacity() {
return array.length - 1;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Queue is empty. Can't get front.");
}
return array[0];
}
@Override
public void enqueue(E ele) {
if (front == (tail + 1) % array.length) {
// 擴充套件佇列長度為原長度2倍
resize(getCapacity() * 2);
}
array[tail] = ele;
size++;
tail = (tail + 1) % array.length;
}
@Override
public E dequeue() {
if (isEmpty()) { // 佇列為空
throw new IllegalArgumentException("Queue is empty. Can't get dequeue.");
}
E ele = array[front];
size--;
array[front] = null;
front = (front + 1) % array.length;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ele;
}
private void resize(int newCapacity) {
E[] newArray = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newArray[i] = array[(front + i) % array.length];
}
array = newArray;
front = 0;
tail = size;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer();
res.append(String.format("queue: size = %d, capacity = %d\n", getSize(), getCapacity()));
res.append("front [");
// 迴圈條件,和迴圈增量都要注意下
for (int i = front; i != tail; i = (i + 1) % array.length) {
res.append(array[i]);
if ((i + 1) % array.length != tail) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
}
(2)迴圈佇列的複雜度
方法 | 複雜度 |
---|---|
enqueue |
O(1) 均攤 |
dequeue |
O(1) 均攤 |
front |
O(1) |
getSize |
O(1) |
isEmpty |
O(1) |
三、用時間說話
(1)用時方法
public static double test(Queue<Integer> q, int opCount) {
// 納秒
long startTime = System.nanoTime();
Random random = new Random();
for (int i = 0; i < opCount; i++) {
q.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
q.dequeue();
}
// 納秒
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
(2)呼叫
// 十萬次入隊和十萬次出隊操作
int opCount = 100000;
ArrayQueue<Integer> aq = new ArrayQueue<>();
double time1 = test(aq, opCount);
System.out.println(time1);
LoopQueue<Integer> lq = new LoopQueue<>();
double time2 = test(lq, opCount);
System.out.println(time2);
(3)結果
- 14.635995113
- 0.054536447
這個就是演算法和資料結構的力量!
原文地址:https://segmentfault.com/a/1190000016147024