語法書 / AA 競程語法書 下冊 / 附錄 / E. 佇列與堆疊

E. 佇列與堆疊

考卷用 C(A 節),沒有 queuestack 這種現成容器,取而代之的是一個陣列配一兩個索引變數。看懂那幾個索引指著哪一格,題目就解掉一半。

先分清楚兩者的差別:

  • 佇列(queue):先進先出(FIFO),像排隊買票,先排的先走。
  • 堆疊(stack):後進先出(LIFO),像一疊盤子,最後放上去的最先被拿走。

佇列:head 與 tail

head 指著下一個要出去的位置tail 指著下一個要放進去的空位

q[tail++] = x;      // 放進來
x = q[head++];      // 拿出去

tail++後置遞增先拿舊的值當索引,再把索引加一——一行同時做完「放在現在這格」與「把位置往後推」。兩個索引都只往後跑,head == tail 就代表空了。

#include <iostream>
using namespace std;

int q[100];        // 佇列本體
int head = 0;      // 下一個要出去的位置
int tail = 0;      // 下一個要放進去的空位

int main() {
    q[tail++] = 3;                    // 3 排進來
    q[tail++] = 1;                    // 1 排進來
    cout << q[head++] << '\n';        // 最前面的先出去
    q[tail++] = 4;                    // 4 排進來
    while (head < tail) {             // 還有人就繼續叫號
        cout << q[head++] << '\n';
    }
    return 0;
}

執行結果:

3
1
4
執行的敘述 q[0] q[1] q[2] head tail 輸出
(開始) 0 0
q[tail++] = 3; 3 0 1
q[tail++] = 1; 3 1 0 2
cout << q[head++]; 3 1 1 2 3
q[tail++] = 4; 3 1 4 1 3
迴圈第一圈 3 1 4 2 3 1
迴圈第二圈 3 1 4 3 3 4

出去的元素其實還留在陣列裡,只是 head 走過去了——「已經出去」的意思就是 head 超過了它

堆疊:只要一個 top

top 指著下一個要放進去的空位,同時也就是目前的元素個數:

st[top++] = x;      // 疊上去
x = st[--top];      // 拿下來

拿的時候是前置遞減:先退一格,再把那一格的值取走。進與出動到的是同一個索引,所以拿到的一定是最後放進去的那個。top == 0 就是空的。

#include <iostream>
using namespace std;

int st[100];       // 堆疊本體
int top = 0;       // 下一個要放進去的空位,也就是目前的元素個數

int main() {
    st[top++] = 3;                    // 3 疊上去
    st[top++] = 1;                    // 1 疊上去
    cout << st[--top] << '\n';        // 最上面的先拿走
    st[top++] = 4;                    // 4 疊上去
    while (top > 0) {                 // 還有東西就一直拿
        cout << st[--top] << '\n';
    }
    return 0;
}

執行結果:

1
4
3
執行的敘述 st[0] st[1] top 輸出
(開始) 0
st[top++] = 3; 3 1
st[top++] = 1; 3 1 2
cout << st[--top]; 3 1 1 1
st[top++] = 4; 3 4 2
迴圈第一圈 3 4 1 4
迴圈第二圈 3 4 0 3

注意倒數第三列:4 直接蓋掉了 st[1] 原本的 1——那一格已經被拿走,蓋掉無妨。

兩個堆疊做出一個佇列

考古題出現過這個組合,觀念只有一句:每倒一次,順序就翻面一次

資料先一個一個推進堆疊 A。要取用時,等 B 空了,再把 A 裡的東西全部拿出來、依序推進堆疊 B:從 A 拿出來的順序已經跟進去時相反(翻了一次面),推進 B 之後再拿出來,又相反一次(第二次翻面)。翻兩次就翻回原樣,所以從 B 拿到的是最早進 A 的那一個,正是佇列的行為。

「等 B 空了」不能省——B 裡還有東西時就把 A 倒過去,後到的會疊在舊的上面先被拿走,順序就亂了。

追蹤這種題目時不必想「為什麼要這樣繞」,只要盯著兩個 top,記得每次整批搬家都會把順序反過來。