Transcript Chap.5

第五章—
樹
1
5.1
緒論
專有名詞
定義:樹狀結構是一個或多個節點的有限集合,它滿足:
(1) 有一個特定的節點稱為根節點(root)。
(2) 其餘的節點分成n ≥ 0 個獨立的集合T1, …, Tn,其中的各個集合是一個樹狀
結構。我們稱T1, …, Tn為根節點的子樹( subtree )。
2
Dusty
Honey Bear
Brunhilde
Gill
Brandy
Tansey Tweed
Nugget
Coyote
Terry
Zoe
crocus
Primrose Nous
Bella
(a) 血統表
Proto Indo-European
Italic
Osco-Umbrian
Osco
Umbrian
Hellenic
Latin
Greek
Germanic
North Germanic
West Germanic
Spanish French Italian Icelandic Norwegian Swedish Low High Yiddish
(b) 族系表
3
樹狀結構:
在畫樹狀結構圖時,通常將樹的根節點畫在最上面。
一個節點的分支度( degree )為此節點的子樹個數。
樹狀結構的分支度為在此樹中最大的節點分支度。
分支度為零的節點稱為葉節點( leaf )或終端節點( terminal )。
具有子樹的節點,為其子樹的根之父節點( parent ),而子樹的根為此節點的子
節點( children )。
具有同一個父節點的節點稱為兄弟( siblings )。
一個節點的祖先( ancestors )為從根節點到此節點路徑上之所有節點。
一個節點的後代( descendants )為在他的子樹之所有節點。
4
我們以根節點位在階層1來定義一個節點的階層。對於其下的所有節點,其階
層為父節點的階層加1。
一個樹的高度( height )或深度( depth )為此樹中任一個節點最大的階層數。
Level
A
B
E
K
C
F
L
1
G
2
D
H
M
I
J
3
4
5
樹狀結構表示法
串列表示法:
 圖5.2中的樹可寫成一個串列,其中的每一子樹也是一個串列。
( A ( B ( E ( K, L),F ), C( G ), D( H( M ), I, J ) ) )
 考慮在記憶體中的樹狀結構表示法。如果要以鏈結串列表示,則一個節點會有
不同的欄位個數,是其分支的個數而定。
Data
link 1
link 2
…
link k
圖5.3:樹狀結構可能的鏈結表示法
6
左子-右弟表示法:
 每一個節點剛好需要兩個鏈結( 或指標 )欄位。
 每一個節點只有一個最左( left most )子節點,以及一個近右( closest right )兄弟節
點。
A
data
left child
right sibling
B
E
K
C
F
L
G
D
H
I
J
M
7
A
B
C
E
F
K
G
D
L
H
M
I
J
圖5.6:樹狀結構的左子-右子表示法
8
5.2
二元樹
抽象資料型態
定義:二元樹是節點的有限集合,他可能是空的,或者包含一個根節點及兩個獨
立的二元樹,分別稱為左子樹跟右子樹。
沒有樹可以是0個節點的,但可以有空的二元樹。
在二元樹中我們要區別子樹的順序,而樹則不必。
9
A
A
B
B
樹結構
B
左子-右弟樹
二元樹
A
A
B
A
C
樹結構
B
A
C
B
C
左子-右弟樹
二元樹
圖5.7:樹的表示法
10
A
A
B
B
兩個不同的二元樹
A
A
B
B
C
C
D
D
E
H
E
F
G
I
傾斜和完整的二元樹
11
二元樹之特性
公設5.1[節點的最多個數]:
1.
2.
在二元樹的階層i上最多的節點個數為2i-1 , i ≥ 1。
在深度為k的二元樹中最多的節點個數為2k – 1 , k ≥ 1。
公設5.2[葉節點個數與分支度為2的節點個數之間的關係]:
1.
•
對任何一個非空的二元樹T,如果n0為葉節點的個樹, n2為分支度為
2的節點個數,則n0 = n2 + 1 。
定義:一個深度為k的完全二元樹( full binary tree )即是深度為k且2k – 1具
個節點, k ≥ 0的二元樹。
12
定義:一個二元樹有n個節點且深度為k,若且惟若他的節點和一個深度為k的完全
二元樹中編號從1到n的節點一致時,就稱他為完整二元樹。
level
1
1
2
4
8
6
5
9
2
3
10
11
12
3
7
13
14
15
4
圖5.10:節點以循序編號,深度為4之完整二元樹
13
二元樹表示法
陣列表示法:
(我們不用陣列中的第0個位置)
公設5.3:如果一個完整二元樹有n個節點( 深度 =
節點i, 1 ≤ i ≤ n,我們可得到:
log2 n  1
)以循序法表示,則任一
(1) parent (i)位在,i≠1。如果i=1,i為根節點,且沒有父節點。
(2) left_child( i )位在2i,若2i ≤ n。如果2i > n,則i沒有左子節點。
(3) right_child(i)位在2i+1,若 2i + 1 ≤ n。如果2i + 1 > n ,則i沒有右子節點。
證明:現在假設對所有的i, 1 ≤ j ≤ i,left_child(j) 位在2j。則在left_child(i+1)之前的
兩個節點為i的右子節點和左子節點。左子節點在2i。所以i+1的左子節點2i+2=2(i+1),
除非(2i+1) > n,在此情況下i+1沒有左子節點。
14
[1]
A
[1]
A
[2]
B
[2]
B
[3]
C
[4]
D
[5]
[5]
E
[6]
[6]
F
[7]
[7]
G
[8]
H
[9]
I
[3]
[4]
[8]
C
D
[9]
[16]
E
圖5.11:圖5.9中的二元樹之陣列表示法
15
LeftChild
data
data
RightChild
Left Child
Right Child
圖5.12:二元樹的節點表示法
root
root
A null
A
B null
C
B
C null
D null
D
null E null
null H null
null E null
null F null
null G null
null I null
16
5.3
二元樹的走訪與樹的疊代函式
當搜尋一個樹狀結構時,我們以相同的走訪方式來對待每一個節點及其子樹。
假設我們接受一種慣用法,先尋訪左邊,後尋訪右邊,則只剩下三種尋訪順序:
LVR,LRV,VLR。
對這些尋訪順序我們分別命名為中序法(inorder),後序法(postorder)以及先序法
(preorder)。
我們以圖5.15中的二元樹為例。此樹狀結構代表算術運算式( 中序表示法 ):
A/B*C*D+E
17
+
*
*
/
A
E
D
C
B
圖5.15:以二元樹表示一個運算式
18
中序尋訪:
資料欄位輸出的順序為: A/B*C*D+E
先序尋訪:
資料欄位輸出的順序為: + * */A B C D E
後序尋訪:
資料欄位輸出的順序為: A B / C * D * E +
迴路式中序尋訪:
沒有動作的節點表示該節點加入堆疊中,而一個具有printf動作的節點表示他從堆
疊中刪除。注意,左子節點一直被加入堆疊中,直到遇到空節點為止,節點隨後
從堆疊中刪除。
而後將其右子節點加入堆疊中。尋訪則由左子節點繼續。當堆疊是空的時,尋訪
結束。
19
程式:迴路式中序尋訪
template <class T>
2 void Tree <T>::Inorder()
3 { // 驅動程式呼叫主程式以走訪整棵樹
4 // 驅動程式宣告為Tree的公用成員函式
5
Inorder(root);
6}
7 template <class T>
8 void Tree <T>::Inorder(TreeNode <T> *currentNode)
9 { // 主程式走訪樹根為currentNode的子樹
10 // 主程式宣告為Tree的私用成員函式
11
if (currrentNode) {
12
Inorder(currentNode→leftChild);
13
Visit(currentNode);
14
Inorder(currentNode→rightChild);
}
}
20
階序尋訪:
首先拜訪根節點,而後是根的左子節點,接著是根的右子節點。持續以這種方式
拜訪每一階層中的節點,由最左邊的節點到最右邊的節點。
階層搜尋之結果:+ * E * D / C A B
21
5.4
更多的二元樹運算
利用二元樹的定義以及遞迴版的中序,先序,和後序尋訪,我們可以很容易的產生其他的二元樹
運算之C函數。
兩個實用的運用為:
(1) 複製一個二元樹。
(2) 測試二元樹是否相等。
對等的二元樹具有相同的構造,且在對應的節點中之資訊也是相同的。
判斷命題是否成立:
考慮一組可以用變數X1,X2,…,Xn和運算符號 ^ ( and ),\/( or )與﹁( not )構成的公式。變數值只
能是true或false之一。以這些變數和運算符號所形成的運
22
算式根據下列規則來定義:
(1) 變數是一個運算式。
(2) 若x,y為運算式,則﹁x,x^y, x\/y也是運算式。
(3) 一般的計算順序為﹁在^之前,^在\/之前,括號可用來改變一般的計算順序。




x1



x2
x3
x3
x1
( x1  x2 )  (x1  x3 )  x3
O(g2n)
23
為了計算運算式之值,我們可以用後序法,對每一個子樹作計算,直到整個運算
式變成單一值。
這個問題的節點構造如圖5.19。left_child和right_child欄位與先前所採用的方法類
似。data欄儲存變數值或命題學的運算符號,而value欄儲存一個TRUE或FALSE值。
Left_Child
data
value
Right_Child
圖5.19:命題是否成立問題所用的節點構造
24
5.5
引線二元樹
要建立引線樹,採用下列的規則:
(1) 如果ptr
left_child為空的,將ptr
left_child以指向一個節點的指標取代,
此節點是中序尋訪時在ptr之前拜訪的節點。
亦即,我們將空指標替換成指向ptr的中序前行節點( inorder predecessor )之指標。
(2) 如果ptr
right_child為空的,將ptr
right_child以指向一個節點的指標取代,
此節點是中序尋訪時在ptr之後拜訪的節點。亦即,我們將空指標替換成指向ptr的
中序後續節點( inorder succeddor )之指標。
25
root
A
B
D
H
C
E
F
G
I
Inorder sequence: H, D, I, B, E, A, F, C, G
圖5.21:與圖5.9(b)對應的引線樹
26
當我們將樹狀結構儲存在記憶體時,必須能夠區分引線和一般指標。這可以在節
點構造中在加入兩個欄位來達成,即left_tread和right_tread。
如果ptr
left_tread = TRUE,則ptr
含指向左子節點的指標。
如果right
right_tread = TRUE,則ptr
它包含指向右子節點的指標。
left_child包含一個引線。否則它包
right_child包含一個引線。否則
假設每一個引線二元樹都有一個標頭節點。這表示空的引線樹永遠包含一個標頭
節點,如圖5.22。
LeftThread
TRUE
LeftChild
data
RightChild
RightThread
FALSE
27
f
B
f
f
t
H
t
D
f
I
E
t
-
f
f
B
f
f
f
t
t
A
f
f
D
f
t
E
t
t
f = FALSE
t = TURE
圖5.23:引線樹的記憶體表示法
28
插入一個節點到引線二元樹:
 如果有一個節點parent 其右子樹是空的。我們希望將child插入,當成parent的右
子節點。必須:
(1)
(2)
(3)
(4)
(5)
改變parent
設定child
設定child
設定child
改變parent
right_thread為FALSE
left_thread和child
left_child指向parent。
left_child指向parent
right_child指向child
right_thread為TRUE
right_child
 如果parent的右子樹不是空的,插入變的困難些,因為parent的右子樹在插入以
後變成child的右子樹。而後child變成原來是parent 的中序後續者的節點之中序前
行節點。
在此情況下,我們要在節點B和D之間插入節點X。
29
root
root
A
A
B
parent
B
parent
C
D
D
C
child
child
插入後
插入前
(a)
30
root
root
A
parent
A
B
C
parent
B
D
E
child
C
child
X
F
D
X
E
F
插入後
插入前
(b)
31
程式:在引線二元樹中插入右子節點
template <class T>
void ThreadedTree <T>::InsertRight (ThreadedNode <T> *s,
*r)
{// 插入 r 成為 s的右兒子
ThreadedNode <T>
r → rightChild = s → rightChild;
r → rightThread = s → rightThread;
r → leftChild = s;
r → leftThread = True; // leftChild 是引線
s → rightChild = r;
s → rightThread = false;
if (! r → rightThread) {
ThreadedNode <T> *temp = InorderSucc (r);
// 回傳r的中序後繼者
temp → leftChild = r;
}
}
32
5.6
堆積
堆積(累堆)抽象資料型態
定義:最大樹( max tree )是一種樹,其中每一節點的鍵值不小於他的子節點的( 如
果有存在的話 )鍵值.最大累堆( max heap )為一種也是最大樹的完整二元樹。
定義:最小樹( min tree )是一種樹,其中每一節點的鍵值不大於他的子節點的鍵值
( 如果有存在的話 ) .最小累堆( min heap )為一種也是最小樹的完整二元樹。
33
[1]
[1] 2
[2]
[4]
10
[3]
7
4
[2]
20
[1]
10
[3]
83
[2]
11
21
[6]
[5 ]
8
6
[4]
50
圖5.25:最小累堆範例
34
[1]
[ 1 ] 14
[2]
[4]
10
[3]
12
7
[2]
6
[1]
9
[3]
3
[2]
30
25
[6]
[5 ]
8
6
[4]
5
圖5.25:最大累堆範例
35
優先佇列
優先佇列可以刪除具有最高(或最低)優先權的元素。在任何時候我們可以加入具有
任意優先權的元素到優先佇列中。
如果我們的應用需求是刪除具有最高優先權的元素,則使用最大累堆。
假設我們的作業系統的排程使用一種優先權系統,給管理人員最高優先權,學生最低
優先權。我們可以使用最大累堆來製作儲存儲存工作的優先佇列。
如果我們的應用需求是刪除具有最低優先權的元素,則使用極小累堆。
假設我們的作業系統的工作排程根據預期的執行時間,給予最短工作較大的優先權。
36
圖5.27:優先權佇列表示法
表示法
插入時間
刪除時間
未排序陣列
(1)
(n)
未排序鏈結串列
(1)
(n)
已排序陣列
O(n)
(1)
已排序鏈結串列
O(n)
(1)
最大累堆
O(log2 n)
O(log2 n)
插入一個節點到最大累堆
要實作我們所說的插入策略,我們必須由一個元素走到他的父節點。可以採用鏈結表
示法,然而因為累堆是一個完整二元樹,因此我們可以採用陣列表示法。
37
[1]
[1]
20
[2]
[2]
15
[ 4 ] 14
[3]
10
[5]
[4]
14
[5]
10
[6]
2
1
(b) 新節點起始位置
[1]
[ 1 ] 20
[ 4 ] 14
[3]
15
2
(a) 插入前的累堆
[2]
15
20
21
[2]
[ 5 ] 10 [ 6 ] 2
(c) 在累堆(a)插入5
[3]
15
[3] 5
[4]
14
[5]
10
[6]
20
2
(c) 在累堆(a)插入21
38
5.6.4
從最大累堆刪除節點
20
removed
[1]
[1]
[2]
10
[2]
15
2
[3]
[ 4 ] 14
[5]
15
[3]
2
[ 4 ] 14
(a) 累堆結構
[1]
(b) 在根節點插入10
15
[2]
14
[3]
2
[ 4 ] 10
(c) 形成的累堆
39
分析delete_max_heap:因為具有n個元素的累堆高度為 log2 (n  1) , delete_max_heap中的
while迴圈重複 O(log n) 次。所以,刪除的複雜度為 O(log n)
2
2
程式:從最大累堆中刪除節點
Template <class T>
void MaxHeap<T>::Pop()
{ // 移除最大元素
if (IsEmpty ()) throw “Heap is empty. Cannot delete.”;
heap[1].
T(); // 移除最大元素
// 從堆積中移除最後一個元素
T lastE = heap{heapSize−−};
// 滲透法
int currentNode = 1; // 樹根
int child = 2;
// currentNode的一個兒子節點
40
while (child <= heapSize)
{
// 設定child為currentNode的兒子中比較大的一個
if (child < heapSize && heap[child] < heap[child + 1]) child++;
// 可以將lastE放入currentNode裡嗎?
if (lastE >= heap[child]) break; // 可以
// 不可以
heap[currentNode] = heap[child]; // 子節點上移
currentNode = child; child *= 2; // 下移一層
}
heap[currentNode] = lastE;
}
41
5.7
二元搜尋樹
簡介
累堆不適合於必須刪除任何元素的應用。因為從有n個元素的累堆中刪除任何一個元
素需要O(n)時間。
定義:二元搜尋樹(binary search tree)是一種二元樹。它可能是空的。如果不是空的具
有下列特性:
(1) 每一個元素有一鑑值,而每一元素鑑值都不一樣。
(2) 在非空的左子樹上的鑑值必小於在該子樹的根節點中的鑑值。
(3) 在非空的右子樹上的鑑值必大於在該子樹的根節點中的鑑值。
(4) 左子樹和右子樹都是二元搜尋樹。
42
30
20
15
14
5
25
10
22
(a)
40
2
(b)
60
70
80
65
(c)
43
搜尋一個二元樹
如果root是NULL,則搜尋樹是空的,且找尋是失敗的。
如果root不是NULL,則比較key和root:
 如果key和root的鑑值相等,則找尋成功且結束。
 如果key小於root的鑑值,則右子樹沒有任何一個節點的鑑值可能等於key。所以
找尋root的左子樹。
 如果key大於root的鑑值,則找尋root的右子樹。
分析search和search2:
如果二元搜尋數的高度為h,使用search或search2都可以在O(h)時間內完成搜尋。
但是search需要額外O(h)堆疊空間。
44
程式:二元搜尋樹的迴路式找尋
template <class K, class E> // 驅動程式
pair<K, E>* BST<K, E> :: Get(const K& k)
{ // 在二元搜尋樹 (*this) 裡搜尋鍵值為k的資料對
// 如果找到就回傳指向該資料對的指標;否則,回傳0.
return Get(root, k);
}
template <class K, class E> // 主程式
pair<K, E>* BST<K, E> :: Get(TreeNode <pair <K, E> >* p, const K& k)
{
if (!p) return 0;
if (k < p→data.first) return Get(p→leftChild, k);
if (k > p→data.first) return Get(p→rightChild, k);
return &p→data;
}
45
在二元搜尋樹中插入元素
要插入一個新的元素key,首先必須檢查此鍵職和現有元素的鑑值是不同的。
為了驗證這一點,我們必須找尋此樹。如果找尋不成功,我們可以在找尋結束點
插入此元素。
30
30
5
2
80
(a) 插入80
40
5
40
2
35
80
(a) 插入35
46
從二元搜尋樹中刪除元素
要刪除葉節點,只要將他的父節點之左節點欄位設定為NULL並釋回此節點即可。
 刪除只有一個子節點的內部節點:
 將節點刪除,而後將他的單一節點放在被刪除的節點之位置上即可。
 刪除具有兩個子節點的內部節點:
 以被刪除的節點之左子樹中最大的元素來取代他的位置。
 以被刪除的節點之右子樹中最小的元素來取代他的位置。
 可用左子樹中最大的元素,或用右子樹中最小的元素來取代。
 刪除可以在O(h)時間內完成,其中h是樹的高度。
47
30
5
80
2
圖5.32:從二元搜尋樹中刪除元素
40
40
20
10
60
30
45
20
70
50
55
52
(a) 刪除60前的樹
10
55
30
45
70
50
52
(b) 刪除60後的樹
48
5.8
選擇樹(selection tree)
令n為k個行程中所有的紀錄之各數。
要合併k個行程最直接的方法是經過k-1次的比較來決定下一個要輸出的紀錄。當
k>2,利用選取樹的觀念,可以減少找到下一個最小的元素所需要的比較次數。
選取樹(selection tree)是二元樹,其中每一個節點代表他的兩個子節點中的較小者。
所以,根節點代表樹結構中最小的節點。
49
1
6
2
6
8
4
9
8
6
10
9
15
16
run1
9
10
5
6
20
6
20
38
20
30
run2
run3
11
12
3
8
17
8
9
15
25
28
15
50
run4
run5
13
14
7
15
90
17
11
16
95
99
18
20
run6
run7
run8
圖5.34:k=8時的選取樹,它顯示八個行程中各個行程的前三個鍵值
50
1
8
2
9
8
4
9
8
15
10
9
15
16
run1
9
10
5
6
11
20
15
20
38
20
30
run2
run3
12
3
8
17
8
9
25
28
15
50
run4
run5
13
14
7
15
90
17
11
16
95
99
18
20
run6
run7
run8
圖5.35:圖5.34中的選取樹在輸出一個紀錄後重整的結果
51
樹結構的階層個樹為 log2 k   1 。所以重整樹結構需時 O(log2 k ) 。每一次有一個
紀錄合併到輸出檔時就必須重整樹結構。所以合併所有n個記錄所需的時間
為 O(n log2 k ) 。
第一次建立選取樹所需的時間為O(k)。因此,合併k個行程所需的全部時間為
O(n log2 k )
0
。
1
2
17
10
run
20
10
9
1
2
8
9
4
9
Overall
winner
6
10
5
6
20
6
3
4
11
12
3
9
90
8
9
5
6
13
14
7
15
90
17
7
8
52
5.9
樹林
定義:樹林( forest )是一組n個,n≥0的不同的樹之集合。
樹林的觀念和樹的觀念很接近,因為,我們若將一樹的根節點刪除,即可形成樹林。
將樹林轉換成二元搜尋樹
定義:如果T1, …, Tn為樹林,則此樹林對應的二元樹以B(T1, …, Tn)表示,則它將是:
(1) 空的,若n=0。
53
(2) 根節點與root( T1 )相同;左子樹與B(T11, T12,…, T1m),相同,其中T11, T12,…, T1m
為root (T1)的子樹;右子樹為B(T2, …, Tn)。
樹林尋訪
一樹林F對應的二元樹T之先序,中序和後序尋訪,與F的先序,中序和後序尋
訪是一致的。
54
5.10
集合表示法
樹狀結構也可以用來表示集合。
假設所要表示的任意兩個集合是互斥的( disjoint ),在這些集合上我們希望進行的
最少的運算有:
 Disjoint Set Union。如果Si 和 Sj為兩個互斥的集合,則其聯集Si ∪Sj = {
在集合 Si 或 Sj中的元素 }。
x ,x為
 Find ( i )。找尋包含元素i的集合。
55
0
6
7
S1
2
4
8
1
9
S2
3
5
S3
圖5.39:集合可能的樹林表示法
56
4
0
6
7
4
8
1
S1 ∪ S2
1
0
9
6
or
7
9
8
S2 ∪ S1
圖5.40: S1 ∪ S2 可能的表示法
57
要實作集合的連集運算,我們只要將一個根節點的parent欄位設定成另一個根節
點即可。
如果每一個根節點有一個指向集合名稱的指標,則根據parent鏈結,我們可以由
一個元素找到其根節點,再傳回指向集合名稱的指標,就可以找到原宿在哪一個
集合中。
Set
Nam
e
S1
S2
Pointe
r
0
6
7
2
4
8
1
9
3
5
S3
圖5.41:S1, S2, S3 的資料表示法
58
如果i是根節點為j的樹中的一個元素,而j有一個指標指向集合名稱表格中第k項,
則集合名稱即為name[ k ]。
因為樹中節點的編號從0到n-1,所以我們可以利用節點的編號作索引。
i
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
parent
-1
4
-1
2
-1
2
0
0
0
4
圖5.42:S1, S2, S3 的陣列表示法
分析:如果Si = {i},0 <= i <p,則一開始的架構是具有p個節點的樹林,且parent[i] =
-1, 0 <= i <p。接下來的運算:union(0, 1), union(1, 2), …, union(n-2, n-1)
find(0),
find (1), …, find(n-1)。
59
這個順序會產生退化樹。因為union所需的時間是常數,全部的n-1個union運算都可
以在O(n)時間內完成。
Union operation
O(n)
n-1
n-1
union(0, 1), find(0)
n-2
union(1, 2), find(0)
Find operation
union(n-2, n-1), find(0)
O(n2)
0
圖5.43:退化樹
60
加權法則:
定義:Weighting rule for union ( i, j )。如果樹狀結構i中的節點個數少於樹狀結構
j中的節點個數,則令j為i的父節點;否則,令i為j的父節點。
0
1
n-1
0
2
n-1
0
1
1
initial
1
0
4
2
3
3
2
union (0,2)
0 = find(0)
union (0,1)
0 = find(0)
0
n-1
union (0,3)…
0 = find(0)
n-1
1
2
n-1
3
union (0,n-1)
61
公設5.4:令T為union2所產生的樹狀結構,其中有n個節點。則T沒有任何一個
節點的階層大於
n  1
log 。
2
此公設假設對具有i個節點,i<=n-1,的所有樹狀結構均成立。
[-1]
[-1]
[-1]
[-1]
[-1]
[-1]
[-1]
[-1]
階層
運算
0
1
2
3
4
5
6
7
1
Initial
[-2]
[-2]
[-2]
[-2]
0
2
4
6
1
union (0, 1)
union (2, 3)
union (4, 5)
1
3
5
7
2
union (6, 7)
62
[-4]
[-4]
0
4
5
2
1
1
3
6
2
7
3
union (0, 2)
union (4, 6)
[-8]
1
0
1
2
3
2
4
5
union (0, 4)
6
3
7
4
63
定義[ collapsing rule ]:如果j是從i到其根節點的路徑上的一個節點,則令j為根節
點的一個子節點。
程式5.20在find運算中加入了折疊法則。新的函數對於個別的find運算大約用了加倍的
時間。但是,它可以降低一連串的find運算之最差狀況時間。
公設[ Tarjan ]:令T( m, n )為混合執行一連串m>=n個find與n-1個union所需之最大時間。
則:
k1(n + fα(f + n, n)) ≤ T(f, u) ≤ k2(n + fα(f + n, n))
其中k1和k2為大於0的常數值。
等價類別
64
我們可視將要產生的等價類別( equivalence classes )為集合。這些集合是互斥的。
一開始,所有的n個多邊形自成一個等價類別;即parent[i] = -1,0 ≦ i <
n。




在處理一個等價的序對i≡j之前,我們必須先確定包含了i和j的集合。
如果它們是不同的,則以它們的聯集來取代這兩個集合。
如果兩個集合是相同的,則不需做任何處理,因為關係i≡j是多餘的。
因此我們必須執行兩個find以及至多一個union。
如果有n個多邊形以及m≧n個等價序對,全部的處理時間最多為O( m
n ) )。
( 2m,

65
[-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1]
0
1
2
3
4
5
6
7
8
9
10
11
[-1] [-2]
[-2]
0
2
4
[-1] [-2]
5
3
10
8
11
[-2]
[-3]
3
8
Initial
0≡4
3≡1
6≡10
8≡9
7≡4
6≡8
3≡5
2≡11
2
11
5
1
[-1]
9
10
[-4]
6
7
4
7
6
1
[-3]
0
[-1] [-2]
INPUT
9
[-3]
0
4
7
[-3]
3
[-4]
6
2
11
10
8
9
1
11≡0
5
66
5.11
計算二元樹的數目
堆疊排列
我們曾經介紹先序,中序和後序尋訪,並指出每一種尋訪需要一個堆疊。假設有
一個二元樹的先序順序如下:
ABCDEFGHI
是否這一組順序可以唯一決定一個二元樹?
67
A
A
B, C
B
D, E, F, G, H, I
D, E, F, G, H, I
C
(a)
(b)
A
B
D
C
F
E
I
G
(c)
H
圖5.49:以中序和先序建立二元樹
68
1
2
4
3
6
5
9
7
8
圖5.50:圖5.49( c )中的二元樹,其節點加上編號
69
2
2
3
(1, 2, 3)
1
1
1
1
2
3
(1, 3, 2)
2
2
3
3
(2, 1, 3)
1
(2, 3, 1)
3
(3, 2, 1)
圖5.51:對於五個不同排列的二元樹
矩陣乘法
另一個問題是n個矩陣的乘積。因為矩陣乘法具有結合性,我們可用任何順序來執行
這些乘法。
70
例如,若n=3,則有兩種可能:
(M1 * M2) * M3
M1 * (M2 * M3 )
若n=4,則有五種可能:
((M1 * M2) * M3) * M4
(M1 * (M2 * M3)) * M4
M1 * ((M2 * M3) * M4 )
(M1 * (M2 * (M3 * M4 )))
((M1 * M2) * (M3 * M4 ))
n 1
bn   bi bn 1 , n  1
i 1
令b為計算n個矩陣乘積的不同方法之個數。則b
2 = 1, b3 = 2, and b4 = 5。
71
bn是由如下的法所產生的所有可能的二元樹之數目之加總:一個根節點與兩
個具有bi 和bn+i 節點的子樹,0 ≦ i < n。
bn   bibni 1 , n  1 , and b0  1
bn
bi
bn-i-1
• 要得知n個節點所產生的不同的二元樹之數目,必須解出遞迴方程式(5.3)。
首先我們令:
B( x)   bi x i
i 0
72
其次藉由觀察遞迴關係式,可得到下列公式:
xB2 ( x)  B( x) 1
1  1  4x
B( x) 
2x

1 / 2 
 1/ 2 
1 
n


(4 x)    
(1) m 22 m1 x m
B( x)  1   
2 x  n0  n 
 m0  m  1
1  2n 
 
bn 
n 1  n 
 bn  O(4n / n3 / 2 )
73