🌳 樹狀結構(Tree)
樹是最常用的非線性資料結構之一。從檔案系統到 HTML DOM、從組織圖到決策樹,樹無所不在。 本頁從定義出發,一路帶你理解術語、二元樹類型、四種遍歷方式,並介紹比賽中最常用的三種建樹模式。
什麼是樹?
樹是一種很特別的圖。樹的定義是:任兩點之間都相通,而且沒有「環」的圖。 「環」是指繞圈子的循環路線。此外,樹的邊數永遠等於節點數 − 1。
樹狀結構術語
下面這張樹會作為術語解釋的範例。節點 1 是這棵樹的根(已強調)。
- 節點 (Node)
- 樹上的每一個點都是節點。圖中共有 7 個節點。
- 邊 / 枝 (Edge / Branch)
- 連接兩個節點的線。樹的邊數 = 節點數 − 1,圖中有 6 條邊。
- 根 (Root)
- 整棵樹的起點。本圖以節點 1 為根(任一節點都可以作為根)。
- 葉 / 外部節點 (Leaf)
- 沒有子節點的節點。圖中 4、5、6、7 都是葉。
- 父親 / 小孩 (Parent / Child)
- 相鄰兩點中靠近根者為父親,靠近葉者為小孩。節點 2 是 4、5 的父親。
- 祖先 / 後代 (Ancestor / Descendant)
- 父親的父親…皆為祖先;小孩的小孩…皆為後代。1 是 4 的祖先,4 是 1 的後代。
- 手足 (Sibling)
- 共享同一個父親的節點。4 與 5 是手足;2 與 3 也是。
- 分支度 (Degree)
- 一個節點的小孩數量。節點 1 的 degree = 2。
- 層級 (Level)
- 節點離根的距離。根通常是 level 0,其子節點是 level 1,以此類推。
- 高度 / 深度 (Height / Depth)
- 整棵樹上最大的層級。本圖的高度 = 2。
二元樹 (Binary Tree)
二元樹中,每個節點最多只有兩個子節點,分別稱為左子樹(left subtree)與右子樹(right subtree)。 依節點分佈可分為三種特殊形態:
歪斜二元樹
完整二元樹
滿二元樹
樹的遍歷 (Tree Traversal)
遍歷是指「走訪樹上每一個節點」的過程。根據「根節點何時被處理」,可以分為四種方式:
- 前序 (Preorder):Root → Left → Right
- 中序 (Inorder):Left → Root → Right
- 後序 (Postorder):Left → Right → Root
- 層序 (Level Order):一層一層走完(BFS)
下方動畫可以切換四種遍歷方式,點選播放看節點如何被依序走訪:
Root → Left → Right
建樹模式 (Building Trees from Input)
💡 為什麼需要學建樹?
LeetCode 等平台會預先把 TreeNode 物件建好給你,學習者只需要專注在「寫函式」。 但在 APCS、ZeroJudge 等競賽中,你必須自己從輸入字串把樹建起來 — 而「建樹」這一步往往才是卡人的關鍵。以下三種模式分別對應不同的輸入型態與操作需求。
模式一:Dict 映射
場景:輸入已經列出每個節點的編號與左右子節點。
資料結構:defaultdict, key 是節點編號,value 是 (left, right) tuple。
3 1, 2, 3 2, -, - 3, -, -
from collections import defaultdict
n = int(input())
tree = defaultdict(lambda: (None, None))
for i in range(1, n + 1):
parts = [p.strip() for p in input().split(',')]
node = int(parts[0])
if i == 1:
root = node # 樹的根節點
left = None if parts[1] == '-' else int(parts[1])
right = None if parts[2] == '-' else int(parts[2])
tree[node] = (left, right)
def height(node):
if node is None:
return -1 # 空節點回傳 -1,葉節點高度為 0
left_h = height(tree[node][0])
right_h = height(tree[node][1])
return max(left_h, right_h) + 1
print(height(root))模式二:Class + BST Insert
場景:輸入是一串數值流,要依照 BST 規則(左子 < 父親 < 右子)建樹。
資料結構:自訂 class Node,每個節點持有 value / left / right。
1 5 5, 3, 8, 1, 4
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
n = int(input())
for _ in range(n):
a = int(input())
nodes = list(map(int, input().split(',')))
root = Node(nodes[0])
for v in nodes[1:]:
current = root
while True:
if v < current.value:
if current.left is None:
current.left = Node(v)
break
current = current.left
else:
if current.right is None:
current.right = Node(v)
break
current = current.right
def preorder(node):
if node:
res.append(node.value)
preorder(node.left)
preorder(node.right)
res = []
preorder(root)
print(' '.join(map(str, res)))模式三:Parent 陣列 + Children 清單
場景:輸入是邊列表 (child, parent),且題目會問父親、手足、祖先等結構性問題。
資料結構:parents[i] 記每個節點的父親,children[p] 記每個節點的小孩清單。
5 2, 1 3, 1 4, 2 5, 2 2 4 3
from collections import defaultdict
n = int(input())
parents = [-1] * (n + 1)
children = defaultdict(list)
for i in range(n - 1):
c, p = map(int, input().split(','))
parents[c] = p
children[p].append(c)
m = int(input())
for _ in range(m):
node = int(input())
p = parents[node]
if p == -1:
print(f'siblings {node} = {{}}')
else:
sibs = sorted([c for c in children[p] if c != node])
print(f'siblings {node} = {{{",".join(map(str, sibs))}}}')如何選擇建樹模式?
| 輸入格式 | 常見操作 | 推薦模式 |
|---|---|---|
每行給 node, left, right | 計算高度、遍歷 | 模式一:Dict 映射 |
| 一串數值需要排序建樹 | BST 查找、遍歷 | 模式二:Class + BST |
邊列表 (child, parent) | 查父親 / 手足 / 祖先 | 模式三:Parent + Children |
下一步
觀念看完了,現在就來試試實戰題目吧!從經典的「比較兩棵樹是否完全相同」開始:
→ LeetCode 100 Same Tree(實戰練習)