Semorphe

佇列與堆疊

兩個只差在「從哪一端拿」的容器。 · ⏱ 約 30 分鐘

你會學到三件事

  1. queue 先進先出、stack 後進先出
  2. 它們故意不讓你看中間
  3. 遞迴其實就是一個你看不見的 stack

開始之前

vector 什麼都能做——那為什麼還要這兩個?

因為限制本身是有價值的。看完這一課再回來想這句話。

🟢 先把編輯器打開,跟著下面一起做:跟著做

一、兩個,一眼看完

同樣放進 1、2,再拿出來——差別只在拿出來的是哪一端:

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1queue<int> q;2q.push(1);3q.push(2);4cout << q.front();  // 15q.pop();6cout << q.front();  // 2
1變數q2變數q變數q的前端元素變數q變數q的前端元素0回傳印出從的前端移除印出加到的尾端加到的尾端建立int(整數)佇列q定義函式 回傳型別int(整數)main使用命名空間std引入函式庫queue(佇列)引入函式庫iostream(輸入輸出)123456

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1stack<int> st;2st.push(1);3st.push(2);4cout << st.top();  // 25st.pop();6cout << st.top();  // 1
1變數st2變數st變數st的頂端元素變數st變數st的頂端元素0回傳印出從的頂端移除印出推到的頂端推到的頂端建立int(整數)堆疊st定義函式 回傳型別int(整數)main使用命名空間std引入函式庫stack(堆疊)引入函式庫iostream(輸入輸出)123456
從哪裡進 從哪裡出 叫什麼
queue 後面 前面 先進先出(FIFO)——排隊
stack 上面 上面 後進先出(LIFO)——疊盤子

⚠️ 名字不一樣:queue 用 front(),stack 用 top()。

積木盤上它們寫著「… 的前端元素」與「… 的頂端元素」 ——而放進去、拿出來那兩顆是「推 … 到 … 的頂端/尾端」 與「從 … 的頂端移除」(stack)/「從 … 的前端移除」(queue)。

⚠️ pop() 不回傳值,它只是拿掉。要值得先 front() / top()。

⚠️ 空的時候呼叫 front() / top() 是未定義行為,不會報錯。 用之前先 if (!q.empty())。

二、🔴 它們故意不讓你看中間

q[3] 是不合法的。這不是缺功能,是設計。

一個限制得剛好的工具,讓錯誤的用法寫不出來。

用 vector 模擬佇列的話,你可以不小心從中間拿東西, 而那多半是一個 bug。queue 讓那個 bug 連編譯都過不了。

所以判準是:你的演算法只從兩端動嗎? 是 → 用專門的那個。

三、它們各自在哪裡出現

用在什麼
queue BFS(第 11 課)、模擬排隊、生產者消費者
stack DFS、括號配對、運算式求值、回溯

四、🔴 遞迴就是一個看不見的 stack

C++ 入門第 16 課的階乘:

f(5) → f(4) → f(3) → f(2) → f(1)
                                 ↓ 回來
                        ← ← ← ← ←

那個「回來」的順序是後進先出:最後被叫的最先回來。

電腦就是用一個 stack 存這些「還沒回來的呼叫」,那叫呼叫堆疊。

⚠️ 所以「遞迴太深會 stack overflow」是字面意思:那個 stack 滿了。

而反過來,任何遞迴都可以改寫成用一個 stack 的迴圈。 遞迴太深時就是這樣改的。

完成的樣子

這一課的參考做法——程式碼每一行的號碼,就印在對應的那塊積木上。

1#include <iostream>2#include <queue>3#include <stack>4using namespace std;5int main() {6    queue<int> q;7    q.push(1);8    q.push(2);9    cout << q.front() << endl;10    q.pop();11    cout << q.front() << endl;12    stack<int> st;13    st.push(1);14    st.push(2);15    cout << st.top() << endl;16    return 0;17}
1變數q2變數q變數q的前端元素換行變數q變數q的前端元素換行1變數st2變數st變數st的頂端元素換行0回傳印出推到的頂端推到的頂端建立int(整數)堆疊st印出從的前端移除印出加到的尾端加到的尾端建立int(整數)佇列q定義函式 回傳型別int(整數)main使用命名空間std引入函式庫stack(堆疊)引入函式庫queue(佇列)引入函式庫iostream(輸入輸出)12345678910111213141516

下一課

queue 和 stack 是標準函式庫給你的,而下一課要自己做一個。 那是 Linked List,也是後面「圖」和「樹」的地基。

換你了

用 stack 判斷一串括號有沒有配對好:"(())" ✅、"(()" ❌。

提示:遇到 ( 就 push,遇到 ) 就 pop。 ⚠️ 兩個地方要小心:pop 之前 stack 空了(右括號太多), 以及最後 stack 沒空(左括號太多)。

問「空了沒」那顆積木寫著「… 是否為空」。 🔴 pop 之前一定要先問它——對一個空的 stack 呼叫 pop() 不是錯誤,是未定義行為:它可能看起來好好的,直到換一台機器。

到編輯器做這一題:練習:用 stack 判斷括號有沒有配對好

做一個

7 個人圍成一圈(編號 1 到 7),從 1 開始報數,每數到第 3 個人就出局, 一直到剩一個人。印出他的編號。

🔴 用 queue:沒出局的就從前面拿出來、排到後面去。 這一題不用陣列也不用刪除,那正是佇列適合的形狀。

到編輯器做這一題:做一個:數到三就出局

這一課你做了什麼

如果卡住了

你看到 多半是因為
q.top() 編譯錯誤 queue 用 front()
pop() 拿不到值 它不回傳。要先 front()/top()
程式當掉 對空的容器呼叫了 front()/top()
順序反了 弄混了 FIFO 和 LIFO
在編輯器打開這一課 →