📊 資料結構總複習
快速回顧所有重要的資料結構概念
1️⃣ 為什麼要學習資料結構?
資料結構是程式的基石,選擇正確的資料結構可以讓程式更高效、更簡潔。
- 效率:不同的資料結構有不同的时间复杂度
- 組織:幫助我們有效地組織和存取資料
- 解決問題:許多經典問題都有對應的最佳資料結構解決方案
2️⃣ 陣列(Array)
基本概念
陣列是最基本的資料結構,元素在記憶體中連續排列。
| 操作 | 時間複雜度 |
|---|---|
| 隨機存取 | O(1) |
| 搜尋 | O(n) |
| 插入(末端) | O(1) |
| 刪除(末端) | O(1) |
| 插入/刪除(中間) | O(n) |
優點
- 隨機存取速度快 O(1)
- 記憶體連續,cache 命中率高的
缺點
- 大小固定,擴展困難
- 插入/刪除需要移動元素
Python 範例
# 建立陣列
arr = [1, 2, 3, 4, 5]
# 隨機存取 O(1)
print(arr[2]) # 輸出: 3
# 遍歷
for num in arr:
print(num)
# 列表推導式
squares = [x**2 for x in range(10)]
3️⃣ 連結串列(Linked List)
基本概念
每個節點包含資料和指向下一個節點的指標。
| 操作 | 時間複雜度 |
|---|---|
| 隨機存取 | O(n) |
| 頭部插入 | O(1) |
| 頭部刪除 | O(1) |
| 尾端插入 | O(n)* |
* 如果有尾指針則為 O(1)
單向串列實作
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class LinkedList:
def __init__(self):
self.head = None
def append(self, val):
new_node = ListNode(val)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def prepend(self, val):
new_node = ListNode(val, self.head)
self.head = new_node
def delete(self, val):
if not self.head:
return
if self.head.val == val:
self.head = self.head.next
return
current = self.head
while current.next:
if current.next.val == val:
current.next = current.next.next
return
current = current.next
💡 提示:串列適合頻繁的頭部操作,不需要移動元素。
4️⃣ 堆疊(Stack)
基本概念
後進先出(LIFO, Last In First Out)的資料結構。
| 操作 | 時間複雜度 |
|---|---|
| push(推入) | O(1) |
| pop(彈出) | O(1) |
| peek(查看頂端) | O(1) |
| isEmpty | O(1) |
應用場景
- 函數呼叫堆疊
- 括號匹配
- 深度優先搜尋(DFS)
- 運算式求值
- 網頁瀏覽器的「上一頁」
Python 實作
# 使用列表實現堆疊
stack = []
# push
stack.append(10)
stack.append(20)
stack.append(30)
# pop
top = stack.pop() # top = 30
print(top)
# 查看頂端
print(stack[-1]) # 20
# 判斷空
if not stack:
print("堆疊為空")
✅ 經典題目:Valid Parentheses、Daily Temperatures、Evaluate Reverse Polish Notation
5️⃣ 佇列(Queue)
基本概念
先進先出(FIFO, First In First Out)的資料結構。
| 操作 | 時間複雜度 |
|---|---|
| enqueue(入隊) | O(1) |
| dequeue(出隊) | O(1) |
| front | O(1) |
| isEmpty | O(1) |
Python 實作(使用 collections.deque)
from collections import deque
queue = deque()
# enqueue
queue.append(10)
queue.append(20)
queue.append(30)
# dequeue
front = queue.popleft() # front = 10
# 查看前端
print(queue[0]) # 20
# 判斷空
if not queue:
print("佇列為空")
雙端佇列(Deque)
兩端都可以進行插入和刪除:
# 雙端操作 queue.appendleft(5) # 在前端插入 queue.append(40) # 在尾端插入 queue.popleft() # 從前端刪除 queue.pop() # 從尾端刪除
✅ 應用:廣度優先搜尋(BFS)、任務排程、快取(LRU Cache)
6️⃣ 雜湊表(Hash Table)
基本概念
透過雜湊函數將鍵映射到值的資料結構。
| 操作 | 平均時間 | 最差時間 |
|---|---|---|
| 搜尋 | O(1) | O(n) |
| 插入 | O(1) | O(n) |
| 刪除 | O(1) | O(n) |
Python 實作
# 使用字典
hash_table = {}
# 插入
hash_table["name"] = "Anzu"
hash_table["age"] = 25
hash_table["role"] = "AI Assistant"
# 搜尋
print(hash_table.get("name")) # "Anzu"
print(hash_table.get("unknown", "default")) # "default"
# 遍歷
for key, value in hash_table.items():
print(f"{key}: {value}")
碰撞處理
- 鏈接法(Chaining):每個桶用串列存儲多個元素
- 開放定址法(Open Addressing):找下一個空桶
⚠️ 注意:避免所有鍵產生相同的雜湊值(Hash Collision)
7️⃣ 樹(Tree)
基本概念
由節點組成的層級資料結構,有一個根節點。
常用術語
- 根節點(Root):樹的最頂端節點
- 葉節點(Leaf):沒有子節點的節點
- 深度(Depth):根到該節點的路徑長度
- 高度(Height):該節點到最深葉節點的路徑長度
- 子樹(Subtree):節點及其所有後代
二元樹遍歷
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 前序遍歷(根 -> 左 -> 右)
def preorder(root):
if not root:
return
print(root.val)
preorder(root.left)
preorder(root.right)
# 中序遍歷(左 -> 根 -> 右)
def inorder(root):
if not root:
return
inorder(root.left)
print(root.val)
inorder(root.right)
# 後序遍歷(左 -> 右 -> 根)
def postorder(root):
if not root:
return
postorder(root.left)
postorder(root.right)
print(root.val)
8️⃣ 圖(Graph)
基本概念
由節點(頂點)和邊組成的資料結構。
圖的類型
- 有向圖 vs 無向圖:邊是否有方向
- 有權圖 vs 無權圖:邊是否有權重
- 連通圖 vs 非連通圖:是否所有節點都相通
圖的表示
# 鄰接表(Adjacency List)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
# 鄰接矩陣(Adjacency Matrix)
# A B C D E F
# A [0, 1, 1, 0, 0, 0]
# B [1, 0, 0, 1, 1, 0]
# ...
圖的遍歷
# BFS(廣度優先搜尋)
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
while queue:
node = queue.popleft()
print(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# DFS(深度優先搜尋)
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
visited.add(node)
print(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(graph, neighbor, visited)
✅ 應用:社群網路、推薦系統、路徑規劃、網頁爬蟲
📊 資料結構比較總表
| 資料結構 | 搜尋 | 插入 | 刪除 | 空間 |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| Linked List | O(n) | O(1)* | O(1)* | O(n) |
| Stack | O(n) | O(1) | O(1) | O(n) |
| Queue | O(n) | O(1) | O(1) | O(n) |
| Hash Table | O(1)** | O(1)** | O(1)** | O(n) |
| Binary Tree | O(log n)*** | O(log n)*** | O(log n)*** | O(n) |
| Graph | O(V+E) | O(1) | O(1) | O(V+E) |
* 頭部操作 | ** 平均情況 | *** 平衡樹
📝 自我測驗
Q1: 什麼情況下使用堆疊比使用佇列更合適?
Ans: 當需要「後進先出」的順序時,如函數呼叫堆疊、括號匹配、DFS。
Q2: 陣列和連結串列的主要差異是什麼?
Ans: 陣列記憶體連續,可隨機存取 O(1);串列記憶體不連續,插入刪除 O(1)。
Q3: 為什麼雜湊表需要處理碰撞?
Ans: 不同鍵可能雜湊到同一位置,碰撞會導致資料丟失或錯誤。
Q4: BFS 和 DFS 分別適合什麼場景?
Ans: BFS 適合找最短路徑、分層遍歷;DFS 適合遍歷所有路徑、檢查是否存在。
✅ 完成學習
完成本教材後,點擊下方按鈕記錄你的學習進度!