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;
}