轉自:http://blog.chinaunix.net/uid-26548237-id-3476141.html
1、二叉樹的深度遍歷
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹的所有結點,使得每個結點被訪問一次且僅被訪問一次。
對於二叉樹的深度遍歷,有前序遍歷二叉樹、中序遍歷二叉樹、後序遍歷二叉樹三種形式,下面分別進行學習和介紹。
1.1 二叉樹的前序遍歷
1)前序遞歸遍歷
規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。如下圖所示,遍歷的順序爲ABDGHCEIF。
前序遞歸遍歷的代碼實現,如下所示。
1
2
3
4
5
6
7
8
9
10
|
//前序遞歸遍歷 void
PreOrderTraverse(BiTree t) { if (t
!= NULL) { printf ( "%c
" ,
t->data); PreOrderTraverse(t->lchild); PreOrderTraverse(t->rchild); } } |
2)前序非遞歸遍歷
根據前序遍歷訪問的順序,優先訪問根結點,然後再分別訪問左孩子和右孩子。即對任一結點,其可看做是根結點,因此可以直接訪問,訪問完之後,若其左孩子不爲空,按相同的規則訪問它的左子樹;當訪問其左子樹時,再訪問它的右子樹,因此其處理過程如下:
對於任一結點p:
a. 訪問結點p,並將結點p入棧;
b. 判斷結點p的左孩子是否爲空,若爲空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置爲當前的結點p,循環置a;若不爲空,則將p的左孩子置爲當前結點p;
c. 直到p爲空,並且棧爲空,則遍歷結束。
前序非遞歸遍歷代碼實現,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//前序非遞歸遍歷 int
NoPreOrderTraverse(BiTree t) { SqStack
s; InitStack(&s); BiTree
tmp = t; if (tmp
== NULL) { fprintf (stdout,
"the
tree is null.\n" ); return
ERROR; } while ((tmp
!= NULL) || (IsEmpty(&s) != 1)) { while (tmp
!= NULL) { Push(&s,
tmp); printf ( "%c
" ,
tmp->data); tmp
= tmp->lchild; } if (IsEmpty(&s)
!= 1) { Pop(&s,
&tmp); tmp
= tmp->rchild; } } return
OK; } |
1.2 中序遍歷二叉樹
1)中序遞歸遍歷
規則是若樹爲空,則空操作返回,否則從根結點開始(注意這裏並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。如下圖所示,遍歷的順序爲:GDHBAEICF。
中序遞歸遍歷代碼實現如下所示。
1
2
3
4
5
6
7
8
9
10
|
//中序遞歸遍歷 void
InOrderTraverse(BiTree t) { if (t
!= NULL) { InOrderTraverse(t->lchild); printf ( "%c
" ,
t->data); InOrderTraverse(t->rchild); } } |
2)中序非遞歸遍歷
根據中序遍歷的順序,對於任一結點,優先訪問其左孩子,而左孩子結點又可以看做一個根結點,然後繼續訪問其左孩子結點,直到遇到左孩子結點爲空的結點才停止訪問,然後按相同的規則訪問其右子樹。其處理過程如下:
對於任一結點:
a. 若其左孩子不爲空,則將p入棧,並將p的左孩子設置爲當前的p,然後對當前結點再進行相同的操作;
b. 若其左孩子爲空,則取棧頂元素並進行出棧操作,訪問該棧頂結點,然後將當前的p置爲棧頂結點的右孩子;
c. 直到p爲空並且棧爲空,則遍歷結束。
中序非遞歸遍歷代碼實現如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//中序非遞歸遍歷二叉樹 int
NoInOrderTraverse(BiTree t) { SqStack
s; InitStack(&s); BiTree
tmp = t; if (tmp
== NULL) { fprintf (stderr,
"the
tree is null.\n" ); return
ERROR; } while (tmp
!= NULL || (IsEmpty(&s) != 1)) { while (tmp
!= NULL) { Push(&s,
tmp); tmp
= tmp->lchild; } if (IsEmpty(&s)
!= 1) { Pop(&s,
&tmp); printf ( "%c
" ,
tmp->data); tmp
= tmp->rchild; } } return
OK; } |
1.3 後序遍歷二叉樹
1)後序遞歸遍歷
規則是若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。遍歷的順序爲:GHDBIEFCA。
後序遞歸遍歷代碼實現,如下所示。
1
2
3
4
5
6
7
8
9
10
|
//後序遞歸遍歷 void
PostOrderTraverse(BiTree t) { if (t
!= NULL) { PostOrderTraverse(t->lchild); PostOrderTraverse(t->rchild); printf ( "%c
" ,
t->data); } } |
2)後序非遞歸遍歷
後序遍歷的非遞歸實現是三種遍歷方式中最難的一種。因爲在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子之前訪問才能訪問根結點,這就爲流程控制帶來了難題。下面介紹一種思路。
要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點p,先將其入棧。若p不存在左孩子和右孩子,則可以直接訪問它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已經被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將p的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子之前別訪問,左孩子和右孩子都在根結點前面被訪問。
後序非遞歸遍歷代碼實現,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
//後序非遞歸遍歷二叉樹 int
NoPostOrderTraverse(BiTree t) { SqStack
s; InitStack(&s); BiTree
cur; //當前結點
BiTree
pre = NULL; //前一次訪問的結點 BiTree
tmp; if (t
== NULL) { fprintf (stderr,
"the
tree is null.\n" ); return
ERROR; } Push(&s,
t); while (IsEmpty(&s)
!= 1) { GetTop(&s,
&cur); // if ((cur->lchild
== NULL && cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild))) { printf ( "%c
" ,
cur->data); //如果當前結點沒有孩子結點或者孩子結點都已被訪問過 Pop(&s,
&tmp); pre
= cur; } else { if (cur->rchild
!= NULL) { Push(&s,
cur->rchild); } if (cur->lchild
!= NULL) { Push(&s,
cur->lchild); } } } return
OK; } |
2、二叉樹的廣度遍歷
廣度遍歷二叉樹(即層次遍歷)是用隊列來實現的,從二叉樹的第一層(根結點)開始,自上而下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。如下圖所示,遍歷的順序爲:ABCDEFGHI。
按照從根結點到葉結點、從左子樹到右子樹的次序訪問二叉樹的結點,具體思路如下:
A. 初始化一個隊列,並把根結點入隊列;
B. 當隊列爲非空時,循環執行步驟3到步驟5,否則執行步驟6;
C. 出隊列取得一個結點,訪問該結點;
D. 若該結點的左子樹爲非空,則將該結點的左子樹入隊列;
E. 若該結點的右子樹爲非空,則將該結點的右子樹入隊列;
F. 結束。
廣度遍歷二叉樹代碼實現,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//層次遍歷二叉樹
- 廣度遍歷二叉樹 - 隊列 int
TraverseBiTree(BiTree t) { LinkQueue
q; InitQueue(&q); BiTree
tmp = t; if (tmp
== NULL) { fprintf (stderr,
"the
tree is null.\n" ); return
ERROR; } InsertQueue(&q,
tmp); while (QueueIsEmpty(&q)
!= OK) { DeQueue(&q,
&tmp); printf ( "%c
" ,
tmp->data); if (tmp->lchild
!= NULL) { InsertQueue(&q,
tmp->lchild); } if (tmp->rchild
!= NULL) { InsertQueue(&q,
tmp->rchild); } } return
OK; } |
3、二叉樹的建立
如果要在內存中建立一個如下左圖這樣的樹,wield能讓每個結點確認是否有左右孩子,我們對它進行擴展,變成如下右圖的樣子,也就是將二叉樹中的每個結點的空指針引出一個虛結點,其值爲一個特定值,比如”#”,稱之爲擴展二叉樹。擴展二叉樹就可以做到一個遍歷序列確定一棵二叉樹了。如前序遍歷序列爲AB#D##C##。
有了這樣的準備,就可以看看如何生成一棵二叉樹了。假設二叉樹的結點均爲一個字符,把剛纔前序遍歷序列AB#D##C##用鍵盤挨個輸入,實現的算法如下所示。
二叉樹建立實現代碼一,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//創建樹 //按先後次序輸入二叉樹中結點的值(一個字符),#表示空樹 //構造二叉鏈表表示的二叉樹 BiTree
CreateTree(BiTree t) { char
ch; scanf ( "%c" ,
&ch); if (ch
== '#' ) { t
= NULL; } else { t
= (BitNode *) malloc ( sizeof (BitNode)); if (t
== NULL) { fprintf (stderr,
"malloc()
error in CreateTree.\n" ); return ; } t->data
= ch; //生成根結點 t->lchild
= CreateTree(t->lchild); //構造左子樹 t->rchild
= CreateTree(t->rchild); //構造右子樹 } return
t; } |
二叉樹建立實現代碼二,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//創建樹方法二 int
CreateTree2(BiTree *t) { char
ch; scanf ( "%c" ,
&ch); if (ch
== '#' ) { (*t)
= NULL; } else { (*t)
= (BiTree) malloc ( sizeof (BitNode)); if ((*t)
== NULL) { fprintf (stderr,
"malloc()
error in CreateTree2.\n" ); return
ERROR; } (*t)->data
= ch; CreateTree2(&((*t)->lchild)); CreateTree2(&((*t)->rchild)); } return
OK; } |
其實建立二叉樹,也是利用了遞歸的原理。只不過在原來應該打印結點的地方,改成生成結點、給結點賦值的操作而已。因此,完全可以用中序或後序遍歷的方式實現二叉樹的建立,只不過代碼裏生成結點和構造左右子樹的代碼順序交互一下即可。
4、二叉樹的深度
樹中結點的最大層次稱爲樹的深度。對於二叉樹,求解樹的深度用以下兩種方法實現。即非遞歸和遞歸的方法實現。
遞歸求解二叉樹的深度實現代碼,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//二叉樹的深度
- 遞歸 //返回值:
二叉樹的深度 int
BiTreeDeep(BiTree t) { int
dept = 0; if (t) { int
lchilddept = BiTreeDeep(t->lchild); int
rchilddept = BiTreeDeep(t->rchild); dept
= lchilddept >= rchilddept ? (lchilddept + 1) : (rchilddept + 1); } return
dept; } |
對於非遞歸求解二叉樹的深度,這裏採用了層次遍歷的原理,通過層次遍歷,找到二叉樹的最後一個結點。然後,根據該結點,尋找其雙親結點,即找到其上一層,此時深度dept加1,依次進行,直到根結點爲止。
非遞歸求解二叉樹深度的實現,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
//返回二叉樹的深度
- 非遞歸 - 受層次遍歷二叉樹的影響 //返回值:
二叉樹的深度 int
NoBiTreeDeep(BiTree t) { LinkQueue
q; InitQueue(&q); BiTree
tmp = t; if (tmp
== NULL) { return
ERROR; } InsertQueue(&q,
tmp); while (QueueIsEmpty(&q)
!= OK) { DeQueue(&q,
&tmp); //printf("%c
", tmp->data); if (tmp->lchild
!= NULL) { InsertQueue(&q,
tmp->lchild); } if (tmp->rchild
!= NULL) { InsertQueue(&q,
tmp->rchild); } } int
deep = 0; BiTree
m = tmp; BiTree
n = t; while (m
!= n) { InsertQueue(&q,
n); while (QueueIsEmpty(&q)
!= OK) { DeQueue(&q,
&tmp); if (m
== tmp->lchild || m == tmp->rchild) { deep++; m
= tmp; break ; } if (tmp->lchild
!= NULL) { InsertQueue(&q,
tmp->lchild); } if (tmp->rchild
!= NULL) { InsertQueue(&q,
tmp->rchild); } } } return
deep + 1; //深度從1開始 } |
5、參考引用
1)《大話數據結構》
2)嚴蔚敏老師之《數據結構》
3)http://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html
4)http://blog.chinaunix.net/uid-20788636-id-1841329.html