C1: 天王星貨運系統:堆疊與佇列
任務背景
- 堆疊系統(Stack):後進先出(LIFO),像一疊盤子
- 佇列系統(Queue):先進先出(FIFO),像排隊結帳
掌握這兩種數據結構是成為高級程式設計師的關鍵!它們是許多進階演算法的基礎。
知識點說明
堆疊(Stack)- 後進先出
想像一疊盤子:最後放上去的盤子,最先被拿走。
[3] ← 最後進入,最先離開
[2]
[1] ← 最先進入,最後離開
-----
基本操作:
push(x):將元素 x 放到頂部pop():移除並返回頂部元素peek():查看頂部元素(不移除)is_empty():檢查是否為空
佇列(Queue)- 先進先出
想像排隊:最先排隊的人,最先被服務。
出口 ← [1] [2] [3] ← 入口
最先進入 最後進入
基本操作:
enqueue(x):將元素 x 加到隊尾dequeue():移除並返回隊首元素front():查看隊首元素(不移除)is_empty():檢查是否為空
Python 實現
堆疊:使用 liststack = []
stack.append(1) # push
stack.append(2)
top = stack.pop() # pop,返回 2
佇列:使用 collections.deque
from collections import deque
queue = deque()
queue.append(1) # enqueue
queue.append(2)
first = queue.popleft() # dequeue,返回 1
⚠️ 重要:不要用 list.pop(0) 實現佇列!時間複雜度是 O(N),非常慢!
範例程式碼
範例 1:貨櫃堆疊管理
import sys
# 任務:管理貨櫃堆疊
stack = []
n = int(sys.stdin.readline()) # 操作次數
for _ in range(n):
command = sys.stdin.readline().strip().split()
if command[0] == "PUSH":
container_id = command[1]
stack.append(container_id)
print(f"✅ 貨櫃 {container_id} 已堆疊")
elif command[0] == "POP":
if stack:
removed = stack.pop()
print(f"📦 移除貨櫃 {removed}")
else:
print("❌ 錯誤:堆疊為空")
elif command[0] == "PEEK":
if stack:
print(f"👀 頂部貨櫃:{stack[-1]}")
else:
print("❌ 堆疊為空")
elif command[0] == "SIZE":
print(f"📊 堆疊大小:{len(stack)}")
print(f"\n最終堆疊:{stack}")
範例 2:太空船維修佇列
import sys
from collections import deque
# 任務:管理太空船維修佇列
queue = deque()
n = int(sys.stdin.readline())
for _ in range(n):
command = sys.stdin.readline().strip().split()
if command[0] == "ARRIVE":
ship_id = command[1]
queue.append(ship_id)
print(f"🚀 太空船 {ship_id} 加入維修佇列")
print(f" 目前排隊:{len(queue)} 艘")
elif command[0] == "SERVICE":
if queue:
serviced = queue.popleft()
print(f"🔧 開始維修太空船 {serviced}")
else:
print("✅ 沒有太空船等待維修")
elif command[0] == "NEXT":
if queue:
print(f"👉 下一艘:{queue[0]}")
else:
print("❌ 佇列為空")
print(f"\n等待維修:{list(queue)}")
範例 3:括號配對檢查(經典堆疊應用)
import sys
# 任務:檢查括號是否正確配對
expression = sys.stdin.readline().strip()
stack = []
pairs = {'(': ')', '[': ']', '{': '}'}
valid = True
for i, char in enumerate(expression):
if char in pairs: # 左括號
stack.append(char)
elif char in pairs.values(): # 右括號
if not stack:
print(f"❌ 錯誤:位置 {i} 的 '{char}' 沒有對應的左括號")
valid = False
break
left = stack.pop()
if pairs[left] != char:
print(f"❌ 錯誤:位置 {i} 的 '{char}' 與 '{left}' 不匹配")
valid = False
break
if valid and stack:
print(f"❌ 錯誤:還有 {len(stack)} 個左括號未配對")
valid = False
if valid:
print("✅ 括號配對正確!")
範例 4:逆波蘭表示法計算器
import sys
# 任務:計算逆波蘭表示法(後綴表示法)
# 例如:3 4 + 5 表示 (3 + 4) 5 = 35
tokens = sys.stdin.readline().strip().split()
stack = []
for token in tokens:
if token in ['+', '-', '*', '/']:
# 彈出兩個操作數
b = stack.pop()
a = stack.pop()
# 執行運算
if token == '+':
result = a + b
elif token == '-':
result = a - b
elif token == '*':
result = a * b
elif token == '/':
result = a // b # 整數除法
stack.append(result)
print(f"計算:{a} {token} {b} = {result}")
else:
# 數字,壓入堆疊
stack.append(int(token))
print(f"壓入:{token}")
print(f"\n最終結果:{stack[0]}")
範例 5:滑動窗口最大值(雙端佇列進階應用)
import sys
from collections import deque
# 任務:找出每個大小為 k 的滑動窗口中的最大值
n = int(sys.stdin.readline())
k = int(sys.stdin.readline())
arr = list(map(int, sys.stdin.readline().strip().split()))
# 使用雙端佇列儲存索引
dq = deque()
result = []
for i in range(n):
# 移除超出窗口範圍的元素
while dq and dq[0] < i - k + 1:
dq.popleft()
# 移除所有比當前元素小的元素(它們不可能是最大值)
while dq and arr[dq[-1]] < arr[i]:
dq.pop()
# 加入當前元素的索引
dq.append(i)
# 當窗口形成後,記錄最大值
if i >= k - 1:
result.append(arr[dq[0]])
print(f"陣列:{arr}")
print(f"窗口大小:{k}")
print(f"每個窗口的最大值:{result}")
程式碼解說
關鍵技術點
- 為什麼佇列要用 deque?
list.pop(0) 會導致所有元素向前移動,非常慢!
- 括號配對的邏輯
# 遇到左括號:壓入堆疊
if char in '([{':
stack.append(char)
# 遇到右括號:檢查是否匹配
if char in ')]}':
if stack and matches(stack[-1], char):
stack.pop()
else:
return False # 不匹配
- 逆波蘭表示法的優勢
- 不需要括號
- 計算機容易處理
- 使用堆疊即可計算
中綴:(3 + 4) * 5
後綴:3 4 + 5 *
計算過程:
3 → [3]
4 → [3, 4]
+ → [7] (彈出 3,4,計算 3+4,壓入 7)
5 → [7, 5]
→ [35] (彈出 7,5,計算 75,壓入 35)
- 堆疊與遞迴的關係
- 遞迴本質上使用系統的「呼叫堆疊」
- 可以用顯式堆疊模擬遞迴
- 避免 Python 的遞迴深度限制
Quiz: 太空電梯調度系統
題目:電梯請求處理
天王星軌道站有一個智能電梯系統。電梯接收兩種請求:UP X:有人在第 X 層要上樓DOWN X:有人在第 X 層要下樓
- 先處理所有上樓請求(從低樓層到高樓層)
- 再處理所有下樓請求(從高樓層到低樓層)
- 第一行:整數 N(請求數量,1 ≤ N ≤ 100)
- 接下來 N 行:每行一個請求,格式為 "UP X" 或 "DOWN X"
- 使用兩個列表分別儲存上樓和下樓請求
- 對列表進行排序
- 上樓請求升序,下樓請求降序
請模擬電梯的運行過程。
輸入格式:6
UP 3
DOWN 5
UP 7
DOWN 2
UP 1
DOWN 8
輸出範例:
處理上樓請求:
樓層 1
樓層 3
樓層 7
處理下樓請求:
樓層 8
樓層 5
樓層 2
總共服務:6 個請求
💡 提示
---
Quiz 解答
import sys
# 讀取請求數量
n = int(sys.stdin.readline())
# 分別儲存上樓和下樓請求
up_requests = []
down_requests = []
# 讀取所有請求
for _ in range(n):
request = sys.stdin.readline().strip().split()
direction = request[0]
floor = int(request[1])
if direction == "UP":
up_requests.append(floor)
else: # DOWN
down_requests.append(floor)
# 排序
up_requests.sort() # 升序:從低到高
down_requests.sort(reverse=True) # 降序:從高到低
# 處理上樓請求
print("處理上樓請求:")
for floor in up_requests:
print(f"樓層 {floor}")
if up_requests:
print()
# 處理下樓請求
print("處理下樓請求:")
for floor in down_requests:
print(f"樓層 {floor}")
print(f"\n總共服務:{len(up_requests) + len(down_requests)} 個請求")
解答說明
- 分類儲存
if direction == "UP":
up_requests.append(floor)
else:
down_requests.append(floor)
將請求分成兩類,方便後續處理
- 排序策略
up_requests.sort() # [1, 3, 7]
down_requests.sort(reverse=True) # [8, 5, 2]
- 上樓:從低到高,符合電梯上升邏輯
- 下樓:從高到低,符合電梯下降邏輯
- 為什麼不用堆疊或佇列?
- 這題需要「排序」後處理
- 堆疊和佇列適合「順序」處理
- 使用列表 + 排序更合適
進階版本:使用優先佇列
import heapq
# 最小堆(用於上樓請求)
up_heap = []
# 最大堆(用於下樓請求,使用負數模擬)
down_heap = []
for _ in range(n):
request = sys.stdin.readline().strip().split()
direction = request[0]
floor = int(request[1])
if direction == "UP":
heapq.heappush(up_heap, floor)
else:
heapq.heappush(down_heap, -floor) # 負數實現最大堆
print("處理上樓請求:")
while up_heap:
floor = heapq.heappop(up_heap)
print(f"樓層 {floor}")
print("\n處理下樓請求:")
while down_heap:
floor = -heapq.heappop(down_heap) # 轉回正數
print(f"樓層 {floor}")
---
訓練完成!
恭喜你完成 堆疊與佇列系統 訓練!