5. 樹--二叉樹的表示及其遍歷

二叉樹

定義

  • 一個有窮的結點集合
  • 這個集合可以爲空
  • 若不爲空,則它是由根結點和稱爲其左子樹TL 和右子樹TR 的兩個不相交的二叉樹組成。
  • 二叉樹具體五種基本形態
    • image
    • image
    • image
    • image
    • image
  • 二叉樹的子樹有左右順序之分

特殊二叉樹

斜二叉樹(Skewed Binary Tree)

image

滿二叉樹(Full Binary Tree)

又稱完美二叉樹(Perfect Binary Tree)

image

完全二叉樹(Complete Binary Tree)

n 個結點的二叉樹,對樹中結點按從上至下、從左到右順序進行編號,編號爲i(1in) 結點與滿二叉樹中編號爲i 結點在二叉樹中位置相同,只會在最後一層出現右邊纔會出現沒有結點的情況。滿二叉樹也是完全二叉樹。

非完全二叉樹

image

二叉樹的性質

  • 一個二叉樹第i 層的最大結點數爲:2i1i1
  • 深度爲k 的二叉樹有最大結點總數爲:2k1k1
  • 對任何非空二叉樹T,若n0 表示葉結點的個數、n1 是度爲1的非葉結點個數、n2 是度爲2的非葉結點個數,那麼兩者滿足關係n0=n2+1
    例:

    image

    • n0=4n1=2
    • n2=3
    • 所以可以推出n0=n2+1

抽象數據類型定義

  • 類型名稱:二叉樹
  • 數據對象集:一個有窮的結點集合。若不爲空,則由根結點和其左右二叉子樹組成
  • 操作集:BTBinTreeItemElementType ,重要的操作有:
    1. Boolean IsEmpty(BinTree BT):判斷BT是否爲空
    2. BinTree CreatBinTree():創建一個二叉樹
    3. void Traversal(BinTree BT):遍歷,按某順序訪問每個結點
      • void PreOrderTraversal(BinTree BT):先序遍歷—根、左子樹、右子樹
      • void InOrderTraversal(BinTree BT):中序遍歷—左子樹、根、右子樹
      • void PostOrderTraversal(BinTree BT):後序遍歷—左子樹、右子樹、根
      • void LevelOrderTraversal(BinTree BT):層序遍歷—從上到下、從左到右

順序存儲結構

完全二叉樹

按從上至下、從左到右順序存儲

n 個結點完全二叉樹的結點父子關係
* 非根結點(序號i1 )的父結點的序號是i/2
* 結點(序號爲i )的左孩子結點的序號是2i (如果2in ,則沒有左孩子)
* 結點(序號爲i )的右孩子結點的序號是2i+1 (如果2i+1n ,則沒有右孩子)

image

結點 序號
A 1
B 2
O 3
C 4
S 5
M 6
Q 7
W 8
K 9

一般二叉樹

一般二叉樹也可以採用這種結果,但會造成空間浪費,不推薦使用

image

結點 序號
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;
}

image

二叉樹的遞歸遍歷

先序遍歷

遍歷過程:
1. 訪問根結點
2. 先序遍歷其左子樹
3. 先序遍歷其右子樹

實現

void PreOrderTraversal(BinTree BT) {
    if (NULL != BT) {
        printf("%d ", BT->Data);
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

image

遍歷結果: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);
    }
}

image

遍歷結果: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);
    }
}

image

遍歷結果:D E F B H G I C A

在整個遍歷過程中,根結點在子樹遍歷時總是最後一個輸出的

總結

  • 先序、中序和後序遍歷過程中經過結點的路線一樣,只是訪問各結點的時機不同
  • 圖中在從入口到出口的曲線上用 三種符號分別標記出了先序、中序和後序訪問各結點的時刻

image

二叉樹的非遞歸遍歷

非遞歸算法實現的基本思路:使用

中序遍歷

  1. 遇到一個結點,就把它壓棧
  2. 當左子樹遍歷結束後,從棧頂彈出這個結點並訪問它
  3. 然後按其右指針再去中序遍歷該結點的右子樹

實現

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;   // 轉向右子樹
    }
}

先序遍歷

  1. 遇到一個結點,先訪問結點,然後把它壓棧
  2. 當左子樹遍歷結束後,從棧頂彈出這個結點但不訪問
  3. 然後按其右指針再去前序遍歷該結點的右子樹

實現

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進行遍歷
  3. 遇到一個結點,先把這個結點壓入棧1,然後同時壓入棧2
  4. 當右子樹遍歷結束後,從棧2中彈出這個結點但不訪問
  5. 然後按照其左指針再去遍歷該結點的左子樹
  6. 遍歷結束以後,輸出棧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);
    }
}

求二叉樹的高度

image

實現
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
}

二元運算表達式樹及其遍歷

三種遍歷可以得到三種不同的訪問結果:
* 先序遍歷得到前綴表達式
* 中序遍歷得到中綴表達式(中綴表達式會受到運算符優先級的影響,輸出的時候不能直接輸出,需要做額外處理)
* 後序遍歷得到後綴表達式

image

  • 先序遍歷:+ + a b c + d e f g
  • 中序遍歷:a + b c + d e + f g,這一部分是的運算符優先級是有問題的
  • 後序遍歷:a b c + d e f + g +

由兩種遍歷序列確定二叉樹

中序遍歷和前序遍歷(後序遍歷)可以唯一確定一棵二叉樹

以先序和中序遍歷序列來確定一棵二叉樹

  • 根據先序遍歷序列第一個結點確定根結點
  • 根據根結點在中序遍歷序列中分割出左右兩個子序列
  • 左子樹和右子樹分別遞歸使用相同的方法繼續分解

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章