第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/pg31221-doc/ch07.pdf ·...

122
7 7 本章學習目標 . 理解樹、二元樹、林的定義。 理解如何動態建立二元樹 理解二元樹的走訪演算法。 理解引線二元樹與相關演算法。

Upload: others

Post on 14-Jul-2020

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

第第 77 章章

樹樹狀狀結結構構

本章學習目標 .

理解樹、二元樹、林的定義。

理解如何動態建立二元樹

理解二元樹的走訪演算法。

理解引線二元樹與相關演算法。

理解累堆、二元搜尋樹與相關演算法。

Page 2: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-2

7.1 樹狀結構簡介

樹(Tree)是一種非線性的資料結構,在日常生活中,我們常常可以看到樹狀結

構的應用,例如男性族譜關係是一種樹狀結構(如圖 7-1)。

圖 7-1 家族表呈現樹狀結構

Page 3: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-3

7.1.1 樹的定義

以圖示來看,可以很容易看出一個結構是否為樹狀結構,但這並不夠嚴謹。

樹其實是(有向)圖的一種特例(下一章介紹),但在此我們先不管圖的部分,

而是將『樹』採用遞迴方式定義如下:

【定義 7-1】樹的定義

樹是由一個以上的節點所構成的有限集合,它必須滿足下列兩個條

件:

(1) 具有唯一的特殊節點,稱為樹根或根節點( root)。

(2) 剩下的節點分為 n 個互斥集合 T1, T2, T3, ..., Tn (n0),每一個集合

T i 也都是一棵獨立的樹,並稱為樹根的子樹( subtree)。

採用遞迴定義,使得樹狀結構的定義得以簡潔。

以圖 7-1 為例,『諸葛珪』是根節點,並有三棵子樹,其三棵子樹的樹根分別

為『諸葛瑾』、『諸葛亮』、『諸葛均』。

請特別注意定義的第(2)點,子樹 T1, T2, ..., Tn必須是互斥且獨立集合,也就是這些

子樹不可以連接在一起。

Page 4: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-4

此外,在遞迴定義中,我們可以發現,除了根節點之外,樹的任何一個節點必定

為某一個子樹的根節點。

以『諸葛懷』節點為例,它是『諸葛懷』子樹的根節點,且『諸葛懷』並未

擁有子樹,而這仍符合定義的第(2)項規定,只是此時 n 恰為 0。

7.1.2 樹狀結構的專有名詞

在了解樹的定義之後,我們還必須介紹樹的相關專有名詞,我們透過圖 7-2 為例,

說明其他必須了解的專有名詞(習慣上,我們會將樹根畫在最上面)。

Page 5: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-5

圖 7-2 樹的專有名詞

1. 節點(node):代表某項資料及其指向其它資料項的分支( branch),

這個分支是個有方向性的邊(directed edge)(邊的定義詳見下一章),

如圖 7-2 的根節點的資料項目為 A,分支有 3 個。

2. 父節點(parent node)與子節點(children node):若節點 X 的分支

連接的節點為 Y,則節點 Y 為節點 X 的子節點;並且,節點 X 為節點

Y 的父節點。

例如:H、 I、 J 為 D 的子節點;D 則為 H、 I、 J 等三個節點的父節點。

3. 根節點( root node):沒有父節點的節點,一棵樹必須也只能有唯一

的根節點。

例如:A 為根節點。

4. 兄弟節點(sibling node):兩個節點的父節點相同時,兩節點互為兄

弟節點。

例如:H、 I、 J 互為兄弟節點。

Page 6: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-6

5. 祖先節點(ancestor node)與子孫節點(descendant node):節點 X

具有一條路徑通往另一節點 Y,則節點 Y 為節點 X 的子孫節點;並

且,節點 X 為節點 Y 的祖先節點。

例如:A、B、F 為 K 的祖先節點,E、F、K、L 為 B 的子孫節點。

6. 非終結節點(non-terminal node):又稱內部節點。有子節點的節點

稱為非終結節點。

例如:A、B、F、C、D、 I 為非終結節點。

7. 終結節點( terminal node):又稱外部節點,一般稱為樹葉節點( leaf

node),凡是沒有子節點的節點稱為樹葉節點。

例如:E、K、L、G、H、M、 J 為終結節點。

8. 分支度(degree):一個節點的子節點數目(分支數目)稱為該節點

的分支度。

例如:A 與 D 的分支度皆為 3,F 的分支度為 2。

9. 樹的分支度:一棵樹的分支度為任一節點所擁有的最大分支度。

例如:圖 7-2 的樹分支度為 3。

Page 7: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-7

10. 層次(Level):又稱為階度,代表節點在樹中的世代關係,其中根節

點的層次為 1,然後往下遞增。

例如:K、L、M 的層次為 4。

11. 樹的高度(height):又稱為樹的深度(depth),代表一棵樹中所有節

點的最大層次。

例如:圖 7-2 的樹高度為 4。

12. 林( forest):林是由 n 棵(n0)互斥樹(disjoint trees)所組成。若

將圖 7-2 的樹根節點 A 去除,則會造成 3 個互斥樹所構成的林。

7.1.3 樹的表示法

樹除了如圖 7-1、7-2 的圖示法外,在記憶體中,我們應該如何來儲存一棵樹呢?

除了特殊的樹之外,通常我們採用鏈結串列來存放樹的節點,並使用鏈結來

表達樹的有向邊。設計如下:

Page 8: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-8

單純的鏈結串列表示法(不佳)

假設樹的分支度為 n,為了要讓每一個節點都能夠正確地表達,因此,我們必須

有 n 個鏈結欄位,用來指向其子樹的根節點,故將節點設計如下:

data link 1 link 2 … link n

由於並非每個節點都有 n棵子樹,缺少的子樹所對應的 link欄位將存放NULL。

明顯地,link 欄位大多時候是浪費的。

除了浪費之外,由於此法必須限制樹的分支為 n,所以在許多場合無法使用。

通常,我們希望樹的鏈結欄位被規範在 2 個,因此發展了『左子右弟』表示法。

左子右弟表示法(佳)

左子右弟(Leftmost-Child-Next-Right-Sibling)表示法的每個節點只需要使用兩個

鏈結欄位,同時它也可以將樹轉換為二元樹(下一節介紹二元樹)。

使用左子右弟表示法完全不需要限制樹的分支度,可適用於任何種類的樹。

採用左子右弟表示法必須有兩個鏈結欄位,一個指向最左邊(left most)的子節點,

一個指向近右(closest right)的兄弟節點,節點設計如下:

Page 9: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-9

資料 data

左子鏈結 left_child_link 右弟鏈結 closest_right_sibling_link

缺少子樹,則 left_child_link 欄位存放 NULL

若無右邊兄弟節點則 closest_right_sibling_link 欄位也存放 NULL(根節點的此

欄位必定為 NULL)。

因此,圖 7-2 若依左子右弟法儲存,則如圖 7-3 所示。

Page 10: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-10

圖 7-3 使用左子右弟法儲存圖 7-2 的示意圖

如果您將圖 7-3 的根節點提起來(順時針旋轉 45°),則可以看作是圖 7-4,

而該圖則是一個二元樹的的範例

至於二元樹,則有許多好用的演算法可以套用,所以左子右弟表示法是最常見的

樹儲存法。

Page 11: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-11

圖 7-4 左子右弟法可以將之視為二元樹的特例

【註】有時候,我們會為了題目所需要的特性,將樹修正為其他方式儲

存,這些方式只要有利於降低空間及時間複雜度都可以彈性使用。

Page 12: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-12

7.2 二元樹(binary tree)

二元樹(binary tree)是一種特殊的樹,看起來像是分支度為 2 的樹,但其實不

然。以下是二元樹的定義。

【定義 7-2】二元樹定義

二元樹是 0個以上節點的集合,當非空集合時,它包含了一個根節點及

兩個獨立的左右子樹,兩個子樹也都是二元樹,第一個(左邊的)子樹

稱為左子樹;第二個(右邊的)稱為右子樹。左右子樹的根節點分別稱

為該二元樹根節點的左子節點(Left child node)與右子節點(Right child

node)。

由上述定義我們可以看出二元樹與分支度為 2 的樹有下列不同之處:

1. 二元樹可以是空的(0 個節點),而樹不能是空的。

2. 二元樹的子樹具有順序性,而樹的子樹沒有順序性。

Page 13: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-13

一個二元樹的範例如圖 7-5 所示,該二元樹的根節點為 A,其中 B,C 為 A 的左右

子節點。而 F 的左子樹為空的二元樹;F 的右子樹為以 J 為根節點的 JKL 二元樹。

其餘依此類推。

圖 7-5 二元樹範例(圖內的箭頭可省略)

Page 14: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-14

7.2.1 二元樹的基本運算

二元樹的基本運算只有三種,分別是取得根節點、取得左子樹,取得右子樹。假

設二元樹的資料型態為 BinaryTree,節點的資料型態為 element,則可定義各運算

如下:

1. element RootData(t):回傳 t 的根節點資料。

2. BinaryTree Lchild(t):否則回傳 t 的左子樹。

3. BinaryTree Rchild(t):否則回傳 t 的右子樹。

按照二元樹的定義,圖 7-6 的兩棵二元樹是不同的,因為左圖的二元樹若執行

Rchild(t) 會獲得空集合 {},而右圖的的二元樹若執行 Rchild(t) 會獲得 {2}。

圖 7-6 二元樹的子樹具有順序性

Page 15: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-15

7.2.2 特殊的二元樹

二元樹每個節點最多只能有兩個子節點。而同高度中節點數量最多的二元樹稱之

為完全二元樹(fully binary tree),如圖 7-10。

二元樹尚具有許多特性,詳述如下:

圖 7-7 使各層次最多的節點數公式

Page 16: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-16

【公設 7.1】

二元樹 i 階層( i1)最多的節點個數為 2i-1。

【證明(略):參考圖 7-7,利用數學歸納法證明】

【公設 7.2】

對深度 k (k1) 的二元樹而言,總節點數最多有 2k-1 個。

【證明(略):參考本書完整版公設 7.3】

【公設 7.3】

對任何非空二元樹 T,ni (0i2) 是分支度為 i 的節點數,則 n0=n2+1。

也就是樹葉的數量(分支度為 0 的節點數)= 分支度為 2 的節點數 + 1

【證明(略):參考本書完整版公設 7.3】

Page 17: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-17

我們現在正式定義三種特殊的二元樹如下,分別是「傾斜樹」、「完全二元樹」

與「完整二元樹」。

傾斜樹(skewed tree)

【定義 7-3】傾斜樹(skewed tree)

每一個節點的右子樹皆為空集合,稱為左斜樹( left skewed tree);

每一個節點的左子樹皆為空集合,稱為右斜樹( right skewed tree)。

圖 7-8 為左斜樹,圖 7-9 為右斜樹。

Page 18: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-18

圖 7-8 左斜樹 圖 7-9 右斜樹

完全二元樹( fully binary tree)

【定義 7-4】完全二元樹( fully binary tree)或稱滿枝二元樹

一棵二元樹,高度為 k (k0),且具有 2k-1 個節點,稱之為「深度為 k

的完全二元樹或滿枝二元數」。

Page 19: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-19

滿枝二元樹是所有同深度二元樹中,節點數量最多的,且每一個深度僅有唯一的

一個完全二元樹。它的節點可以按順序循序編號(如圖 7-10 所示),滿枝二元樹

適用以陣列來循序儲存,因為其節點順序可以當作是陣列的索引。

圖 7-10 完全二元樹(或稱滿枝二元樹)

完整二元樹(complete binary tree)

【定義 7-5】完整二元樹(complete binary tree)

一個二元樹 T,高度為 k,有 n 個節點若且唯若 T 的節點與深度為 k 的

Page 20: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-20

滿枝二元樹的編號 1~n 節點完全一致時,稱 T 為完整二元樹。

由上述定義可知,深度為 k(k>1) 的完整二元樹不止一個(共有 2k-1個)

例如圖 7-10、7-11、7-12 都是深度為 4 的完整二元樹。同時由於編號一致,

因此也適用使用陣列依照循序法儲存。

98

1

4 5 6 7

2 3

98

1

1110 12

4 5 6 7

2 3

圖 7-11 完整二元樹(一) 圖 7-12 完整二元樹(二)

完全二元樹也是完

整二元樹嗎?

是的,完全二元樹也是完整

二元樹,而且還是同高度中

Page 21: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-21

在使用陣列儲存二元樹時,我們可以先將之補足為完整二元樹,就能夠按照編號

知道節點必須儲存在陣列的哪個位置。

因此,首先我們應該先了解完整二元樹的特性。

完整二元樹的特性

完整二元樹可以依序編號,這些編號間具有一些特別的性質,當使用於陣列來

儲存時,特別方便,所以有必要進行了解。

(請注意,下列公設在後面仍會使用)

【公設 7.4】

假設一 n 個節點的完整二元樹,編號順序為 1, 2, …, n,則對於任一個節

點 i (1in),具有下列特點:

,節點數量最多的完整

二元樹。

Page 22: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-22

1. 該樹高度為 log2 n + 1。

2. 若 i=1,則 i 為樹根(無父節點);若 i1,則節點 i 的父節點為節點

2

i

3. 若 2in,則節點 i 的左子節點為節點 2i(若 2i>n,則節點 i 無左子節

點)。

4. 若 2i+1n,則節點 i 的右子節點為節點 2i+1(若 2i+1>n,則節點 i 無

右子節點)。

公設 7.4 的證明可透過數學歸納法得證,在此不多加說明,讀者可透過測試高

度為 4 的幾個完整二元樹加以印證。

7.2.3 二元樹的表示法

二元樹的表示法可分為陣列表示法及鏈結表示法兩種

但陣列表示法(又稱循序表示法)的適用時機限制較大。

Page 23: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-23

二元樹的陣列表示法

使用陣列存放二元樹,由於陣列索引具有連續編號特性,因此較適合使用在完整

二元樹或滿枝二元樹的儲存,原因是存取速度快

使用陣列存放二元樹,非常不適用於傾斜樹,因為很浪費記憶體空間。

假設我們想要以陣列儲存二元樹,首先要將各節點先編號

編號方式按照完整二元樹的方式來編號,缺少的節點仍必須編號

換句話說,除非是完整二元樹,否則編號是採用跳號的方式編列。

根據公設 7.4 的四個公式

當我們已經知道節點的編號為 i 時,要計算節點的父節點、左子節點、右子節

點的編號都非常簡單

所以採用陣列存放時,可套用該公式使得演算法的時間複雜度只有常數時間。

而為了方便對應 C 語言的陣列索引,通常陣列的[0]並不會存放任何節點。

Page 24: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-24

由於陣列大小必須宣告為 n(n 為節點數目)

因此對於完整二元樹而言,空間完全沒有浪費

但對於高度 k 的右斜樹而言,則需使用 2k-1 個空間,其中浪費了 2

k-1-k 個空

間。如圖 7-13 所示。

圖 7-13 使用陣列儲存二元樹

小試身手 7-1

Page 25: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-25

如果要使用二維陣列來存放二元樹,請您設計一種比一維陣列更節省空

間的方法(分別以四層的左傾樹與右傾術為例)。

二元樹的鏈結表示法

對於非完整二元樹的一般二元樹來說,使用鏈結表示法比較具有彈性

並且在設計相關演算法時,通常能以簡單的遞迴方式設計。

鏈結表示法的每一個節點有三個欄位

left_child(指向左子節點)

data(存放資料)

right_child(指向右子節點)。

若以 C 語言來宣告二元樹的節點結構可如下宣告:

typedef struct nodestr *bt_pointer;

typedef struct nodestr

{

int/char data; /* 由節點資料決定適當的型態 */

程式7.1

節點結構名稱 這是節點結構的指標別名。

由於指標別名bt_pointer宣告在

前,所以可以直接來拿宣告左右子

節點的鏈結。

Page 26: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-26

bt_pointer left_child,right_child;

}treenode;

1. 如果 data 為整數可宣告為 int data,若為字元資料,可宣告為 char data

2. 若無左子節點,則 left_child 指向 NULL;若無右子節點,則 right_child

指向 NULL。

3. 通常二元樹的樹根會以一個指標指向它來代表整棵樹,該指標可用上

述的自定結構指標別名 bt_pointer 來宣告(例如 bt_pointer T;)。

4. 使用鏈結表示法存放二元樹的示意圖,如圖 7-14。

這是節點結構的別名

Page 27: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-27

圖 7-14 使用鏈結表示法儲存二元樹

【註】鏈結表示法在尋找父節點方面較為困難,有時候在特殊應用時,

可彈性考慮是否多用一個鏈結欄位指向父節點。

7.2.4 動態建立二元樹

假設我們想要在程式中,以鏈結法建立二元樹,首先會面臨的問題是我們要如何

以文字方式(非圖形方式)表達一棵二元樹呢?

Page 28: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-28

有一種「樹」的串列(list)型式,經由修改就可以解決這樣的問題。

以圖 7-2 為例,「樹」的串列表示如下:

(A(B(E,F(K,L)),C(G),D(H,I(M),J)))

但是由於「二元樹」的子節點最多只有兩個,所以我們不需要使用「,」。只

需要使用兩個括號來代表即可。

假設根節點為 R,左子樹的根節點為 A,右子樹的根節點為 B,那就表達為

R(A….)(B….)

其中關於「…」的部分,則由左子樹與右子樹依照同樣的原則遞迴撰寫。

當然左右子樹可能為空,此時,就以 0 來代表子樹的根節點,且之後就不

再出現「…」了。

如果資料項目為數字,為了避免紊亂,那麼您也可以使用=來代替 0,代表

子樹為空。

左右括號是為了要讓人們容易判斷它是哪一個節點的子節點,而在程式中讀

取串列時,左右括號是沒有意義的,因此可以刪除。

以圖 7-5 為例,它的串列表示法為

A(B(D(H(0)(0))(I(0)(0)))(E(0)(0)))(C(F(0)(J(K(0)(0))(L(0)(0))))(G(0)(0)))

Page 29: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-29

A B D H 0 0 I 0 0 E 0 0 C F 0 J K 0 0 L 0 0 G 0 0

以圖 7-14 為例,它的串列表示法為

A(B(0)(D(0)(0)))(C(E(0)(G(0)(0)))(F(0)(0)))

A B 0 D 0 0 C E 0 G 0 0 F 0 0

那麼程式讀取上述串列後應該如何建立一棵樹呢?

首先,我們要思考的是,第一個被完整建立的子樹會是哪一棵?

A B 0 D 0 0 C E 0 G 0 0 F 0 0

這是一個由下往上的建立過程,所以若以圖 7-14 為例,它應該是 D00 子樹,

然後才能完整建立 B0D 子樹。

程式讀取串列的順序通常是由左向右

Page 30: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-30

我們可以知道,會先讀到整棵樹的根節點 A,然後讀取到 A 的左子節點 B,

而在讀取到 A 的右子節點 C 之前,會先讀取到 B 子樹的所有子孫節點。

因此,我們應該先把 B 子樹建立完成,然後才建立 C 子樹。

換句話說,我們可以透過一個遞迴來設計建立二元樹的演算法,其順序是

建立根節點、建立左子樹、建立右子樹。

Page 31: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-31

建立二元樹的演算法設計如下:

bt_pointer CreateBTree(char *list)

{

bt_pointer NewNode=NULL;

static int i=-1;

i++;

if(list[i]!='\0')

{

if(list[i]=='0')

return NULL;

NewNode = (bt_pointer) malloc(sizeof(treenode));

NewNode->data = list[i];

NewNode->left_child = CreateBTree(list);

NewNode->right_child = CreateBTree(list);

}

return NewNode;

}

以圖 7-14 為例,上述演算法的建立流程如下: 後續接動畫頁

程式7.1

用static宣告的變數,可

以記住上次執行的值,不會

一再地被重新設定為-1

建立左右子樹

時,只要遞迴呼

叫即可。

用遇到0,代表該子樹為空,直接回傳NULL,

讓左或右子鏈結指向NULL即可。

Page 32: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-32

動畫頁(書附光碟也有)

Page 33: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-33

7.3 二元樹的走訪

在上一節中,我們介紹了如何動態建立二元樹的方法。那麼要如何確認我們所設

計的演算法能夠正確地建立二元樹呢?

最簡單的方式,就是走一遍,也就是走訪二元樹的各個節點。

二元樹的走訪(traversing a binary tree)代表的是走訪每一個節點各一次。

以下圖為例,當我們從根節點開始出發走訪時只有三種動作

L(向左移動造訪左子樹)

D(印出根節點的資料)

R(向右移動造訪右子樹)

Page 34: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-34

如此透過遞迴,每棵子樹都以相同方式造訪,就可以完成整棵樹的造訪。

7.3.1 建立與走訪二元樹的實例

L、D、R 可能的組合有 3!=6 種,分別是 LDR、LRD、DLR、DRL、RDL、RLD,

但二元樹的特性是左優先於右,因此剩下 LDR、LRD、DLR 等三種

而依照資料被走訪的次序,此三種走訪依據資料所在位置分別被命名為 LDR

中序走訪、DLR 前序走訪、LRD 後序走訪,其詳細意義如下。

LDR 中序走訪(Inoder):

先造訪左子樹→然後造訪根節點→最後造訪右子樹。

DLR 前序走訪(Preoder):

先造訪根節點→然後造訪左子樹→最後造訪右子樹。

LRD 後序走訪(Postoder):

先造訪左子樹→然後造訪右子樹→最後造訪根節點。

由上可發現,三種走訪方式很容易可以利用「遞迴」,設計演算法如下:

Page 35: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-35

LDR 中序走訪演算法

void inorder(bt_pointer ptr) /* 中序走訪 */

{

if(ptr)

{

inorder(ptr->left_child); ○L

printf("%c ",ptr->data); ○D /* 如為數值資料改為%d */

inorder(ptr->right_child); ○R

}

}

後續接動畫頁

程式7.1

與if(ptr!=NULL)同義

Page 36: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-36

依照此演算法 LDR 中序走訪圖 7-14 的二元樹,則結果為 B D A E G C F。

由於是遞迴執行,因此執行過程需要用到系統堆疊。其過程如下表所示:

動畫頁(書附光碟也有)

Page 37: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-37

呼叫 inorder()次數 子樹根值 動作 呼叫 inorder()次數 子樹根值 動作

1(1) A

2(A1s t) L○A R B 8(A2n d) L○A R C

3(B1st) L○B R NULL 9(C1st) L○C R E

L○BR printf B 10(E1st) L○E R NULL

4(B2n d) L○B R D L○ER printf E

5(D1s t) L○D R NULL 11(E2n d) L○E R

G

L○DR printf D 12(G1st) L○G R NULL

7(D2n d) L○D R NULL L○GR printf G

L○AR printf A 12(G2n d) L○G R NULL

後續步驟列於表格右方

L○CR printf C

12(C2n d) L○C R F

12(F1st) L○F R NULL

L○FR printf F

Page 38: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-38

12(F2n d) L○F R NULL

Page 39: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-39

DLR 前序走訪演算法

void preorder(bt_pointer ptr) /* 前序走訪 */

{

if(ptr)

{

printf("%c ",ptr->data); ○D /* 如為數值資料改為%d */

preorder(ptr->left_child); ○L

preorder(ptr->right_child); ○R

}

}

依照此演算法 DLR 前序走訪圖 7-14 的二元樹,則結果為 A B D C E G F。

由於是遞迴執行,因此執行過程需要用到系統堆疊。

程式7.1

Page 40: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-40

LRD 後序走訪演算法

void postorder(bt_pointer ptr) /* 後序走訪 */

{

if(ptr)

{

postorder(ptr->left_child); ○L

postorder(ptr->right_child); ○R

printf("%c ",ptr->data); ○D /* 如為數值資料改為%d */

}

}

依照此演算法 LRD 後序走訪圖 7-14 的二元樹,則結果為 D B G E F C A。

由於是遞迴執行,因此執行過程需要用到系統堆疊。

【7-1】使用上一節所介紹的建立二元樹函式 CreateBTree()建

立圖 7-14,並利用前面介紹的三種走訪演算法進行走訪,透過

這個方式來確認程式是否在記憶體中正確地建立了二元樹。

程式7.1

Page 41: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-41

1 #include <stdio.h>

2 #include <stdlib.h>

3 #include <malloc.h>

4 #include <stdbool.h> /* 記錄 bool列舉值 */

60 int main(void)

61 {

62 char *list="AB0D00CE0G00F00";

63 bt_pointer T;

64 printf("二元樹建立中...");

65 T=CreateBTree(list);

66 printf("...建立完畢");

67 printf("\n二元樹的中序走訪結果:");

68 inorder(T);

69 printf("\n二元樹的前序走訪結果:");

70 preorder(T);

71 printf("\n二元樹的後序走訪結果:");

72 postorder(T);

73

74 system("pause");

75 return 0;

76 }

程式7.1

... 同本小節之各函式內容 ...

將串列表示法先存放在字串中

執行完畢後,二元樹已

經建立完成

執行三種走訪

Page 42: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-42

二元樹建立中......建立完畢

二元樹的中序走訪結果:B D A E G C F

二元樹的前序走訪結果:A B D C E G F

二元樹的後序走訪結果:D B G E F C A

程式的執行結果顯示了二元樹的走訪結果。兩棵二元樹的[前序走訪+中序走訪]

或 [後序走訪+中序走訪] 之結果如果相等,就代表二元樹是相同的。

小試身手 7-2

請改寫程式 7.1 的 CreateBTree 函式,使之能夠執行兩棵二元樹的走訪,

測試時請以圖 7-5 與圖 7-14 為例。意思是呼叫兩次 CreateBTree 函式,

可正確建立兩棵二元樹。並利用兩棵二元樹的正確走訪,以證明確實經

過呼叫兩次 CreateBTree 函式,正確地建立了兩棵二元樹。

Page 43: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-43

考試偷撇步

由於前序、中序、後序走訪常常出現在各種場合的考題中,而遞迴

模擬往往容易出錯,因此,有人研究出一些圖形速解法,如下圖。

圖 7-15 二元樹走訪的速解法

Page 44: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-44

筆者的話

以上的速解法,其實前序與後序都是按照 DLR 與 LRD 的原理來畫

圖的,下刀時,請特別注意根節點 A 應該在最前還是最後就不容易畫錯

了,而中序走訪的爬山法則與引線二元樹有關,詳見下一節的介紹。

LDR 中序走訪演算法(非遞迴式)

以上的三個演算法因為使用了遞迴因此程式碼非常簡潔,但當樹的深度較深時,

必須使用較大的系統堆疊空間來存放函式呼叫所需要的活動記錄。

Page 45: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-45

在第二章中,我們曾經提過所有的遞迴程式都可以改用疊代法(iteration)改寫,

以下,我們將 inorder 函式以疊代法改寫如下:

int top=-1;

bt_pointer stack[MaxStackSize]; /* 以陣列時做堆疊 */

void iterator_inorder(bt_pointer node) /* 非遞迴中序走訪 */

{

while(1)

{

while(node)

{

push(&top,node); /* push 見 6.2.1節 */

node=node->left_child;

}

node=pop(&top); /* pop 見 6.2.1節 */

if(!node)

break;

printf("%c ",node->data); /* 如為數值資料改為%d */

node=node->right_child;

}

}

程式7.a

如果node指向NULL,則跳出外層的無窮while迴圈

node一開始應該指向樹的根節點

Page 46: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-46

程式看起來比較複雜,實際上是我們在模擬堆疊的執行狀況,以取代遞迴呼叫時

的系統堆疊。

若樹的節點為 n 個,深度為 i,則此程式的時間複雜度為 O(n)

最大空間複雜度則為 O(i)。因為樹中每一個節點都恰好會被 push 到堆疊一次,

並由堆疊 pop 出來一次。

那麼是否能夠不透過堆疊進行中序走訪呢?

答案是可以的,但必須另外花費一些空間,例如後面要介紹的引線二元樹就

可以很簡單完成中序走訪。

7.3.2 決定唯一的二元樹

如果給定二元樹的 [前序走訪+中序走訪] 或 [後序走訪+中序走訪],我們就可以

還原回原來的二元樹。請見以下範例:

【7-2】假設前序 DLR 走訪結果為 ABCDEF,中序 LDR 走訪為

BCAEDF,請畫出二元樹。

Page 47: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-47

1. 由 DLR 前序走訪結果可以知道 A 必為根節點,再由 LDR 中序走訪結

果可知 BC 為左子樹,EDF 為右子樹,A BC DEF,如圖 7-16(a)。

2. 接著判斷 A 的左子樹 BC 的根節點,因為由 DLR 前序走訪結果可以得

知 B 為根節點,而由 LDR 中序走訪結果可知 C 為 B 的右子節點(在中

序,B 在 C 前面),如圖 7-16(b)。

3. 最後判斷 A 的右子樹 DEF 的根節點,因為由 DLR 前序走訪結果可以得

知 D 為根節點,由 LDR 中序走訪結果可知 E 為 D 的左子節點,F 為 D

的右子節點,如圖 7-16(c)。

Page 48: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-48

圖 7-16 由 [前序走訪+中序走訪 ]還原二元樹

另外一個問題是如果給定 [前序走訪+後序走訪] 是否可還原二元樹?

答案是當節點 n2 時,無法決定唯一的二元樹。

例如當 n=2,前序為 AB,後序為 BA,則二元樹可能有如下兩種情況(無法

決定 B 為左子樹還是右子樹):

小試身手 7-3

Page 49: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-49

請改寫程式 7.1 的 CreateBTree 函式,使之完成同樣的功能(在記憶體中

建立二元樹),請將結果對照範例 7-1 的 [前序走訪+中序走訪 ] 或 [後

序走訪+中序走訪 ]來判斷您所改寫的 CreateBTree 函式是否正確?此

外,您也可以將兩棵樹分別以舊的及新的 CreateBTree 函式建立,然後

撰寫一個函式自動判別兩棵樹是否相等?

Page 50: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-50

7.3.3 二元運算樹

[前序走訪+中序走訪] 或 [後序走訪+中序走訪] 可以決定唯一的二元樹,請回顧

運算式之前、中、後序表示法,事實上,兩者之間存在著對應的關係。換句話說,

運算式可以使用二元樹來存放,此稱之為二元運算樹(binary expression tree)。

對二元運算樹進行前序、中序、後序走訪恰可求出該運算式的前序、中序、後序

表示式。

反過來說,當給定一個運算式的前序、中序或後序表示式時,則可藉著下列方式

建立對應的二元運算樹:

1. 替運算式加小括號,加小括號時請考慮運算子的結合性與優先權。

2. 從最內層的括號逐步化為二元樹,其中,運算子做為樹根,左邊運算

元做為左子節點,右邊運算元做為右子節點。完成後,該括號將構成

一棵子樹。做為外層括號的運算元。

3. 重複上述步驟,直到處理完所有的運算子,當然最後處理的運算子將

會是整棵樹的樹根。

Page 51: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-51

後續接動畫頁

【7-3】請建立下列各運算式對應的二元運算樹。

1. C 語言中序運算式為 A+B/C–(D+E)

2. C 語言前序運算式為+*+ACDE

3. C 語言後序運算式為 FEAB-D+*/G–

動畫頁(書附光碟也有)

Page 52: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-52

動畫頁(書附光碟也有)

Page 53: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-53

當我們製作完二元運算樹之後,要求出運算式的前中後序表示式就很簡單,只要

透過前序走訪、中序走訪、後序走訪運算法,就可以得到。

動畫頁(書附光碟也有)

Page 54: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-54

小試身手 7-4

建立中序式 X+(Y++)-Z 的二元運算樹。

【7-4】請求出範例 7-3 運算式的前中後序運算式。

1. 對二元表示樹使用前序造訪求出前序運算式: -+A/BC+DE

對二元表示樹使用後序造訪求出後序運算式:ABC/+DE+-

2. 對二元表示樹使用中序造訪求出中序運算式: (((A+C)*D)+E)

對二元表示樹使用後序造訪求出後序運算式:AC+D*E+

3. 對二元表示樹使用前序造訪求出前序運算式: -/F*E+-ABDG

對二元表示樹使用中序造訪求出中序運算式: (F/(E*((A-B)+D)))-G

Page 55: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-55

7.4 引線二元樹

二元樹的走訪是很常見的需求,以中序走訪來說

中序結果為 A+B-C+D 的二元運算樹,在節點 B 想要知道 B 的運算子為何,

則從中序結果的『+』即可得知,這是因為『+』的結合性為由左向右。

中序結果為 X+(Y++)-Z 的二元運算樹,想要知道 Y 的運算子為何,則從中序

結果的『++』就可得知,因為『++』的結合性為由右向左。

換句話說,某一個節點在中序的前行者或中序的後繼者會是我們常常關注的議題。

在本節中的後繼者一律指的是中序後繼者(inoder successor)。

中序走訪演算法看起來很直觀

但實際上由於使用了遞迴呼叫,因此時間複雜度不是很理想。

在空間使用上,任何一棵包含 n 個節點的二元樹,將使用 2n 個鏈結欄位(指

標),而其中有 n+1 個為空指標(存放 NULL),浪費了記憶體。

為了避免空指標(或稱虛指標)浪費記憶體,後來又發明了引線二元樹

事實上,引線二元樹使用的節點記憶體空間比二元樹還要多(因為要多使用

兩個變數),但卻可以更快速地找到某一個節點的中序前行者或後繼者。

Page 56: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-56

引線二元樹的鏈結欄位仍然宣告為指標,但不同的是,這些指標可以分為兩類:

傳統鏈結及引線(不會再出現 NULL)。

在圖示上,傳統鏈結以實線來表示,而引線則以虛線來表示。

為了區別鏈結欄位內的指標是否為引線,必須額外使用布林變數來記錄,

由於有兩個鏈結欄位,因此,兩個布林變數命名為 left_thread 與

right_thread。

在 C 語言中,可以宣告為 int 資料型態,若為了節省空間,也可以宣告為

short int 資料型態,有些書甚至宣告為 char 資料型態。

因此「引線二元樹的節點結構」宣告如下:

typedef struct thread_T_node *thread_bt_pointer;

typedef struct thread_T_node

{

short int left_thread,right_thread;

char data;

thread_bt_pointer left_child,right_child;

}threadTreenode;

程式7.b

多了兩個欄位,用來指出指標是

傳統鏈結還是引線。

Page 57: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-57

為了方便作圖,因此將節點結構表示如下(在記憶體的順序則必須互調):

left_thread left_child data right_child right_thread

例如當 left_child 為引線時,則 left_thread 應該存放 TRUE(也就是 1),否

則存放 FALSE(也就是 0)。

引線二元樹與一般二元樹的鏈結欄位唯一的不同在於,一般二元樹的鏈結欄位如

果是 NULL,則在引線二元樹中應該修正為引線,如圖 7-17 範例。

左引線應該指向該節點的中序先行者

右引線應該指向該節點的中序後繼者

圖 7-17 引線二元樹的引線應該指向中序先行者或中序後繼者

Page 58: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-58

在圖 7-17 中,我們會發現 B 的左引線與 F 的右引線沒有節點可以供其所指,因為

B 與 F 在中序走訪中為最前與最後的節點。

為了讓所有的鏈結欄位不會出現 NULL,所以我們必須額外增加一個節點,稱之

為引線標頭節點(head node),並將這兩個引線指向它。

至於引線標頭節點的資料欄位應該是沒有資料的,而左右鏈結欄位則都是實

鏈結(非引線),左子鏈結指向樹的根節點,右子鏈結指向本身。

萬一遇到空的二元樹,則兩個鏈結都指向自己。

因此,我們可以將圖 7-17 修正為圖 7-18。

Page 59: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-59

圖 7-18 引線二元樹完整示意圖(對應圖 7-17 的二元樹)

Page 60: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-60

7.4.1 走訪引線二元樹

設計引線二元樹的「中序走訪」非常簡單,如下:

void thread_inorder(thread_bt_pointer tree)

{

thread_bt_pointer temp = tree;

while(1)

{

temp = thread_inoder_successor(temp);

if(temp==tree) break;

printf("%c ",temp->data);

}

}

tree 是一個指向標頭節點的指標。一開始將 temp 也指向標頭節點。

我們只要透過 thread_inoder_successor 依序求出 temp 節點的中序後繼者即

可完成中序的走訪。

程式7.b

回到起始節點,代表走完了,所

以跳離迴圈。

thread_inoder_successor會找

出目前節點的下一個中序後繼者。透

過while迴圈一直找中序後繼者,輸

出就是中序走訪的結果。

Page 61: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-61

而 thread_inoder_successor 設計如下,它能夠找出目前節點的下一個中序後繼者,

並回傳中序後繼者節點的記憶體位址(指標)。

#define TRUE 1

#define FALSE 0

thread_bt_pointer thread_inoder_successor(thread_bt_pointer temp)

{

thread_bt_pointer ptr;

ptr=temp->right_child;

if(temp->right_thread!=TRUE)

{

while(temp->left_thread !=TRUE)

{

ptr=ptr->left_child;

}

}

return ptr;

}

由於中序為 LDR 順序,所以後繼者必定位於右子樹中,故將代表 ptr 設定為節點

的右子鏈結。而 ptr 可能有下列兩種狀況:

程式7.b

如果右子鏈結不是右引線

prt 設定為右子鏈結,但這個鏈結可能是右子節點,也

可能是右引線。如果是右引線,那就是中序後繼者,直

接回傳ptr即可。

當左子鏈結非引線,持續往左探

Page 62: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-62

1. 如果右子鏈結為引線,則中序後繼者為 ptr,回傳 ptr 即可

(例如圖 7-18 節點 C 的後繼者為 A)。

2. 如果右子鏈結非引線(為實際鏈結),則沿著左子節點路徑往左探(因

為中序 LDR 的走訪優先順序為往左探),直到遇見 ptr->left_thread 為

TRUE,也就是一直往左探,直到左鏈結為引線為止,則該節點即為中

序後繼者

(例如圖 7-18 節點 B 的後繼者為 C、節點 A 的後繼者為 E)。

【註】請注意,上述兩種狀況畫出中序走訪的順序就是爬山法。

事實上,若將標頭節點也視為普通節點,則中序造訪為 BCAEDF-。

而上述演算法除了 F 的後繼者為標頭節點『-』之外,還會得到標頭節點『-』

的後繼者是 B。

換句話說,是採循環方式認定後繼者。因此,需要加入 if(temp==tree) break;,

否則程式不會停止。

當然若事先知道節點數量,則可改用 for 迴圈,這樣子作還可以從任一節點開

始進行中序走訪,例如從 E 節點開始的話,會得到的中序走訪為 DF-BCAE。

Page 63: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-63

上述演算法除了具有循環走訪優點之外,和之前的演算法來比較,它並不需要使

用堆疊,而且時間複雜度為 O(n),雖然疊代版 inorder 演算法的時間複雜度也為

O(n),但上述演算法的時間常數較小一些。

7.4.2 修改引線二元樹

雖然引線二元樹在空間使用率上較傳統二元樹優良,並且走訪的時間複雜度也較

佳,但如果要修改引線二元樹(例如加入或刪除節點)就相對比較困難。

在這裡,我們僅探討如何加入節點到引線二元樹,至於刪除節點則類似此法。

加入節點必須先定義好要將節點加入到目前節點的那一邊

假設目前節點稱為 parent,則我們僅探討在該節點加入右子節點的做法。

Page 64: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-64

而加入右子節點的狀況又可以分為兩種,分述如下:

Case 1:parent 的右子樹為空的(右子鏈結為引線)。

圖 7-19(a) 引線二元樹加入右子節點(右子樹為空時)

Page 65: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-65

考慮狀況如圖 7-19(a) 所示,必須完成的事有下列幾項:

新子節點 D 的 right_child 為引線,並指向 B->right_child 原本指向

的 A。

【因為 B 的後繼者原為 A,而加入 D 之後,D 的後繼者也應該是 A】

新子節點 D 的 left_child 為引線,並指向 B。

B->right_child 需修改為非引線,並指向新子節點 D。

為了撰寫程式的方便,因此按照順序撰寫如下(B 為 parent、D 為 child):

child->right_child= parent->right_child; child->right_thread=TRUE;

child->left_child=parent; child->left_thread=TRUE;

parent->right_child=child; parent->right_thread=FALSE;

因為 B 的新後繼

者為D

Page 66: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-66

Case 2:parent 的右子樹為非空的(右子鏈結非引線)。

圖 7-19 (b) 引線二元樹加入右子節點(右子樹非空時)

Page 67: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-67

由於右子樹為非空,因此我們必須先考慮加入新節點後,原來的右子樹(XYZ)是

新節點 D 的右子樹還是左子樹,為了單純化,我們規定原來的右子樹將是新節點

的右子樹。考慮狀況如圖 7-19(b) 所示,必須完成的事有下列幾項:

新子節點 D 的 right_child 為非引線,並指向 B->right_child 原指向

的 X。

新子節點 D 的 left_child 為引線,並指向 B。

B->right_child 指向新子節點 D。

Y 的 left_child 由指向 B 改為指向新子節點 D。

【與的用意是因為 Y 為原本 B 的後繼者,加入 D 之後,Y 後來變

成 D 的後繼者】

為了撰寫程式的方便,因此按照順序撰寫如下(B 為 parent、D 為 child):

child->right_child= parent->right_child; child->right_thread=FALSE;

child->left_child=parent; child->left_thread=TRUE;

parent->right_child=child;

thread_inoder_successor(child)->left_child=child;

因為B的新後繼者為D

Page 68: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-68

為了合併 Case 1 與 Case 2,因此,要找出不同之處,完全相同,而 、不同,

至於則只有在 Case 2 情況下才發生,所以可以合併如下:

child->right_child=parent->right_child;

child->right_thread=parent->right_thread;

說明:由 parent->right_thread 來決定 child->right_thread,即可同時適

用 Case 1 與 Case 2。

child->left_child=parent; child->left_thread=TRUE;

說明:完全不需要改變。

parent->right_child=child; parent->right_thread=FALSE;

說明:Case 2 的 parent->right_thread 本來就是 FALSE,因此直接拿

Case 1 的做法來套用,並無影響。

Page 69: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-69

為了要同時適用於 Case 1 與 Case 2,只有當 child_right_thread 為

FALSE 時,才作 thread_inoder_successor(child)->left_child=child。

依照上述討論,我們可以將插入右子節點的演算法設計如下:

void insert_right(thread_bt_pointer parent,

thread_bt_pointer child)

{

thread_bt_pointer temp;

child->right_child = parent->right_child;

child->right_thread=parent->right_thread;

child->left_child=parent;

child->left_thread=TRUE;

parent->right_child=child;

parent->right_thread=FALSE;

if(child->right_thread==FALSE)

{

temp=thread_inoder_successor(child);

temp->left_child=child;

}

程式7.b

Page 70: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-70

}

Page 71: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-71

7.5 林(forest)

林(forest;或翻為樹林)是由樹所組成,其詳細定義如下:

【定義 7-6】林

林( forest)是一組由 n 個(n0)不同的樹所構成的集合。

如果將一棵樹的根節點刪除就可以構成林。

如果將二元樹的根節點刪除則產生具有兩棵樹的林。

Page 72: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-72

將林轉換為二元樹

在前面介紹樹的左子右弟儲存法時,事實上,就是把一棵樹轉換為二元樹的方法。

而要將林轉換為二元樹,方法也非常類似,以下是轉換的定義:

【定義 7-7】林轉二元樹

若一個林 F 由 T1, T2,…,Tn 等樹所構成,該林所對應(轉換後)的二元

樹以 B(T1, T2, ….Tn) 來示,則 B 具有下列特性:

(1) n=0 時,B(T0)=空集合。

(2) B(T1, T2, …,Tn) 的根節點與 root(T1) 相同,左子樹與 B(T11,

T12, ...,T1m) 相同,其中,T11, T12, …,T1m 為 root(T1) 的子樹;右子樹

為 B(T2, T3, …,Tn)。

依照上述定義,林轉二元樹可以由下列步驟來完成。

Step 1: 將林中的每棵樹先化為二元樹(左子右弟法,但最後先不旋轉 45°)

Page 73: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-73

Step 2: 將所有二元樹利用根節點全部鏈結在一起(T1 的根節點之右鏈結

欄位連至 T2,T2 的根節點之右鏈結欄位連至 T3,…….)。

Step 3: 旋轉 45°

【7-5】以由三棵樹所構成的林為例,將林轉換為二元樹。

Page 74: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-74

若以範例 7-5 說明定義,則 T1為 {ABCDEF} 所構成的樹,T2為 {GH} 所構成的

樹,T3為 {IJK} 所構成的樹。

Page 75: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-75

B(T1, T2, …,Tn) 的根節點為 A,而 root(T1) 也為 A。T11, T12,…,T1m所指的左子樹

為 {BCDEF} 所構成。右子樹則為 B(T2, T3)。而 T21所指的左子樹為 {H} 所構

成。T31, T32所指的子樹為 {JK} 所構成。

Page 76: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-76

7.6 累堆(heaps)

在前面我們已經介紹了樹、二元樹與林的定義及資料結構,並且介紹了何謂樹的

走訪。在接下來的篇幅中,我們將會利用樹與二元樹進行應用

通常它們會應用在搜尋、排序、找最大值、最小值等場合

並且會因著應用而制定出具備更多特性的特殊樹或特殊二元樹資料結構。

在本章中,我們將介紹累堆、二元搜尋樹及 m-元搜尋樹

至於其他各類的樹,則會在後面章節中陸續介紹。

累堆是一種特殊的完整二元樹,也是一種資訊領域常用的資料結構,例如優先權

佇列就是採用最大累堆實作。

Page 77: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-77

7.6.1 優先權佇列

優先權佇列(priority queue)是一種特殊的佇列,它與第六章所介紹的佇列不同

一般的佇列具有先進先出的特性,換句話說,誰先輸出由誰先進入的時間為

準則。但有時候,這種佇列並不合乎實際的需要。

例如在 UNIX 系統中,它允許透過工作排程來安排各種要求作業的順序,而在安

排作業順序時,它允許對於不同等級的人員或工作,設定不同的優先權

例如在學校的 UNIX 系統中,教授或研究生的優先權通常高於大學部學生。

在其他的實際應用上,例如某些公司會規定 VIP 會員必須優先處理,甚至還區分

為金卡 VIP、銀卡 VIP 等類似的等級區別。

這個時候,使用優先權佇列做為資料結構將會是比較好的選擇。

簡單來說,佇列就像是一般的排隊,誰先排就決定了誰先被服務

優先權佇列則像是「允許插隊」的排隊,隊伍不斷地被插隊的人修正為新的排列

方式,而誰的優先權較高則決定了誰先被服務

除非是優先權相等的人,才會以時間做為服務順序的依據。

Page 78: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-78

優先權佇列仍屬於佇列的一種,因為第一個服務的仍舊應該是排在佇列最前端的

作業。只不過作業在存入佇列時,必須調整佇列內原有的順序。

使用傳統佇列的演算法無法滿足優先權佇列的需求,當然您可以改變演算法,使

得每次新加入的作業都調整佇列內原有作業的順序。

不過這樣一來,原有的演算法會變得比較複雜

我們希望的是找到一種資料結構,恰好具有最大值永遠位於第一位的特性,

而這種資料結構恰巧是最大累堆的特性

所以一般來說,優先權佇列都採用最大累堆來實作。

7.6.2 最大累堆

在前面的說明中,曾經出現過「累堆」與「最大累堆」等名詞,以下,我們針對

相關名詞先行定義,然後再介紹最大累堆的資料結構。

【定義 7-8】最大樹(max tree)

最大樹是一種樹,

其每一個節點的鍵值不小於它的子節點(如果存在)的鍵值。

Page 79: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-79

記憶口訣:父節點子節點。

【定義 7-9】最小樹(min tree)

最小樹是一種樹,

其每一個節點的鍵值不大於它的子節點(如果存在)的鍵值。

記憶口訣:父節點子節點。

一個節點的鍵值可以視為節點的資料,但如果資料欄位已經被使用,則可以另行

宣告一個欄位作為鍵值。

節點的鍵值是用來辨識節點,例如當要比較節點大小時,就可以依照節點的鍵值

來比較,想要尋找節點時,也可以比對鍵值來判斷要找的節點是哪一個。

【定義 7-10】最大累堆(max heap)與最小累堆(min heap)

最大累堆是一棵完整二元樹,且也是一棵最大樹。

Page 80: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-80

最小累堆是一棵完整二元樹,且也是一棵最小樹。

1. 由於最大累堆是完整二元樹,因此使用「陣列」存放即可。如果為了套

用公設 7.4 所推導的公式,可使用陣列 A[1:n]來存放

但 C 語言的陣列由 0 開始,因此為了套用公式,我們宣告陣列大小為 n+1,

並放棄第 0 個位置。

2. 在圖 7-20 中,三棵二元樹都是最大累堆,明顯地,如果節點的鍵值為

優先權的話,在應用於優先權佇列時,第一個要被服務的對象應該是

根節點。

Page 81: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-81

圖 7-20 最大累堆

使用陣列來存放最大累堆

建立空的累堆只需要宣告陣列即可。

HeapFull、HeapEmpty 等觀察運算是用來判定累堆是否已空或已滿。

最大累堆還必須包含插入與刪除運算,這兩種運算比較複雜,原因是我們必

須在插入與刪除節點後,將二元樹調整使之仍符合最大累堆的定義。

因此,我們將分別在 7.6.3 節與 7.6.4 節中說明插入與刪除運算。

以下是使用 C 陣列來實作上述最大累堆的資料結構(除加入與刪除運算):

#define MaxSize 201 /* 累堆可以最多有 200個元素 */

#define HeapFull(n) (n==MaxSize-1)/* n代表目前累堆有幾個元素 */

程式7.c

Page 82: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-82

#define HeapEmpty(n) (!n) /* n為 0時,!0=1 -> 相當於回傳 TRUE */

typedef struct

{

int key;

/* 其他欄位 */

}element;

element heap[MaxSize]; /* 宣告存放累堆的陣列 */

int n=0; /* 一開始累堆是空的 */

7.6.3 加入一個新的元素到最大累堆

由於最大累堆是一個完整二元樹,因此當我們要加入一個新的元素時,有下列兩

點需要注意:

1. 加入新元素後,仍必須是完整二元樹。

2. 為了仍需保持最大累堆的性質,因此,新元素的節點位置必須正確。

由於 1. 的規定,我們可以很容易得知加入後的完整二元樹形狀為何

就是原本編號最後一個再多加一個節點,如圖 7-21。

key 是鍵值,例如可用來代表優先權等級,節

點如果還有其他資料,可在結構內多宣告幾個

欄位。

Page 83: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-83

圖 7-21 最大累堆加入新元素後,形狀可預測

問題是「新加入的元素最終並不一定放在編號最後一個節點」,不過我們可以先

將新加入的元素放在最後一個節點,如圖 7-22(a),然後再來做調整。

後續接動畫頁

動畫頁(書附光碟也有)

Page 84: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-84

由於使用陣列來存放最大累堆,而這也使得祖先節點在下移時,變得很簡單。依

照公設 7.4 介紹的公式,我們可以將加入元素的演算法設計如下:

void insert_maxheap(element item,int *n) /* 插入新元素到累堆 */

{

int i;

if(HeapFull(*n)) { printf("Heap is Full!\n"); exit(1); }

(*n)++;

i=*n;

while(i!=1)

程式7.c

透過迴圈來調整元素的位置。

i先設定為最後一個節點的陣列索引

*n加1,代表累堆要增加一個新元素

Page 85: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-85

{

if(!(item.key > heap[i/2].key)) break;

heap[i]=heap[i/2]; /* 將父節點往下降 */

i=i/2; /* 由於宣告為 int,所以自動轉型相當於取下高斯 */

}

heap[i]=item;

}

1. 演算法使用 C 語言函式完成,由於 n 代表的是目前累堆有多少個元

素,因此採用的是傳指標呼叫。

由於在前面的宣告時,採用 int n=0; ,因此呼叫此函式記得採用

insert_maxheap(item,&n),以便使得 n 的值能夠改變。

2. 本函式的時間複雜度由 while 迴圈決定

在非最差狀況下,可由 if 條件式決定提早跳出迴圈

在最差狀況下,必須調整到根節點時才會離開 while 迴圈,而由於一個 n 節點

的完整二元樹,其深度為 log2 n + 1,因此,在最差的狀況下,時間複雜度

為 O(log2n)。

最後才將新元素插入累堆的正確位置

Page 86: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-86

3. 使用最大累堆實作優先權佇列的插入新元素時間複雜度為 O(log2n),

而使用一般的已排序陣列或一般的已排序鏈結串列,要插入新元素的

時間複雜度都為 O(n)

明顯地,最大累堆是實作優先權佇列的不二選擇。

7.6.4 從最大累堆刪除一個元素

從最大累堆刪除一個元素的運算,是代表取出鍵值最大的元素。

在最大累堆中,最大的元素就是根節點的元素。

而提取根節點元素非常簡單,因為它必定位於陣列的第 1 個位置。

當我們移除根節點元素後,必須調整樹的形狀及內容,使它仍舊是一個最大

累堆。

我們可以很容易得知刪除元素後的完整二元樹形狀為何,就是原樹編號最後一個

節點應該去除,如圖 7-23。

Page 87: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-87

圖 7-23 最大累堆刪除元素後,形狀可預測

為了要調整累堆中所有的節點以符合最大累堆的規定,首先我們應該要先決定要

調整哪一個節點到根節點

後續接動畫頁

動畫頁(書附光碟也有)

Page 88: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-88

Page 89: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-89

同樣地,我們可以將刪除最大鍵值元素的演算法設計如下:

element delete_maxheap(int *n) /* 刪除最大鍵值元素 */

{

int parent,child;

element item,temp;

if(HeapEmpty(*n)) { printf("Heap is Empty!\n"); exit(1); }

item=heap[1]; /* */

temp=heap[(*n)]; /* 取出編號最後的節點放入 temp */

(*n)--; /* 整棵樹的節點數量應該少一個 */

parent=1; /* 由根節點開始比較 */

child=2; /* 根的左子節點編號 */

程式7.c

item是要回傳的最大鍵值元素

Page 90: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-90

while(child <= *n)

{

if ((child <*n) && (heap[child].key < heap[child+1].key))

child++; /* 右子節點較大,所以應該比較的是右子節點 */

if(temp.key >= heap[child].key)

break;

else

{

heap[parent]=heap[child]; /* 與較大的子節點互換 */

parent=child;

child=child*2;

}

}

heap[parent]=temp;

return item;

}

1. 時間複雜度若在最差狀況下,必須到樹葉節點時才會離開 while 迴圈

而 n 節點的完整二元樹,其深度為 log2n + 1,因此,在最差的狀況下,時間

複雜度為 O(log2n)。

將編號最後的節點放入正確的位置

修正已經完成

移到下一層

Page 91: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-91

2. 使用最大累堆刪除最大鍵值元素的時間複雜度為 O(log2n),也就是當

用來實作優先權佇列時,取出最大優先權的時間複雜度為 O(log2n) ,

而使用一般的未排序陣列或一般的未排序鏈結串列的時間複雜度則為

較差的 O(n)。

3. 雖然使用一般的已排序陣列或鏈結串列,取出最大優先權的時間複雜

度為常數,但對陣列或鏈結串列進行排序,目前已知的最佳時間複雜

度同樣也需要 O(logn)【在第九章會討論排序問題】。

4. 綜合插入新元素與刪除最大鍵值元素來看

使用最大累堆實作優先權佇列是最好的選擇。

5. 最大累堆並不適合用在刪除任一元素或搜尋特定元素

其時間複雜度已證明為 O(n)

因此,又發明了二元搜尋樹來解決這樣的問題,我們將於下一節介紹。

Page 92: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-92

【7-6】

有一個最大累堆,如下圖所示,請畫出當刪除最大鍵值元素之

後,最大累堆的樹狀圖。

Page 93: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-93

小試身手 7-5

請撰寫程式,在記憶體中建立範例 7-6 的累堆,並執行刪除最大鍵值元

素後,將累堆的內容列印出來。

○延○伸○學○習 更多的累堆議題

有關累堆還有很多議題,在第九章介紹排序時,我們將會介紹如何

利用累堆進行排序。而其他較深入的議題還包含最小 -最大累堆與雙向累

堆,您可以參閱本書完整版的附錄 F 或演算法書籍的高等資料結構章節。

Page 94: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-94

7.7 二元搜尋樹(binary search tree)

二元搜尋樹的適用對象與最大累堆有所不同。

二元搜尋樹特別適合用在搜尋特定的節點

在二元搜尋樹中,每一個節點都有一個唯一的鍵值,它也適合用於刪除特定

的節點,例如刪除鍵值為 v 的元素,刪除第二個最小的元素等等。

二元搜尋樹的特色是

對於所有的內部節點而言,其左子節點都必須小於該節點,而右子節點則必

須大於該節點。

在下圖中,(a) (b) 都是二元搜尋樹,但 (c) (d) 都不是二元搜尋樹。

圖 7-25 二元搜尋樹的正反例

Page 95: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-95

二元搜尋樹的詳細定義,是利用遞迴來加以定義,二元搜尋樹的定義如下:

【定義 7-11】二元搜尋樹(binary search tree)

二元搜尋樹是一種特殊的二元樹,它可以是空的,若非空時,具有下列

特性:

令整棵樹為 T,TL 為其左子樹,TR 為其右子樹, root() 為取出該樹根

的鍵值,則

(1) 每一個元素具有一個鍵值(該鍵值具有唯一性)。

(2) 若 TL 非空,則 root(TL)<root(T)。

(3) 若 TR 非空,則 root(T)<root(TR)。

(4) TL 與 TR 都必須是二元搜尋樹【遞迴定義】。

事實上,(2)(3)(4) 已經說明,每一個鍵值都具有唯一性,之所以在定義 (1) 中

列出此特性,只是為了特別強調二元搜尋樹的每一個節點元素的鍵值都不同。

Page 96: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-96

二元搜尋樹不一定是完整二元樹,因此我們採用「鏈結串列」來存放二元搜尋樹

前序、中序、後序走訪等演算法都仍舊可以使用在二元搜尋樹上。

當您採用中序走訪二元搜尋樹時,獲得的結果會是由小到大排序的結果。

考試偷撇步

如果想要練習中序走訪的爬山圖形速解法,可透過二元搜尋樹來練

習,因為中序走訪結果必定為由小到大的排序結果。例如將圖 7-15 的英

文字母改為合乎二元搜尋樹定義的數字,就可以很容易看出爬山法的路

徑了。

對二元搜尋樹進行刪除與加入等運算的演算法必須另行設計,以維持二元搜尋樹

的特性。

二元搜尋樹是非常適合搜尋特定元素的一種資料結構,以下,我們將介紹二元搜

尋樹的搜尋、加入、刪除等運算。

Page 97: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-97

7.7.1 二元搜尋樹的搜尋運算

在二元搜尋樹中,要搜尋特定(假設鍵值為 k)的元素是非常容易的。

只要使用遞迴並在根節點太大時往左子樹找,太小時往右子樹找即可。

如下是其搜尋演算法的遞迴版本:

bt_pointer search(bt_pointer root,int k)

{ /* root指向子樹根節點,k為目標鍵值 */

if(root == NULL) return NULL; /* 已找到樹葉節點,仍未找到符合者 */

if(k == root->data) return root;

if(k < root->data)

return search(root->left_child,k);

else

return search(root->right_child,k);

}

時間複雜度與樹的高度有關,因為每一層只會找一個節點來比較,若樹的高

度為 h,則時間複雜度為 O(h)。

程式7.d

相等代表已經找到

目標值比較小,所以往左子樹找到

目標值比較大,所以往右子樹找到

Page 98: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-98

上述演算法使用遞迴,所以需要使用 O(h) 個系統堆疊記憶體空間

如果將之修改為非遞迴版本,是否還需要模擬堆疊呢?

其實並不需要,如下為疊代版的搜尋演算法。

bt_pointer iteration_search(bt_pointer BSTree,int k)

{

/* BSTree指向樹的根節點,k為目標鍵值 */

while(BSTree!=NULL)

{

if(k == BSTree->data) return BSTree;

if(k < BSTree->data)

BSTree=BSTree->left_child;

else

BSTree=BSTree->right_child;

}

return NULL; /* 已找到樹葉節點,仍未找到符合者 */

}

上述演算法的時間複雜度仍為 O(h),但過程不需要使用堆疊。

程式7.d

相等代表已經找到

往左或往右找

Page 99: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-99

7.7.2 在二元搜尋樹中插入元素

在二元搜尋樹中,要插入新元素必須先確認樹中並無相同鍵值的元素(否則按照

定義不可新增該元素),這可以透過搜尋演算法來完成

在上一小節,我們已經有兩個版本的搜尋演算法了。

接下來,我們要思考的是新元素應該插入到樹的那一個位置上。

由於疊代版的搜尋比較節省記憶體空間,所以我們應該會選擇此版本

而當我們執行該版本並搜尋一個樹中沒有的鍵值時,將會發現到,指標

BSTree 最後會停留在新元素應該出現的節點處。

例如在圖 7-26(a) 中,如果搜尋 k=16,則 BSTree 會停留在 17 的左子節點

處(當然該處應為 NULL)

如果搜尋 k=39,則 BSTree 會停留在 30 的右子節點處(當然該處也應為

NULL)。

Page 100: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-100

為何會這樣呢?

道理其實很簡單,因為假設 16 或 39 早已位於樹中,則 BSTree 應該停留

在該節點,所以自然該位置就是新元素應該放置的正確位置

如圖 7-26 的虛線節點若改為實線就是插入後的示意圖。

圖 7-26 二元搜尋樹插入新元素

Page 101: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-101

依照上述結論,我們將 iteration_search 稍作修改為 modify_search,使得它不但具

有搜尋功能,也可以提供做為插入元素時使用:

和原始搜尋的回傳值不同,若找到回傳 NULL,若未找到則回傳某一個節點

bt_pointer modify_search(bt_pointer BSTree,int k)

{ /* BSTree指向樹的根節點,k為目標鍵值 */

while(BSTree!=NULL)

{

if(k == BSTree->data) return NULL;

if(k < BSTree->data)

{

if(BSTree->left_child==NULL) break;

BSTree=BSTree->left_child; /* 往左子樹找 */

}

else

{

if(BSTree->right_child==NULL) break;

BSTree=BSTree->right_child; /* 往右子樹找 */

}

}

return BSTree;

}

程式7.d

加入這一條敘述,代表左子節點為

空,跳出迴圈,此時 BSTree就是

應插入位置的父節點

加入這一條敘述,代表右子節點為

空,跳出迴圈,此時 BSTree就是

應插入位置的父節點

未找到資料,回傳應插入位置的父節點

Page 102: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-102

經過修改的搜尋,若找到則回傳 NULL;若未找到則不回傳 NULL,而是回傳應

插入位置的父節點。

因此,應用這個 modify_search,可以將插入節點的演算法撰寫如下:

void insert_node(bt_pointer *wnode,int num)

{

bt_pointer ptr;

bt_pointer temp = modify_search(*wnode,num);

if(temp || ((*wnode)==NULL))

{

ptr=(bt_pointer)malloc(sizeof(treenode)); /* 可增配置是否成功,略 */

ptr->data=num;

ptr->left_child =NULL;

ptr->right_child=NULL;

程式7.d

*wnode為 NULL,代表空樹。非空樹時,由 temp是否為

NULL決定是否要作插入新節點的動作

temp 取得插入新節點

之父節點的位置。

製作新節點的內容,稍後再加到

二元搜尋樹中。

Page 103: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-103

if((*wnode)!=NULL) /* 非空樹時 */

{

if(num < temp->data)

temp->left_child = ptr;

else

temp->right_child = ptr;

}

else

{

*wnode=ptr; /* 空樹時,新元素直接指定為根節點 */

}

}

}

空樹時,輸入的新元素必定成為根節點。

非空樹時,由呼叫 modify_search 的結果 temp 是否為 NULL 決定是否要將新元素

加入樹中。

若需加入,則 temp 為新元素的父節點,並透過比較大小決定加在左或右子節

點。

在父節點的左或右加入新節點

Page 104: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-104

假設要建立圖 7-26(a) 的二元搜尋樹,可以依照下列範例程式呼叫。

int i;

int array[8]={25,15,30,12,17,12,16,27};

bt_pointer head= NULL;

for(i=0;i<=7;i++)

insert_node(&head,array[i]);

我們故意讓鍵值 12 出現兩次,如此可檢驗樹中是否會出現重複的鍵值。

為了檢驗搜尋二元樹是否與圖 7-26(a)相同,因此,我們可以利用「前序+中序」

來判定。如下為呼叫前序走訪與中序走訪的程式碼。

printf("preoder\n");

preorder(head);

printf("\ninoder\n");

inorder(head);

程式7.d

Page 105: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-105

利用「前序+中序」走訪來檢驗程式是否正確建立一棵樹,是二元樹程式設計的常

用技巧。如下為其執行結果。

preoder

25 15 12 17 16 30 27

inoder

12 15 16 17 25 27 30

因為中序走訪的順序為『左子樹 根節點 右子樹』,而二元搜尋樹的左子樹鍵值

必定比根節點小,而右子樹鍵值必定比根節點大。

所以二元搜尋樹內的鍵值是已經排序的結果。

探討 insert_node 演算法可以發現,其時間複雜度也為 O(h)。因為時間花費主要是

發生在呼叫 modify_search,其餘皆為常數時間。

中序走訪二元搜尋樹必定是由小到大排序的結果。

Page 106: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-106

7.7.3 在二元搜尋樹中刪除元素

由於二元搜尋樹的定義,使得二元搜尋樹在刪除元素時,變得特別簡單,以下是

其演算法:

Procedure delete(T,k) /* T為二元搜尋樹,k為欲刪除元素的鍵值 */

If (k is not in T) then [ return ]

If (k is in T's node ptr) then

[

If ((ptr無左子節點) && (ptr無右子節點)) then

[ 將 ptr的父節點指向 prt的鏈結欄位設為 NULL ]

else If (ptr無左子節點) then

[ 將 ptr的父節點指向 prt的鏈結欄位設為 ptr的右子節點 ]

else If (ptr無右子節點) then

[ 將 ptr的父節點指向 prt的鏈結欄位設為 ptr的左子節點 ]

else

[ 將 ptr的左子樹中最大的元素取代 ptr節點,並調整相關鏈結 ]

free(ptr)

]

endProcedure

Case 2

Case 3

Case 1

Page 107: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-107

上述演算法可以分為三種狀況來討論:

Case 1:欲刪除的節點為樹葉節點,則釋放該節點即可。

Case 2:欲刪除的節點只有一個子節點,則將子節點取代該節點,然後釋放該節

點即可。

Case 3:欲刪除的節點有兩個子節點,此時,可以有兩個選擇。

將左子樹中的最大元素節點取代該節點

或者將右子樹中最小元素節點取代該節點即可。

上述三種狀況,除了 Case 3 之外,其作法都顯而易見。至於 Case 3,在上述演算

法中,我們選擇的是將左子樹中的最大元素節點取代該節點,這樣做是否有道理?

請見下列的討論。

由於二元搜尋樹的定義,每一個節點鍵值必須大於左子樹任一節點的鍵值,並小

於右子樹任一節點的鍵值。

所以只有左子樹中的最大元素或右子樹中最小元素足以取代欲刪除節點原有

的位置。

Page 108: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-108

圖 7-27 二元搜尋樹刪除元素

至於取代時,是否會有兩個鏈結欄位無法處理?

答案是不會的,因為左子樹中的最大元素必定不會有右子樹,而右子樹中的

最小元素必定不會有左子樹,所以取代時,並不會有任何問題。

例如圖 7-27 中,欲刪除 45,在 45 的左子樹中最大者為 43,因此,43 將

取代 45 的位置,而由於 43 必定無右子樹,因此,可直接將 43 的左子樹取

代 43 原來的位置即可。 後續接動畫頁

Page 109: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-109

以上的演算法明顯地,仍受到搜尋目標節點的影響,所以時間複雜度仍為 O(h),

至於 C 語言版本的函式,則留給讀者練習。

動畫頁(書附光碟也有)

Page 110: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-110

7.7.4 二元搜尋樹的高度

在介紹搜尋、加入、刪除等運算時,我們發現這些演算法的時間複雜度皆為 O(h),

h 為二元搜尋樹的高度。

因此,h(高度)與 n(總節點數)之間的關係是我們關心的議題。

如果您曾注意插入新元素的演算法就會發現,如果我們插入元素的順序為 {1, 2, 3,

4,…, n},則建立出來的二元搜尋樹將是一棵右傾樹,這樣的問題是

非常嚴重的,因為相關的演算法時間複雜度皆為 O(h),若 n=h(傾斜樹),則會

變成 O(n)。

所以我們必須很小心地調整資料的輸入順序,避免發生此類狀況。

資料的輸入順序有時是不可預測及控制的,所幸平均來說,若資料以雜亂順序輸

入,其平均的高度 h 為 log2n。而 O(log2n) 是我們可以接受的時間複雜度。

二元搜尋樹在資訊領域非常重要

例如編譯器常用的符號表就可採用此種結構

因此,最差時間複雜度 O(n) 仍無法被接受,而後來又設計了高度平衡搜

尋樹(AVL),其高度最差狀況為 logn,我們將於第 10 章介紹 AVL 樹。

Page 111: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-111

○延○伸○學○習 霍夫曼解碼樹

二元搜尋樹還有另外一種特殊的應用可用來進行編碼與解碼,稱之

為霍夫曼解碼樹,它利用了下一章要介紹的圖形之權重邊來強化二元搜

尋樹的邊,有興趣的讀者可於閱讀下一章後,自行參考附錄的介紹。

Page 112: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-112

Page 113: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-113

7.8 m 元搜尋樹(m-way search tree)

本章前面將樹轉為二元樹以便進行其他的應用,是因為要避免記憶體的浪費,但

樹並非完全沒有優點。

例如圖 7-1 與圖 7-3 中,從根節點 A 移動到節點 m 只需要三步,但若將

其轉換為二元樹之後,就必須要六步才能到達節點 m。

換句話說,樹並非完全沒有優點的,只是場合適不適合的問題。

二元搜尋樹由於採用二分法,因此要找到目標節點常常需要大量地移動

鏈結,才能到達目標節點。

如果在某個應用場合中,節點的鍵值可以有多個,那麼搜尋樹的高度就

能夠縮短。

也就是說,如果節點不只是作為小於大於的分界,而是區間的區分,則能夠更快

速地找到所需要的節點。

因此,有人發明出 m 元搜尋樹,將搜尋的原則跨出了二分法,而成為區間

法。

Page 114: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-114

m 元搜尋樹的結構

一棵 m 元搜尋樹代表各節點的子樹最多有 m 個,所以必須有 m 個鏈結欄位,相

對地,要隔出這 m 個區間,就必須有 m-1 個鍵值欄位

假設令之為 k0,k1,k2,…,km-1,則必須由小到大排列,其中,k0<k1<k2<…<km-1。

由於並非所有的節點都包含 m 個子樹,因此可以利用 1 個欄位來記錄實際存

在的鍵值數,若該欄位值令之為 n,則 1nm-1

換句話說,並非每個鍵值欄位都必須填入值,但至少 k0必須填入數值。

故節點共有(1+m+m-1)=2m 個欄位,如圖 7-28。

圖 7-28 m 元搜尋樹的節點結構

Page 115: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-115

m 元搜尋樹的搜尋原理

相對於二元搜尋樹的目標鍵值為 k,當 k<鍵值往左子樹搜尋,k>鍵值往右子樹搜

尋,若 k=鍵值,代表搜尋成功。

在 m 元搜尋樹當中,若目標鍵值為 k,則搜尋依照下列原則不斷地進行節點的比

對搜尋直到搜尋成功或失敗為止:

(1)若 k<k1,則往 L0所指的子樹搜尋

若子樹為空,則搜尋失敗。

(2)若 ki<k<ki+1,則往 Li所指的子樹搜尋

若子樹為空,則搜尋失敗。

(3)若 k>kn(1nm-1),則往 Ln所指的子樹搜尋

若子樹為空,則搜尋失敗。

(4)若 k=ki(1in) ,代表搜尋成功。

Page 116: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-116

三元搜尋樹的搜尋範例

如果我們採用的是三元搜尋樹,那代表每個節點有 3 個鏈結指向子節點或 NULL,

並有兩個以內的鍵值,1鍵值數2。

三元搜尋樹可以將子節點利用兩個鍵值 k1,k2分為(目標鍵值< k1)、(k1<目標鍵

值< k2)及(目標鍵值> k2)三個區間,以便加速找到所需要的節點。如圖 7-29

範例。

假設要尋找的節點目標鍵值為 45,則下列是搜尋過程:

(1)與節點 a 比對:(k1)20<45<80(k2),朝節點 a 的 L1所指子樹移動。

(2)與節點 b 比對:(k1)30<45<60(k2),朝節點 b 的 L1所指子樹移動。

(3)與節點 d 比對:45<50(k1),朝節點 d 的 L0所指子樹移動。

(4)與節點 e 比對:45=45(k1),搜尋成功。

假設要尋找的節點目標鍵值為 25,則下列是搜尋過程:

(1)與節點 a 比對:(k1)20<25<80(k2),朝節點 a 的 L1所指子樹移動。

(2)與節點 b 比對:25<30(k1),朝節點 b 的 L0所指子樹移動。

(3)與節點 c 比對:25>24(k2),朝節點 c 的 L1所指子樹移動,但子樹為空,所以

搜尋失敗。

Page 117: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-117

圖 7-29 三元搜尋樹範例

Page 118: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-118

m 元搜尋樹同樣有高度平衡的問題,因此後來又發明出 B 樹及其特例 2-3 樹、紅-

黑樹等等。這些特別的樹會在第 10 章中進行簡單的介紹。

Page 119: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-119

7.9 本章重點

在本章中,我們介紹了資訊領域非常重要的樹狀資料結構,包含樹、二元樹及林。

其中,最重要的為二元樹。

樹可以藉由左子右弟表示法轉化為二元樹

林也可以透過類似方法轉化為二元樹。

二元樹通常使用鏈結串列來儲存,但若為滿枝或完整二元樹,則可以考慮使用陣

列來存放,因為可以減少找到或更換節點的時間。

使用鏈結串列來儲存二元樹時,每一個節點有三個欄位,分別是指向左子節

點的鏈結欄位、資料欄位、指向右子節點的鏈結欄位。

走訪二元樹分為前序、中序、後序等三種方式,其遞迴演算法直觀而簡單,但由

於呼叫函式多次,因此時間較慢,而且需要使用系統堆疊。

即使我們將之改為疊代版本,也必須模擬堆疊的運作。

走訪二元樹的一個常見應用是二元運算樹,對二元運算樹進行走訪即可求出

運算式的前序、中序、後序表示式。

Page 120: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-120

有 [前序+中序] 或 [後序+中序] 的走訪結果,可以還原為唯一的一棵二元樹。

一般二元樹是很浪費鏈結欄位的,因為 2n 個鏈結欄位中有 n+1 個將會存放

NULL,為此,後來設計了引線二元樹。

引線二元樹將原本 NULL 的鏈結欄位設定為引線,並將之指向中序走訪的前

行者與後繼者。

如此一來,在空間使用率上得到了改善,並且走訪的時間複雜度也較佳

不過在引線二元樹中加入節點或刪除節點則比較困難。

二元樹的應用在資訊領域中時常出現,在本章最後介紹了最大累堆與二元搜尋樹。

其中,最大累堆是製作優先權佇列的不二選擇

二元搜尋樹則可以應用於符號表的製作

由於高度平衡是二元搜尋樹的重要關鍵,因此,有更多特殊的二元搜尋樹

被發明出來,例如 AVL 樹。

同樣地,為了減少樹的高度,因此,二元搜尋樹的想法也被延伸為 m 元搜尋樹,

使得不再只以二分法來進行搜尋,而是採用區間方式來搜尋。

m 元搜尋樹同樣有高度平衡的問題,因此後來又被改良為 B 樹等。

Page 121: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-121

關於高度平衡的問題屬於較高階的資料結構課題,留待本書第 10 章再討論。

Page 122: 第77 章章 樹狀結構el.fotech.edu.tw/localuser/eetuml/web1/PG31221-DOC/ch07.pdf · 樹狀結構 7-5圖7-2 樹的專有名詞 1. )節點(node :代表某項資料及其指向其它資料項的分支(branch),

樹狀結構 7-122

本章習題