想了解更多數據結構以及算法題,可以關注微信公衆號“數據結構和算法”,每天一題爲你精彩解答。也可以掃描下面的二維碼關注
基礎知識
隊列是一種特殊的線性表,他的特殊性在於我們只能操作他頭部和尾部的元素,中間的元素我們操作不了,我們只能在他的頭部進行刪除,尾部進行添加。就像大家排隊到銀行取錢一樣,先來的肯定要排到前面,後來的只能排在隊尾,所有元素都要遵守這個操作,沒有VIP會員,所以走後門插隊的現象是不可能存在的,他是一種先進先出的數據結構。我們來看一下隊列的數據結構是什麼樣的
1,一般隊列
他只能從左邊進,右邊出,隊列實現方式一般有兩種,一種是基於數組的,還一種是基於鏈表的,如果基於鏈表的倒還好說,因爲鏈表的長度是隨時都可以變的,這個實現起來比較簡單。如果是基於數組的,就會稍微有點不同,因爲數組的大小在初始化的時候就已經固定了,我們來看一下基於數組的實現,假如我們初始化一個長度是10的隊列
front指向的是隊列的頭,tail指向的是隊列尾的下一個存儲空間,最初始的時候front=0,tail=0,每添加一個元素tail就加1,每移除一個元素front就加1,但是這樣會有一個問題,如果一個元素不停的加入隊列,然後再不停的從隊列中移除,會導致tail和front越來越大,最後會導致隊列無法再加入數據了,但實際上隊列前面全部都是空的,這導致空間的極大浪費。我們自己來寫一個簡單的隊列看一下
public class MyQueue<E> {
private final Object[] data;
private final int maxSize;
private int size;
private int front = 0;
private int tail = 0;
public MyQueue(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("隊列容量必須大於0 : " + maxSize);
}
this.maxSize = maxSize;
data = new Object[this.maxSize];
}
public void add(E e) {
if (isFull()) {//這地方可以擴容也可以拋異常,爲了方便這裏我們就不在擴容了。
throw new IllegalStateException("隊列已經滿了,無法再加入……");
}
data[tail++] = e;
size++;
}
public E remove() {
if (isEmpty()) {
throw new IllegalStateException("隊列是空的,無法移除……");
}
E t = (E) data[front];
data[front++] = null;
size--;
return t;
}
//隊列頭和隊列尾指向同一空間的時候,並且沒到隊尾,表示隊列是空的
public boolean isEmpty() {
return front == tail && !isFull();
}
public boolean isFull() {//最後一個位置是不存儲數據的
return tail == maxSize - 1;
}
public int getSize() {
return size;
}
}
代碼非常簡單,當然隊列的實現不一定是這一種方式,比如我們可以讓tail指向隊尾的元素,或者以鏈表的形式來實現都是可以的,不同的實現方式會導致上面的方法有所不同。我們來測試一下
public static void main(String[] args) {
MyQueue myQueue = new MyQueue(10);
System.out.println("isEmpty()=" + myQueue.isEmpty());
System.out.println("isFull()=" + myQueue.isFull());
System.out.println("getSize()=" + myQueue.getSize());
for (int i = 0; i < 9; i++) {
myQueue.add(i * 100);
myQueue.remove();
}
System.out.println("----------------------------");
System.out.println("isEmpty()=" + myQueue.isEmpty());
System.out.println("isFull()=" + myQueue.isFull());
System.out.println("getSize()=" + myQueue.getSize());
}
看一下打印的結果
isEmpty()=true
isFull()=false
getSize()=0
----------------------------
isEmpty()=false
isFull()=true
getSize()=0
我們添加了9次,然後又移除了9次,結果隊列竟然滿了,如果我們再添加一次的話肯定會拋異常,但實際上隊列的size是0,還是空的,也就是說數組的每個位置只能使用一次,這樣就造成了極大的浪費。那麼前面使用過的空間還能不能再次利用了呢,實際上是可以的,我們可以把隊列看成是環形的,當tail到達數組末尾的時候,如果數組的前面有空位子,我們可以讓tail從頭開始,這個時候一個新的隊列就產生了,那就是雙端隊列。
2,雙端隊列
雙端隊列也是有兩個指針,front指向隊首,tail指向隊尾的下一個存儲單元,我們來看一下圖
這樣空間就可以循環利用了,不會造成浪費,我們來看下代碼
public class MyQueue<E> {
//存儲的元素
private Object[] data;
//指向隊列頭,這個頭並不是數組的第0個元素,如果這樣
// front就沒有意義了,這個從下面的addFirst(E e)方
// 法也可以看出,如果當front等於0的時候,在添加到
// first,那麼會添加到數組的末尾,並且front也指向
// 數組的末尾
private int front;
//指向隊列尾的下一個空間,可以這樣理解,front指向
// 的是第一個元素,tail指向的是最後一個元素的下一
// 個,指的是空的。
private int tail;
public MyQueue(int numElements) {
data = new Object[numElements];
}
//空間擴容,我們這裏選擇擴大一倍,當然也可以選擇其
// 他值,僅僅當滿的時候才能觸發擴容, 這時候front
// 和tail都會指向同一個元素
private void doubleCapacity() {
int p = front;
int n = data.length;//數組的長度
//關鍵是r不好理解,舉個例子,在數組中,front
// 不一定是之前0位置的,他可以指向其他位置,
// 比如原來空間大小爲16,front爲13,也就是第
// 14個元素(數組是從0開始的),那麼r就是16-13=3,
// 也就是從front往後還有多少元素,待會copy的時候
// 也是先從最後的r個元素開始
int r = n - p;
Object[] a = new Object[n << 1];//擴大一倍
System.arraycopy(data, p, a, 0, r);//先copy後面的r個
System.arraycopy(data, 0, a, r, p);//再copy前面的p個
data = a;
//重新調整front和tail的值
front = 0;
tail = n;
}
public void addFirst(E e) {
//添加到front的前面,所以front-1
front = (front - 1 + data.length) % data.length;
data[front] = e;
if (front == tail)//判斷是否滿了
doubleCapacity();
}
public void addLast(E e) {
//添加到最後一個,這個方法和addFirst有很明顯的不同,
// addFirst是添加的時候就要計算front的位置,而addLast
// 方法是存值之後在計算tail的,/因爲tail位置是沒有
// 存值的,他表示的末端元素的下一個,是空,所以存值之後
//要計算tail的值
data[tail] = e;
tail = (tail + 1) % data.length;
if (tail == front)//判斷是否滿
doubleCapacity();
}
public E removeFirst() {//刪除第一個
if (isEmpty())
throw new IllegalStateException("隊列是空的,無法移除……");
E result = (E) data[front];
data[front] = null;
// 刪除第一個,這裏的所有第一我們都認爲是front所指的,
// 不是數組的0位置,然後在計算front的值
front = (front + 1) % data.length;
return result;
}
public E removeLast() {//刪除最後一個
if (isEmpty())
throw new IllegalStateException("隊列是空的,無法移除……");
tail = (tail - 1 + data.length) % data.length;
E result = (E) data[tail];
data[tail] = null;
return result;
}
public E peekFirst() {
if (isEmpty())
throw new IllegalStateException("隊列是空的,無法獲取……");
return (E) data[front];
}
public E peekLast() {
if (isEmpty())
throw new IllegalStateException("隊列是空的,無法獲取……");
return (E) data[(tail - 1 + data.length) % data.length];
}
public int size() {//元素的size
return (tail - front + data.length) % data.length;
}
//是否爲空,在上面添加元素的時候也可能front==tail,當添加
// 元素之後front==tail的時候就認爲是滿了,然後擴容,重新
// 調整front和tail的值,所以擴容之後front就不可能等於tail。
//如果沒有觸發上面添加元素的時候front等於tail我們就認爲他是空的。
public boolean isEmpty() {
return front == tail;
}
}