二叉樹的建立、遍歷、廣義錶轉換

PRO_Z發表於2022-02-27

1樹的定義及相關術語

1.1 樹的定義

  • 樹是一種非線性的資料結構,由n(n>=0)個結點組成的有限集合;
    • 如果n=0,稱為空樹;
    • 如果n>0,則:
      • 有一個特定的結點被稱之為根結點(root),根結點只有直接後繼,沒有前驅;
      • 除根結點外的其他結點劃分為m(m>=0)個互不相交的有限集合T0,T1...Tm-1,每一個集合又是一顆子樹,並稱之為根的子樹。

1.2 樹的特點

  • 每個結點有零個或多個子結點;
  • 沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分為多個不相交的子樹;
  • 節點的數量等於邊數加一;樹由n 個節點 和 (n-1)條邊 構成,其中n ≥ 1;
  • 樹或棧這種資料結構用於解決** 具有完全包含關係的問題**;
  • 問題抽象(思維邏輯):本質上是把一個大問題分成若干個小問題,當小問題解決了,大問題自然也解決了;首先,將樹中的“節點”看作“集合”,“邊”看作“關係”,接著把“全集”看成是一個大問題,而“子集”看作是若干個小問題,我們只需要關注小問題的求解即可。
二叉樹的建立、遍歷、廣義錶轉換

1.3 樹的相關術語

  • 節點深度:對任意節點x,x節點的深度表示為根節點到x節點的路徑長度。所以根節點深度為0,第二層節點深度為1,以此類推;
  • 節點高度:對任意節點x,葉子節點到x節點的路徑長度就是節點x的高度;
  • 樹的深度(高度):一棵樹中節點的最大深度就是樹的深度,也稱為樹的高度;
  • 度:節點的子樹數目就是節點的度;
  • 葉子節點 vs 分支節點:度為零的節點就是葉子節點(路徑中的最後一個節點);度不為零的節點就是分支節點;
  • 父節點:若一個節點含有子節點,則這個節點稱為其子節點的父節點;
  • 子節點:一個節點含有的子樹的根節點稱為該節點的子節點;
  • 兄弟節點:擁有共同父節點的節點互稱為兄弟節點;
  • 節點的層次:從根節點開始,根節點為第一層,根的子節點為第二層,以此類推;
  • 祖先:對任意節點x,從根節點到節點x的所有節點都是x的祖先(節點x也是自己的祖先)
  • 後代:對任意節點x,從節點x到葉子節點的所有節點都是x的後代(節點x也是自己的後代)
  • 森林:m顆互不相交的樹構成的集合就是森林
  • n叉樹:由一個節點向外最多引出節點的數量,即當前這個節點最多向外有n個指向;
  • 補充:度分為“入度”和“出度”;
    • 出度:由一個節點向外指向邊的數量;
    • 入度:有多少條邊指向一個節點的數量;樹的入度為1,所以只考慮出度,即為度;
  • 案例:
    • 樹的深度(高度):4,path = 1→4→6→8
    • 節點4的深度:2,path = 1→4;節點4的高度:3,path = 8→6→4
    • 節點2的度:1, =(5, );節點4的度:2,=(6, 7);葉子節點的度:0
二叉樹的建立、遍歷、廣義錶轉換

2 二叉樹

2.1 定義

  • 在一個樹中,由一個節點向外最多引出2個節點,即當前這個節點最多向外有2個指向;

2.2 n叉樹 vs 二叉樹

如何定義n叉樹,n是多少的問題?

  • 對於n叉樹中的n是一個不確定性的問題,一般計算機只能處理確定性問題,所以,我們可以將n 叉樹轉換為2叉樹來思考;
  • 由於計算機底層是基於二進位制計算的,故2 叉樹 與 n叉樹之間可以相互轉換;
  • 表示方法:左孩子右兄弟表示法,又名十字連結串列法
二叉樹的建立、遍歷、廣義錶轉換

2.3 特點

  • 每個節點的度最多為2;
  • 度為0的節點比度為2的節點多1個;
    • 證明:參考公式 —> 節點的數量等於邊數加一
    • xi:表示度為i的節點數,其中i 為 0,1,2。總共的節點數 = x2 + x1 + x0,度為2提供的邊數:2x2, 度為1提供的邊數:x1,度為0提供的邊數:0;所以 節點數 = 邊數 + 1==> x2 + x1 + x0 = 2x2 + x1 + 1 ==> x0 = x2 + 1

2.4 遍歷

  • 二叉樹的遍歷是指從二叉樹的根結點出發,按照某種次序依次訪問二叉樹中的所有結點,使得每個結點僅被訪問一次。
  • 二叉樹的訪問次序:前序遍歷、中序遍歷、後序遍歷
二叉樹的建立、遍歷、廣義錶轉換
  • 前序遍歷:1→2→4→5→3→6
  • 中序遍歷:4→2→5→1→3→6
  • 後序遍歷:4→5→2→6→3→1

2.5 分類

  • 中國版
二叉樹的建立、遍歷、廣義錶轉換
  • 國際版
二叉樹的建立、遍歷、廣義錶轉換
  • 完美二叉樹:葉子節點都在同一層並且除葉子節點外的所有節點都有兩個子節點;
  • 完全二叉樹(重點):對於一顆二叉樹,假設其深度為d(d>1)。除第d層外的所有節點構成完美二叉樹,且第d層所有節點從左向右連續地緊密排列,這樣的二叉樹被稱為完全二叉樹;
  • 完全二叉樹的優勢:從根節點到葉子節點中的每一個節點進行一個順序(連續)標號
  • 完全二叉樹的特點:
    • 編號為i的子節點:左孩子編號為2i,右孩子編號為2i+1,注:i ≥ 1;
    • 若 i 從0 開始標記,則左孩子編號為2i + 1,右孩子編號為2i+2,無意中多了一次加法運算;(不建議)
    • 可以用連續空間儲存(陣列);
  • 完全二叉樹實現方式:順序表 和 連結串列

2.6 廣義表

可以使用廣義表來表示一顆二叉樹

二叉樹的建立、遍歷、廣義錶轉換 二叉樹的建立、遍歷、廣義錶轉換

2.7 二叉排序樹

  • 本質:基於二叉樹這種結構,定義了一種性質,並且在不斷維護這種性質的一種新結構。
  • 性質:一顆二叉樹中的任何三元組都滿足 B(左孩子) < A(根節點) < C (右孩子)的這種關係。故中序遍歷即可實現排序操作。
  • 補充:資料結構的本質:定義一種性質,並且維護這種性質的一種結構

2.8 廣義錶轉二叉樹

  • 通過“棧”這種資料結構實現廣義錶轉二叉樹;

3 程式碼展示

3.1 二叉排序樹

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Node {
    int data;
    struct Node *lchild, *rchild;
} Node;

typedef struct Tree {
    Node *root;
    int length; // 記錄二叉樹的節點個數
} Tree;

Node *getNewNode(int val);
Tree *getNewTree();
Node *insert_node(Node *, int, int *);
void insert(Tree *, int);
void pre_order_node(Node *);
void pre_order(Tree *);
void in_order_node(Node *);
void in_order(Tree *);
void post_order_node(Node *);
void post_order(Tree *);
void clear_node(Node *);
void clear(Tree *);
void output_node(Node *);
void output(Tree *);


int main() {
    srand(time(0));
    Tree *tree = getNewTree();
    #define MAX_OP 10
    for (int i = 0; i < MAX_OP; i++) {
        int val = rand() % 100;
        insert(tree, val);
        output(tree);
    }
    #undef MAX_OP
    pre_order(tree);
    in_order(tree);
    post_order(tree);
    clear(tree);

    return 0;
}

Node *getNewNode(int val) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->data = val;
    p->lchild = p->rchild = NULL;
    return p;
}

Tree *getNewTree() {
    Tree *t = (Tree *)malloc(sizeof(Tree));
    t->root = NULL;
    t->length = 0;
    return t;
}

// ⼆叉排序樹插入操作
// 性質:一顆二叉樹中的任何三元組都滿足 B(左孩子) < A(根節點) < C (右孩子)的這種關係,故中序遍歷可實現排序
// 插入過程(尾插法):從根節點開始,找到待插入位置前的葉子節點(向下尋找),然後返回各個節點地址(向上回溯)
Node *insert_node(Node *root, int val, int *flag) {
    if (root == NULL) {
        *flag = 1;
        return getNewNode(val);
    }
    if (root->data == val) return root; // ⼆叉排序樹的性質:任何三元組都滿足 B(左孩子) < A(根節點) < C (右孩子)
    if (root->data > val) root->lchild = insert_node(root->lchild, val, flag);
    else root->rchild = insert_node(root->rchild, val, flag);
    return root;
}

void insert(Tree *t, int val) {
    if (t == NULL) return ;
    int flag = 0;   // 傳出引數,標記當前插入節點是否成功,成功:1,失敗:0
    t->root = insert_node(t->root, val, &flag);
    t->length += flag;
    return ;
}

// 前序:根->左->右
void pre_order_node(Node *root) {
    if (root == NULL) return ;
    printf("%d ", root->data);
    pre_order_node(root->lchild);
    pre_order_node(root->rchild);
    return ;
}

void pre_order(Tree *t) {
    if (t == NULL) return ;
    printf("pre_order : ");
    pre_order_node(t->root);
    printf("\n");
    return ;
}

// 中序:左->根->右
void in_order_node(Node *root) {
    if (root == NULL) return ;
    in_order_node(root->lchild);
    printf("%d ", root->data);
    in_order_node(root->rchild);
    return ;
}

void in_order(Tree *t) {
    if (t == NULL) return ;
    printf("in_order : ");
    in_order_node(t->root);
    printf("\n");
    return ;
}

// 後序:左->右->根
void post_order_node(Node *root) {
    if (root == NULL) return ;
    post_order_node(root->lchild);
    post_order_node(root->rchild);
    printf("%d ", root->data);
    return ;
}

void post_order(Tree *t) {
    if (t == NULL) return ;
    printf("post_order : ");
    post_order_node(t->root);
    printf("\n");
    return ;
}

// 從葉子節點開始刪除
void clear_node(Node *node) {
    if (node == NULL) return ;
    clear_node(node->lchild);
    clear_node(node->rchild);
    free(node);
    return ;
}

void clear(Tree *t) {
    if (t == NULL) return ;
    clear_node(t->root);
}

// 相當於前序遍歷
void output_node(Node *root) {
    if (root == NULL) return ;
    printf("%d", root->data);
    if (root->lchild == NULL && root->rchild == NULL) return ;  // 若是葉子節點,則結束
    printf("(");
    output_node(root->lchild);
    printf(",");
    output_node(root->rchild);
    printf(")");
    return ;
}

void output(Tree *t) {
    if (t == NULL) return ;
    printf("tree(%d) : ", t->length);
    output_node(t->root);
    printf("\n");
}
// tree(1) : 6
// tree(2) : 6(,28)
// tree(3) : 6(,28(9,))
// tree(4) : 6(,28(9,87))
// tree(5) : 6(,28(9,87(61,)))
// tree(6) : 6(,28(9,87(61(34,),)))
// tree(7) : 6(,28(9,87(61(34(,40),),)))
// tree(8) : 6(,28(9,87(61(34(,40),65),)))
// tree(9) : 6(,28(9(,20),87(61(34(,40),65),)))
// tree(9) : 6(,28(9(,20),87(61(34(,40),65),)))
// pre_order : 6 28 9 20 87 61 34 40 65 
// in_order : 6 9 20 28 34 40 61 65 87 
// post_order : 20 9 40 34 65 61 87 28 6

3.2 廣義錶轉二叉樹

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

 //⼆叉樹的結構定義
typedef struct Node {
    char data;
    struct Node *lchild, *rchild;
} Node;

typedef struct Tree {
    Node *root;
    int length;
} Tree;

//棧的結構定義(儲存⼆叉樹節點地址)
typedef struct Stack {
    Node **data;    // 定義二級指標的原因:使用陣列來存放每個節點的地址,即陣列的每個元素都是 Node*
    int top, size;  // top: 標記棧頂位置,預設為-1;size: 當前棧空間的大小
} Stack;

Node *getNewNode(char);             // 節點的初始化
Tree *getNewTree();                 // ⼆叉樹的初始化
void clear_node(Node *);            // 銷燬⼆叉樹的節點
void clear_tree(Tree *);            // 銷燬⼆叉樹
Stack *init_stack(int);             // 棧的初始化
void clear_stack(Stack *);          // 棧的銷燬
Node *top(Stack *);                 // 輸出棧頂元素
int empty(Stack *);                 // 棧的判空
int push(Stack *, Node *);          // 壓棧操作
int pop(Stack *);                   // 彈棧操作
void pre_order_node(Node *);
void pre_order(Tree *);             // 前序遍歷
void in_order_node(Node *);
void in_order(Tree *);              // 中序遍歷
void post_order_node(Node *);
void post_order(Tree *);            // 後序遍歷
Node *build(const char *, int *);   // 構建一顆二叉樹


int main() {
    char str[1000] = { 0 };
    int node_num = 0;
    scanf("%[^\n]s", str);
    getchar();
    Tree *tree = getNewTree();
    tree->root = build(str, &node_num);
    tree->length = node_num;
    pre_order(tree);
    in_order(tree);
    post_order(tree);
    clear_tree(tree);

    return 0;
}

Node *getNewNode(char val) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->lchild = p->rchild = NULL;
    p->data = val;
    return p;
}

Tree *getNewTree() {
    Tree *t = (Tree *)malloc(sizeof(Tree));
    t->root = NULL;
    t->length = 0;
    return t;
}

void clear_node(Node *root) {
    if (root == NULL) return;
    clear_node(root->lchild);
    clear_node(root->rchild);
    free(root);
    return;
}

void clear_tree(Tree *t) {
    if (t == NULL) return;
    clear_node(t->root);
    free(t);
    return;
}

Stack *init_stack(int n) {
    Stack *s = (Stack *)malloc(sizeof(Stack));
    s->data = (Node **)malloc(sizeof(Node *) * n);
    s->top = -1;
    s->size = n;
    return s;
}

void clear_stack(Stack *s) {
    if (s == NULL) return;
    free(s->data);
    free(s);
    return;
}

Node *top(Stack *s) {
    return s->data[s->top];
}

int empty(Stack *s) {
    return s->top == -1;
}

int push(Stack *s, Node *val) {
    if (s == NULL) return 0;
    if (s->top == s->size - 1) return 0;
    s->data[++(s->top)] = val;
    return 1;
}

int pop(Stack *s) {
    if (s == NULL) return 0;
    if (empty(s)) return 0;
    s->top -= 1;
    return 1;
}

// 二叉樹的廣義表表示:str = "A(B(, D), C(E))"
// '(': 根節點入棧,設定flag=0; ')': 出棧; ',': 設定flag=1;
// flag=0: 表示是左子節點, flag=1: 表示是右子節點
// 將廣義錶轉化為二叉樹
// 前序|後續遍歷 && 中序遍歷 ==> 二叉樹
// 本方法:棧實現廣義錶轉二叉樹
// 方法2:系統棧(遞迴)實現廣義錶轉二叉樹
Node *build(const char *str, int *node_num) {
    Stack *s = init_stack(strlen(str));
    int flag = 0;
    Node *temp = NULL, *p = NULL;   // temp:指向每層的父節點,p:指向根節點
    while (str[0]) {
        switch (str[0]) {
        case '(':
            push(s, temp);
            flag = 0;
            break;
        case ')':
            p = top(s);
            pop(s);
            break;
        case ',': flag = 1; break;
        case ' ': break;
        default:
            temp = getNewNode(str[0]);
            if (!empty(s) && flag == 0) {
                top(s)->lchild = temp;
            }
            else if (!empty(s) && flag == 1) {
                top(s)->rchild = temp;
            }
            ++(*node_num);
            break;
        }
        ++str;
    }
    clear_stack(s);
    if (temp && p == NULL) p = temp;    // 只有根節點的情況
    return p;
}

void in_order_node(Node *root) {
    if (root == NULL) return;
    in_order_node(root->lchild);
    printf("%c ", root->data);
    in_order_node(root->rchild);
    return;
}

void in_order(Tree *tree) {
    if (tree == NULL) return;
    printf("in_order(%d) : ", tree->length);
    in_order_node(tree->root);
    printf("\n");
    return;
}

void pre_order_node(Node *root) {
    if (root == NULL) return;
    printf("%c ", root->data);
    pre_order_node(root->lchild);
    pre_order_node(root->rchild);
    return;
}

void pre_order(Tree *tree) {
    if (tree == NULL) return;
    printf("pre_order(%d) : ", tree->length);
    pre_order_node(tree->root);
    printf("\n");
    return;
}

void post_order_node(Node *root) {
    if (root == NULL) return;
    post_order_node(root->lchild);
    post_order_node(root->rchild);
    printf("%c ", root->data);
    return;
}

void post_order(Tree *tree) {
    if (tree == NULL) return;
    printf("post_order(%d) : ", tree->length);
    post_order_node(tree->root);
    printf("\n");
    return;
}

相關文章