【C++】競程筆記(資料結構:Binary Tree)
【C++】競程筆記(資料結構:Binary Tree)
程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。
簡介
樹(Tree)在電腦科學領域是一個由「節點」(Node)和「邊」所組成的圖形,如圖:

Image Source:https://www.geeksforgeeks.org/dsa/binary-tree-data-structure/
基本術語
- 節點(Node):
- 如 1、2、3、4 等這些數字被圓圈包起來的就是一個節點。
- 根節點(Root):
- 指的是最上層的節點,沒有父節點,如 1 就是根節點 Root。
- 子節點(Child):
- 位於某節點之下的直接後繼節點,如 1 底下的 2 跟 3 都是 1 的子節點;2 底下的 4 跟 5 都是 2 的子節點。
- 父節點(Parent):
- 反之,父節點為子節點的上層節點。1 是 2 跟 3 的父節點,2 是 4 跟 5 的父節點。
- 葉節點(Leaf):
- 沒有任何子節點的節點。如 8, 5, 9, 10 都沒有子節點,所以稱為葉節點。
- 深度(Depth):
- 根節點的深度定義為 0,子節點的深度等於其父節點深度+1。如 2 的深度是 1,4 的深度是 2。
- 高度(Height):
- 從某節點到其最遠葉節點之最大邊數或節點數。整棵樹的高度即根節點的高度。如 1 的高度為 4(依節點看) 或 3(依邊數看)。
- 子樹(Subtree):
- 以某節點為根所構成的整個樹,如 6、9、10 成為一棵子樹。
二元樹類型
- 滿二元樹(Full Binary Tree):
- 每個非葉節點恰好有兩個子節點。

- Image Source:https://www.geeksforgeeks.org/dsa/types-of-binary-tree/
- 完全二元樹(Complete Binary Tree):
- 除了最底層外,每一層的節點都達到最大數;最底層的節點集中在左側。

- Image Source:https://www.geeksforgeeks.org/dsa/types-of-binary-tree/
- 完美二元樹(Perfect Binary Tree):
- 是一個滿二元樹,且所有葉節點深度相同;節點數 $= 2^{(h+1)} – 1$ , $h$ 為高度。

- Image Source:https://www.geeksforgeeks.org/dsa/types-of-binary-tree/
- 平衡二元樹(Balanced Binary Tree):
- 任一節點的兩個子樹高度差不超過 1。如:AVL 樹(Adelson‑Velsky and Landis Tree)、紅黑樹(Red‑Black Tree)。
n 元樹
n 元樹的意思就是每個節點至多有 n 個子節點。根據定義,一元樹也是一種樹,但是他其實跟陣列沒兩樣。通常這種樹會被稱作一個鏈(Chain)。
From NTUCPC Guide
Binary Search Tree(BST)
二元搜尋樹中的每個節點最多有兩個子節點,左子節點會比右子節點的值還小,當然反過來說,右子節點的值會比左子節點還大,如圖:

Image Source:https://www.geeksforgeeks.org/binary-search-tree-data-structure/
在 C++ 中存一棵 BST:
1 | // from NTUCPC Guide |
node:節點。
parent:父節點。
lson、rson:左子節點、右子節點。
val:節點值。
這裡的第五行冒號(:)為建構子初始化列表(Constructor Initialization List)的行為。
語法為:
1 | 類別名稱(參數) : 成員1(值1), 成員2(值2), ... { |
這種寫法等價於:
1 | // ... |
但用 : 去寫會更有效率、簡潔一點。
一個指標通常需要占 8 個 bytes,所以用指標實作的東西都要花滿多的記憶體,在時間上常數也會比較大。
From NTUCPC Guide
插入一個元素
若要在 BST 中插入一個數字,需遵循以下規則:
- 如果插入值比目前節點小,則往左子樹找插入位置。
- 如果插入值比目前節點大,則往右子樹找插入位置。
- 若目前節點為 NULL,表示找到插入點,則創建新節點。
1 | // From NTUCPC Guide |
在此程式中沒有處理到相等的情況,代表會不允許重複的元素插入。
建立二元搜尋樹
若有 $n$ 個數字 $[a_1, a_2, \cdots , a_n]$ ,設定 $a_1$ 為 Root,再對其他數字依序插入。
Inorder Traversal
二元搜尋樹的特性:左子樹比右子樹小。
中序遍歷(Inorder Traversal)先走左子樹,再走右子樹,即可輸出一個由小到大的序列。
:::info
中序遍歷走訪順序:
左子樹 → 根節點 → 右子樹
:::
1 | // From NTUCPC Guide |
查詢節點
假設要搜尋一值 $x$,目前所在節點值為 $y$。
若 $x < y$ ,往左走;反之, $x > y$ 就往右走。
1 | // NTUCPC Guide |
刪除節點
要刪除節點時有三種情形:
- 葉節點直接刪。
- 其中一個子節點為空,則將其拉上來。如下圖,4 連到 7。

- From NTUCPC Guide
- 找中序後繼(inorder successor)或前驅來取代被刪除節點。(用另一個「替代節點」的值來取代 x 的值,再刪除那個替代節點)
:::info
中序前驅(in-order predecessor):
x->lson中的最大節點(也就是x->lson子樹最右邊的節點)- 該值
< x->val,是小於x的最大值
:::
:::info
中序後繼(in-order successor):
x->rson中的最小節點(也就是x->rson子樹最左邊的節點)- 該值
> x->val,是大於x的最小值
:::
1 | // From NTUCPC Guide |
刪除一個值
1 | // From NTUCPC Guide |
複雜度分析
設 BST 含有 $n$ 個節點, $h$ 為樹的高度。則:
| 操作 | 最佳情況 | 平均情況 | 最差情況 |
|---|---|---|---|
| 搜尋 Search | 平衡樹 | 大致平衡 | 退化為鏈結串列 |
| 插入 Insert | 平衡樹 | 大致平衡 | 退化為鏈結串列 |
| 刪除 Delete | 平衡樹 | 大致平衡 | 退化為鏈結串列 |
| 最小/最大查詢 | $O(log n)$ | $O(log n)$ | $O(n)$ |
| 中序走訪 Traversal | $O(n)$ | $O(n)$ | $O(n)$ |
當插入的資料為已排序的序列時,BST 不再分叉,整棵樹會變成單一路徑,因此樹會不平衡,退化成一個鏈結串列,高度會變成 $h = n$ ,效率變差,直接變成 $O(n)$ 。
然後二元搜尋樹是要去平衡的,有些測資如習題練習第一題,還蠻大的,沒去好好平衡的話,時間複雜度甚至會到 $O(n^2)$ 。
習題練習
二元搜尋樹 (TRVBST)
Source:https://tioj.ck.tp.edu.tw/problems/1609
問中序遍歷結果。
TLE 解(手刻實作二元搜尋樹,且未平衡):
1 |
|
AC 解(內建函式 sort() 大法):
1 |
|
1d-kd-tree
Source:https://tioj.sprout.tw/problems/46
查詢操作是比較難的(查詢一個點,問距離哪一個點的距離最短),但可以用二元搜尋樹找 predecessor(不大於 x 的最大點) 跟 successor(大於 x 的最小點),並比較這些點與 x 的距離。
1 |
|




