二叉樹
定義
- 一個有窮的結點集合
- 這個集合可以爲空
- 若不爲空,則它是由根結點和稱爲其左子樹
TL 和右子樹TR 的兩個不相交的二叉樹組成。 - 二叉樹具體五種基本形態
- 二叉樹的子樹有左右順序之分
特殊二叉樹
斜二叉樹(Skewed Binary Tree)
滿二叉樹(Full Binary Tree)
又稱完美二叉樹(Perfect Binary Tree)
完全二叉樹(Complete Binary Tree)
有
非完全二叉樹
二叉樹的性質
- 一個二叉樹第
i 層的最大結點數爲:2i−1 ,i≥1 - 深度爲
k 的二叉樹有最大結點總數爲:2k−1 ,k≥1 對任何非空二叉樹T,若
n0 表示葉結點的個數、n1 是度爲1的非葉結點個數、n2 是度爲2的非葉結點個數,那麼兩者滿足關係n0=n2+1
例:n0=4 ,n1=2 n2=3 - 所以可以推出
n0=n2+1
抽象數據類型定義
- 類型名稱:二叉樹
- 數據對象集:一個有窮的結點集合。若不爲空,則由根結點和其左右二叉子樹組成
- 操作集:
BT∈BinTree ,Item∈ElementType ,重要的操作有:
Boolean IsEmpty(BinTree BT)
:判斷BT是否爲空BinTree CreatBinTree()
:創建一個二叉樹void Traversal(BinTree BT)
:遍歷,按某順序訪問每個結點
void PreOrderTraversal(BinTree BT)
:先序遍歷—根、左子樹、右子樹void InOrderTraversal(BinTree BT)
:中序遍歷—左子樹、根、右子樹void PostOrderTraversal(BinTree BT)
:後序遍歷—左子樹、右子樹、根void LevelOrderTraversal(BinTree BT)
:層序遍歷—從上到下、從左到右
順序存儲結構
完全二叉樹
按從上至下、從左到右順序存儲
* 非根結點(序號
* 結點(序號爲
* 結點(序號爲
例
結點 | 序號 |
---|---|
A | 1 |
B | 2 |
O | 3 |
C | 4 |
S | 5 |
M | 6 |
Q | 7 |
W | 8 |
K | 9 |
一般二叉樹
一般二叉樹也可以採用這種結果,但會造成空間浪費,不推薦使用
例
結點 | 序號 |
---|---|
A | 1 |
B | 2 |
O | 3 |
NULL | 4 |
NULL | 5 |
M | 6 |
NULL | 7 |
NULL | 8 |
NULL | 9 |
NULL | 10 |
NULL | 11 |
NULL | 12 |
C | 13 |
鏈式存儲
結構定義
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode {
ElementType Data;
BinTree Left;
BinTree Right;
}
二叉樹的遞歸遍歷
先序遍歷
遍歷過程:
1. 訪問根結點
2. 先序遍歷其左子樹
3. 先序遍歷其右子樹
實現
void PreOrderTraversal(BinTree BT) {
if (NULL != BT) {
printf("%d ", BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
例
遍歷結果:A B D F E C G H I
在整個遍歷過程中,根結點在子樹遍歷時總是第一個輸出的
中序遍歷
遍歷過程:
1. 中序遍歷其左子樹
2. 訪問根結點
3. 中序遍歷其右子樹
實現
void InOrderTraversal(BinTree BT) {
if (NULL != BT) {
InOrderTraversal(BT->Left);
printf("%d ", BT->Data);
InOrderTraversal(BT->Right);
}
}
例
遍歷結果:D B E F A G H C I
中序遍歷時,根結點的左子樹輸出在根結點之前,右子樹輸出在根結點之後
後序遍歷
遍歷過程:
1. 後序遍歷其左子樹
2. 後續遍歷其右子樹
3. 訪問根結點
實現
void PostOrderTraversal(BinTree BT) {
if (NULL != BT) {
PostOrderTraversal(BT->Left);
PostOrderTraversal(BT->Right);
printf("%d ", BT->Data);
}
}
例
遍歷結果:D E F B H G I C A
在整個遍歷過程中,根結點在子樹遍歷時總是最後一個輸出的
總結
- 先序、中序和後序遍歷過程中經過結點的路線一樣,只是訪問各結點的時機不同
- 圖中在從入口到出口的曲線上用
⊗ 、⋆ 和△ 三種符號分別標記出了先序、中序和後序訪問各結點的時刻
二叉樹的非遞歸遍歷
非遞歸算法實現的基本思路:使用棧
中序遍歷
- 遇到一個結點,就把它壓棧
- 當左子樹遍歷結束後,從棧頂彈出這個結點並訪問它
- 然後按其右指針再去中序遍歷該結點的右子樹
實現
void InOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 創建並初始化棧S
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左並將沿途結點壓入棧中
Push(S, T);
T = T->Left;
}
T = Pop(S); // 結點彈出棧
printf("%d ", T->Data); // 訪問結點
T = T->Right; // 轉向右子樹
}
}
先序遍歷
- 遇到一個結點,先訪問結點,然後把它壓棧
- 當左子樹遍歷結束後,從棧頂彈出這個結點但不訪問
- 然後按其右指針再去前序遍歷該結點的右子樹
實現
void PreOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 創建並初始化棧S
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左並將沿途結點壓入棧中
printf("%d ", T->Data); // 訪問結點
Push(S, T);
T = T->Left;
}
T = Pop(S); // 結點彈出棧
T = T->Right; // 轉向右子樹
}
}
後序遍歷
- 後序遍歷思路與前序遍歷相似,不過是按照根、右子樹、左子樹的方式遍歷以後逆序輸出
- 使用兩個棧來進行,棧1保存訪問的結果,棧2進行遍歷
- 遇到一個結點,先把這個結點壓入棧1,然後同時壓入棧2
- 當右子樹遍歷結束後,從棧2中彈出這個結點但不訪問
- 然後按照其左指針再去遍歷該結點的左子樹
- 遍歷結束以後,輸出棧1的值,即爲後序遍歷的結果
實現
void PostOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 創建並初始化棧S
Stack Resut_S = CreatStack(MaxSize); // 保存訪問結果的棧
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左並將沿途結點壓入棧中
Push(Resut_S, T); // 保存訪問結果
Push(S, T);
T = T->Right;
}
T = Pop(S); // 結點彈出棧
T = T->Left; // 轉向左子樹
}
// 逆序輸出
while (!IsEmpty(Resut_S)) {
T = Pop(Resut_S); // 結點彈出棧
printf("%d ", T->Data); // 訪問結點
}
}
層序遍歷
使用隊列來實現,層序基本過程:先根結點入隊,然後:
1. 從隊列中取出一個元素
2. 訪問該元素所指結點
3. 若該元素所指結點的左右孩子結點非空,則將其左右孩子的指針順序入隊列
實現
void LevelOrderTraversal(BinTree BT) {
Queue Q;
BinTree T;
if (NULL == BT) // 如果是空樹則直接返回
return;
Q = CreatQueue(MaxSize); // 創建並初始化隊列Q
AddQ(Q, BT);
while (!IsEmptyQ(Q)) {
T = DeleteQ(Q);
printf("%d ", T->Data); // 訪問取出隊列的結點
if (NULL != T->Left)
AddQ(Q, T->Left);
if (NULL != T->Right)
AddQ(Q, T->Right);
}
}
二叉樹遍歷的應用
輸出二叉樹中葉子結點
在二叉樹的遍歷算法中增加檢測結點的“左右子樹是否都爲空”
實現
以前序遍歷爲例
void PreOrderTraversal(BinTree BT) {
if (NULL != BT) {
if (NULL == BT->Left && NULL == BT->Right)
printf("%d ", BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
求二叉樹的高度
實現
int PostOrderGetHeight(BinTree BT) {
int HL, HR, MaxH;
if (NULL != BT) {
HL = PostOrderGetHeight(BT->Left); // 求左子樹深度
HR = PostOrderGetHeight(BT->Right); // 求右子樹深度
MaxH = (HL > HR) ? HL : HR; // 取左右子樹較大的深度
return MaxH + 1; // 返回樹的深度
}
return 0; // 空樹深度爲0
}
二元運算表達式樹及其遍歷
三種遍歷可以得到三種不同的訪問結果:
* 先序遍歷得到前綴表達式
* 中序遍歷得到中綴表達式(中綴表達式會受到運算符優先級的影響,輸出的時候不能直接輸出,需要做額外處理)
* 後序遍歷得到後綴表達式
例
- 先序遍歷:
+ + a ∗ b c ∗ + ∗ d e f g - 中序遍歷:
a + b ∗ c + d ∗ e + f ∗ g ,這一部分是的運算符優先級是有問題的 - 後序遍歷:
a b c ∗ + d e ∗ f + g ∗ +
由兩種遍歷序列確定二叉樹
由中序遍歷和前序遍歷(後序遍歷)可以唯一確定一棵二叉樹
例
以先序和中序遍歷序列來確定一棵二叉樹
- 根據先序遍歷序列第一個結點確定根結點
- 根據根結點在中序遍歷序列中分割出左右兩個子序列
- 對左子樹和右子樹分別遞歸使用相同的方法繼續分解