第7章 二叉樹

第7章 二叉樹

1、二叉樹的基本概念

  1. 二叉樹:一個根結點及兩顆互不相交的分別稱做這個根結點的左子樹和右子樹的二叉樹組成
  2. 二叉樹與普通樹的區別:二叉樹的子樹必定有序;普通樹子樹能夠有序也能夠無序
  3. 滿二叉樹:全部終端結點都位於同一層次,且其餘非終端結點的度都爲 \(2\)
  4. 徹底二叉樹:一顆二叉樹扣除其最大層次後爲一顆滿二叉樹,且層次最大那層的全部結點都向左靠齊
  5. 注:滿二叉樹必定是徹底二叉樹;徹底二叉樹不必定是滿二叉樹
  6. 二叉樹的性質:
    1. 一顆非空二叉樹的第 \(i\) 層上至多有 \(2^{i-1}\) 個結點 (\(i>=1\)
    2. 深度爲 \(h\) 的二叉樹至多有 \(2^h-1\) 個結點(\(h>=1\)
    3. 對於任何一顆二叉樹 \(T\),若是其終端結點(葉子結點)數爲 \(n_0\),度爲 \(1\) 的結點樹爲 \(n_1\),度爲 \(2\) 的結點樹爲 \(n_2\),則 \(n_0=n_2+1\)
      1. 注:\(n_0+n_1+n_2 = n_1+2*n_2+1\)
    4. 對於具備 \(n\) 個結點的徹底二叉樹,若是按照從
      上到下、同一層次上的結點按從左到右的順序對二叉樹中的全部結點從$ 1$ 開始順序編號,則對於序號爲 \(i\)
      結點,有:
      1. 若是 \(i>1\),則序號爲 \(i\) 的雙親結點的序號爲 \([i/2](取整函數)\)
      2. 若是 \(2*i>n\),則結點 \(i\) 無左子女(此時結點 \(i\) 爲終端結點);不然其左子女爲結點 \(2*i\)
      3. 若是 \(2*i+1>n\),則結點 \(i\) 無右子女;不然其右子女爲節點 \(2*i+1\)

2、二叉樹的基本運算

3、二叉樹的存儲結構

3.1 順序存儲結構

3.2 鏈式存儲結構

3.2.1 二叉樹鏈式存儲結構

typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
    // struct node *parent; // 指向雙親的指針 (無關緊要)
} bintnode;
typedef bintnode *bintree;
bintree root; // 指向二叉樹根結點的指針

4、二叉樹的遍歷

4.1 二叉樹遍歷的定義

  1. 前序遍歷:首先訪問根結點;
    而後按照前序遍歷的順序訪問根結點的左子樹;
    再按照前序遍歷的順序訪問根結點的右子樹node

  2. 中序遍歷:首先按照中序遍歷的順序訪問根結點的左子樹;
    而後訪問根結點;最後按照中序遍歷的順序訪問根結點的右子樹算法

  3. 後序遍歷:首先按照後序遍歷的順序訪問根結點的左子樹;
    而後按照後序遍歷的順序訪問根結點的右子樹;最後訪問根結點函數

  4. 圖二叉樹的遍歷:spa

4.2 二叉樹遍歷的遞歸實現

  1. 二叉樹遍歷的遞歸實現 :按照遍歷規定的次序,訪問根結點時就輸出根結點的值設計

  2. 經常使用操做:指針

    1. 中序遍歷的二叉樹的遞歸算法
    2. 後序遍歷的二叉樹的遞歸算法

4.2.1 前序遍歷的二叉樹的遞歸算法(算法)

typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

void preorder(bintree t) {
    if (t) {
        printf("%c", t->data);
        preorder(t->lchild);
        preorder(t->rchild);
    }
}

4.2.2 前序遍歷時二叉樹的建立算法(算法)

typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

bintree createbintree() {
    char ch;
    bintree t;
    if ((ch = getchar()) == '#') t = NULL;
    else {
        t = (bintnode *) malloc(sizeof(bintnode)); // 生成二叉樹的根結點
        t->data = ch;
        t->lchild = createbintree(); // 遞歸實現左子樹的創建
        t->rchild = createbintree(); // 遞歸實現右子樹的創建
    }
    return t;
}
  1. 圖建立二叉樹:

4.3 二叉樹遍歷的非遞歸實現

  1. 經常使用操做:
    1. 中序遍歷的二叉樹的非遞歸算法
    2. 後序遍歷的二叉樹的非遞歸算法

4.3.1 前序遍歷的二叉樹的非遞歸算法(算法)

  1. 算法步驟:
    1. 對於一顆樹(子樹)\(t\)
    2. 訪問完 \(t\) 的根結點值後,進入 \(t\) 的左子樹,可是此時須要將 \(t\) 保存起來
    3. \(t\) 處設置一個回溯點
    4. 訪問完左子樹後,經過回溯點 \(t\) 進入 \(t\) 的右子樹訪問
    5. 注:棧頂元素即將出棧時,意味着根結點和左子樹訪問完成,出棧後須要進入其右子樹訪問
typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

void preorder1(bintree t) {
    seqstack s;
    s.top = 0;

    // 當前處理的子樹不爲空或棧不爲空則循環
    while ((t) || (s.top != 0)) {
        if (t) {
            printf("%c ", t->data);
            push(&s, t);
            t = t->lchild;
        } else {
            t = pop(&s);
            t = t->rchlid;
        }
    }
}

5、二叉樹其餘運算的實現

5.1 二叉樹的查找(算法)

  1. 算法步驟:
    1. 首先判斷樹是否爲空
    2. 若是樹(子樹)結點值爲 \(x\),則返回
      1. 不然前往左子樹查找,找到返回值
        1. 不然前往右子樹查找,找到返回值
typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

bintree locate(bintree t, dataype x) {
    bintree p;
    if (t == NULL) return NULL;
    else {
        if (t->data == x) return t;
        else {
            p = locate(t->lrchild);
            if (p) return p;
            else return locate(t->rchild)
        }
    }
}

5.2 統計二叉樹中的結點的個數(算法)

  1. 算法步驟:
    1. 判斷樹(子樹)是否爲空
    2. 不爲空遞歸查找左子樹和右子樹,而且返回左子樹結點總數+右子樹結點總數+根結點
typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int numofnode(bintree t) {
    if (t == NULL) return 0;
    else return (numofnode(t->lchild) + numofnode(t->rchild) + 1);
}

5.3 判斷二叉樹是否等價(算法)

  1. 算法步驟:
    1. 判斷兩個二叉樹是否都爲空,都爲空則等價
    2. 若是兩個二叉樹不都爲空
      1. 首先判斷根結點是否相同
      2. 其次遞歸判斷左子樹是否相同
      3. 最後遞歸判斷右子樹是否相同,是否等價的標準取決於右子樹是否也相同
typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int isequal(bintree t1, bintree t2) {
    int t;
    t = 0;
    if (t1 == NULL && t2 == NULL) t = 1; // t1 和 t2 均爲空,則兩者等價
    else {
        // 處理 t1 和 t2 均不爲空的狀況
        if (t1 != NUll && t2 != NULL)
            if (t1->data == t2->data) // 若是根結點的值相等
                if (isequeal(t1->lchild, t2->lchild)) // 若是 t1 和 t2 的左子樹等價
                    t = isequeal(t1->rchild, t2->rchild); // 返回值取決於 t1 和 t2 的右子樹是否等價
    }
    return (t);
}

5.4 求二叉樹的高度(算法)

  1. 算法步驟:
    1. 首先處理空二叉樹的狀況
    2. 其次遞歸得出左子樹的高度
    3. 最後遞歸得出右子樹的高度
    4. 遞歸期間,若是左子樹高度大於右子樹高度,左子樹高度加 \(1\),不然右子樹高度加 \(1\)
    5. 注:遞歸出口應該用第三個變量 \(h\) 來返回子樹高度
typedef char datatype; // 結點屬性值的類型
// 二叉樹結點的類型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int depth(bintree t) {
    int h, lh, rh;
    if (t == NULL) h = 0; // 處理空二叉樹的狀況
    else {
        lh = depth(t->lchild); // 求左子樹的高度
        rh = depth(t->rchild); // 求右子樹的高度
        if (lh >= rh) h = lh + 1; // 求二叉樹t的高度
        else h = rh + 1;
    }
    return h;
}

6、穿線二叉樹

6.1 穿線二叉樹的定義

  1. 穿線二叉樹的指針:結點的左、右指針指向其左、右子女code

  2. 中序穿線二叉樹的線索:結點的左、右指針指向其中序遍歷的前驅、後繼結點blog

  3. 爲了區別結點的左右指針是指針仍是線索,通常加上 \(ltag\)\(rtag\) 兩個標誌位遞歸

    1. \(ltag=0\) 表示結點的左指針指向其左子女
    2. \(ltag=1\) 表示結點的左指針指向其中序遍歷的前驅
    3. \(rtag=0\) 表示結點的右指針指向其右子女
    4. \(rtag=1\) 表示結點的右指針指向其中序遍歷的後繼
  4. 圖中序穿線二叉樹:get

6.2 中序穿線二叉樹的基本運算

6.3 中序穿線二叉樹的存儲結構及其實現

  1. 經常使用操做:
    1. 建立一顆中序二叉樹

6.3.1 中序穿線二叉樹的存儲結構

typedef char datatype;
typedef struct node {
    datatype data;
    int ltag, rtag; // 左右標誌位
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *binthrtree;

6.3.2 中序遍歷中序穿線二叉樹(真題)(算法)

  1. 算法步驟:
    1. 找到中序遍歷下的第一個結點(從根結點出發,沿着左指針不斷往左走,直至左指針爲空)
    2. 從中序遍歷的第一個結點開始,不斷地尋找當前結點在中序遍歷下的後繼結點並輸出
      1. 在中序穿線二叉樹中找後繼結點步驟:
        1. 若右標誌爲 \(1\),則代表右指針正好指向其中序遍歷下的後繼結點
        2. 若右標誌爲 \(0\),則說明他有右子樹,所以其中序遍歷下的後繼結點應該是該右子樹中序遍歷下的第一個結點(右子樹的最左下的結點,與第一步步驟徹底相同)
typedef char datatype;
typedef struct node {
    datatype data;
    int ltag, rtag; // 左右標誌位
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *binthrtree;

// 尋找結點 p 在中序遍歷下的後繼結點
binthrtree insuccnode(binthrtree p) {
    binthrtree q;
    if (p->rtag == 1) // p 的右指針爲線索,恰巧指向p的後繼結點
        return p->rchild;
    else {
        q = p->rchild; // 尋找 p 的右子樹中最左下的結點
        while (q->ltag == 0) q = q->lchild; // 求該右子樹下中序遍歷下的第一個結點
        return q;
    }
}

// 中序遍歷中序穿線二叉樹
void inthrtree(binthrtree p) {
    if (p) {
        while (p->ltag == 0) p = p->lchild; // 求 p 中序遍歷下的第一個結點
        do {
            printf("%c ", p->data);
            p = insuccnode(p); // 求 p 中序遍歷下的後繼結點
        } while (p);
    }
}

7、樹、森林和二叉樹的轉換

  1. 注:任意一顆樹(森林)都惟一地對應一顆二叉樹;相反,任何一顆二叉樹都惟一地對應一顆樹(森林)

7.1 樹、森林到二叉樹的轉換

  1. 樹、森林到二叉樹的轉換步驟

    1. 在全部兄弟結點之間添加一條連線,若是是森林,則在其全部樹的樹根之間一樣也添加一條連線
    2. 對於樹、森林中的每一個結點,除保留其到第一個子女的連線外,撤消其到其它子女的連線;
    3. 將以上獲得的樹按照順時針方向旋轉45度
  2. 圖樹到二叉樹的轉換:

  3. 圖森林到二叉樹的轉換:

7.2 二叉樹到樹、森林的轉換

  1. 首先將二叉樹按照逆時針方向旋轉45度

  2. 若某結點是其雙親的左子女,則把該結點的右子女,右子女的右子女,……都與該結點的雙親用線連起來

  3. 最後去掉原二叉樹中全部雙親到其右子女的連線

  4. 注:最後鏈接子結點,只能鏈接右子女,而不能鏈接左子女

  5. 圖二叉樹到森林的轉換:

8、經過前中後序遍歷肯定二叉樹(補充)

8.1 前序+中序遍歷

  1. 已知一棵二叉樹的前序和中序序列,構造該二叉樹的過程以下:
    1. 根據前序序列的第一個元素創建根結點;

    2. 在中序序列中找到該元素,肯定根結點的左右子樹的中序序列;

    3. 在前序序列中肯定左右子樹的前序序列;

    4. 由左子樹的前序序列和中序序列創建左子樹;

    5. 由右子樹的前序序列和中序序列創建右子樹。

如:已知一棵二叉樹的先序遍歷序列和中序遍歷序列分別是 abdgcefh、dgbaechf,求二叉樹及後序遍歷序列。

先序:abdgcefh—>a bdg cefh
中序:dgbaechf—->dgb a echf
得出結論:a 是樹根,a 有左子樹和右子樹,左子樹有 bdg 結點,右子樹有 cefh 結點

先序:bdg—>b dg
中序:dgb —>dg b
得出結論:b 是左子樹的根結點,b 無右子樹,有左子樹

先序:dg—->d g
中序:dg—–>dg
得出結論:d 是 b 左子樹的根節點,d 無左子樹,g 是 d 的右子樹

而後對於 a 的右子樹相似能夠推出

而後還原

8.2 中序+後序遍歷

  1. 已知一棵二叉樹的中序和後序序列,構造該二叉樹的過程以下:
    1. 根據後序序列的最後一個元素創建根結點
    2. 在中序序列中找到該元素,肯定根結點的左右子樹的中序序列
    3. 在後序序列中肯定左右子樹的後序序列
    4. 由左子樹的後序序列和中序序列創建左子樹
    5. 由右子樹的後序序列和中序序列創建右子樹
如:已知一棵二叉樹的後序遍歷序列和中序遍歷序列分別是gdbehfca、dgbaechf,求二叉樹

後序:gdbehfca—->gdb ehfc a
中序:dgbaechf—–>dgb a echf
得出結論:a是樹根,a有左子樹和右子樹,左子樹有 bdg 結點,右子樹有 cefh 結點

後序:gdb—->gd b
中序:dgb—–>dg b
得出結論:b 是 a 左子樹的根節點,無右子樹,有左子樹 dg

後序:gd—->g d
中序:dg—–>d g
得出結論:d 是 b 的左子樹根節點,g 是 d 的右子樹

而後對於 a 的右子樹相似能夠推出

而後還原

8.3 前序+後序遍歷

  1. 注:前序和後序在本質上都是將父節點與子結點進行分離,但並無指明左子樹和右子樹的能力,所以獲得這兩個序列只能明確父子關係,而不能惟一地肯定一棵二叉樹

9、算法設計題

9.1 求一顆給定二叉樹中葉子結點的個數(遞歸和非遞歸)(真題)(算法)

分別採用遞歸和非遞歸方式編寫兩個函數,求一棵給定二叉樹中葉子結點的個數

  1. 算法步驟(遞歸):
    1. 判斷二叉樹是否爲空,爲空返回 \(0\)
    2. 判斷二叉樹的子樹(子樹的子樹)的兩個左右孩子是否爲空,爲空返回 \(1\)
    3. 左右遞歸搜索兩棵子樹
  2. 算法步驟(非遞歸):
    1. 二叉樹不爲空或棧不爲空則開啓循環
      1. 若是二叉樹存在,而且左右子樹爲空,計數加 \(1\)
        1. 不然將該子樹放入棧中,而且搜索該子樹的左子樹
      2. 若是二叉樹不存在,證實左子樹搜索完畢,從棧中取出子樹搜索其右子樹
typedef char datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *bintree;

// 遞歸方法求二叉樹葉子結點的個數
int leaf1(bintree t) {
    if (t == NULL) return 0;
    else if (!t->lchild && !t->rchild)
        return 1;
    else
        return leaf1(t->lchild) + leaf1(t->rchild);
}

// 非遞歸方法求二叉樹葉子結點的個數
int leaf2(bintree t) {
    seqstack s; // 順序棧
    int count = 0; // 葉子結點計數變量
    init(&s); // 初始化空棧

    while (t || !empty(&s)) {
        if (t) {
            if (!t->lchild && !t->rchild) count++;
            push(&s, t);
            t = t->lchild;
        } else {
            t = pop(&s);
            t = t->rchild;
        }
    }
    return count;
}

9.2 返回一顆給定二叉樹在中序遍歷下的最後一個結點(真題)(算法)

試編寫一個函數,返回一顆給定二叉樹在中序遍歷下的最後一個結點

  1. 算法 步驟:
    1. 不斷地查找右子樹的右子結點
    2. 返回最後一個右子結點
typedef char datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *bintree;

// 遞歸實現
bintree midlast(bintree t) {
    if (t && t->rchild) t = midlast(t->rchild);
    return t;
}

// 非遞歸實現
bintree midlast(bintree t) {
    bintree p = t;
    while (p && p->rchild) p = p->rchild;
    return p;
}

10、錯題集

  1. 前序(根左右)和中序(左根右)遍歷結果相同的二叉樹(去掉左都爲「根右」)爲(全部結點只有右子樹的二叉樹);前序(根左右)和後序(左右根)遍歷結果相同的二叉樹(哪個子樹都不能夠去掉)爲(只有根結點的二叉樹)

  2. \(n\) 個結點的二叉樹,已知葉結點個數爲 \(n_0\),則該樹中度爲 \(1\) 的結點的個數爲(\(n-2*n_0+1\));若此樹是深度爲 \(k\) 的徹底二叉樹,則 \(n\) 的最小值爲 (\(2^{k-1}\)

    1. 注:徹底二叉樹的最後一層,必定有一個結點,所以 \(n\) 的最小值爲 \(k-1\) 層總結點數加 \(1\)\(2^{k-1}-1+1\)
  3. (真題)對於一顆具備 \(n\) 個結點的二叉樹,該二叉樹中全部結點的讀書之和爲(\(n-1\)

  4. (真題)試分別畫出具備 \(3\) 個結點的樹和具備 \(3\) 個結點的二叉樹的全部不一樣形態。(此題注意樹具備無序性)

    1. 圖例題7-9答案
相關文章
相關標籤/搜索