struct binary_tree {
int data ; // Data area
binary_tree * left;
binary_tree * right;
};
typedef struct binary_tree node;
接下來我們需要把數據插入到對應的位置上; 我們希望樹左邊分支的的數據總是比樹右邊分支的要小;
void insert(node ** tree, int val) {
node * temp = NULL;
if(!(*tree)) {
//TODO
return ;
}
if (val < (*tree)->data) {
//TODO
}else if (val > (*tree)->data) {
//TODO
}
}
因此我們代碼會像上面這樣寫; 第一個if語句判斷這個樹節點是否存在; 若是不存在,我們應該生成一個節點,然後添加到樹上來; 第二個if-else呢,則是判斷這個給定要存入的數據是大於當前節點的呢還是小於; 小於呢,存在左分支。大於存在右分支;
if(!(*tree)) {
temp = (node *)malloc(sizeof(node));
temp->left = temp->right = NULL;
temp->data = val;
*tree = temp;
return ;
}
分析上面代碼片段,我們發現temp的作用是臨時變量正如其名; malloc分配內存,然後初始化節點左右指針域為空,以及數據域為val; 最後*tree=temp 把節點安裝到樹上; 並且返回上一級; 對於已經存在的樹節點,我們需要往左右兩分子擴展; 因此我們的代碼會是這樣的; if (val < (*tree)->data) { insert(&(*tree)->left,val); }else if (val > (*tree)->data) { insert(&(*tree)->right,val); } 從代碼中可以看出,只對小於和大於兩個方向的數據進行操作; 你也許會考慮到萬一等於呢。 注意,在這裡應該是數據的唯一性有要求的,它類似於數學裡的集合,不會有重復的; 它的這種特性對我們往後要寫得單詞統計程序非常有幫助; 那麼這個函數的所有代碼如下:
void insert(node ** tree, int val) {
node * temp = NULL;
if(!(*tree)) {
temp = (node *)malloc(sizeof(node));
temp->left = temp->right = NULL;
temp->data = val;
*tree = temp;
return ;
}
if (val < (*tree)->data) {
insert(&(*tree)->left,val);
}else if (val > (*tree)->data) {
insert(&(*tree)->right,val);
}
}
節點創建好了,注意我們用malloc創建; 因此,我們是在堆中分配的內存,因此我們需要手動釋放; 那顯然需要用到free函數與之對應; 所以我們釋放節點的函數應該是這樣的; void deltree(node * tree) { if(tree) { free(tree); } } 這樣似乎也沒有問題啦!但是仔細觀察我們發現; 直接釋放啦free就只是釋放啦根節點; 就好比,我們去拔花生;我們只是簡單的用剪刀把上面的葉子剪斷啦; 沒有想過把花生沿著根一直挖下去是不可能把所有花生弄出來的; 因此,我們需要這樣做;
void deltree(node * tree) {
if(tree) {
deltree(tree->left);
deltree(tree->right);
free(tree);
}
}
這樣我們找到左邊的根啦,又繼續往左邊找; 找不到啦,就往右邊找; 再找不到啦,就執行到free釋放節點然後返回上一級; 好啦!樹也有函數建啦,也有辦法“砍”啦! 接下來是怎麼展示我們的樹啦; 樹的遍歷有三種; 前,中,後; void print_preorder(node * tree) { if(tree) { //TODO } } 首先我們需要判斷tree是否空; 要是空的,我們就沒有必要看裡面還有什麼數據啦;
void print_preorder(node * tree) {
if(tree) {
printf("%d\n",tree->data);
print_preorder(tree->left);
print_preorder(tree->right);
}
}
void print_inorder(node * tree) {
if(tree) {
print_inorder(tree->left);
printf("%d\n",tree->data);
print_inorder(tree->right);
}
}
void print_postorder(node * tree) {
if(tree) {
print_postorder(tree->left);
print_postorder(tree->right);
printf("%d\n",tree->data);
}
}
x 同樣的我們把中序和後序寫出來;
void print_preorder(node * tree) {
if(tree) {
printf("%d\n",tree->data);
print_preorder(tree->left);
print_preorder(tree->right);
}
}
void print_inorder(node * tree) {
if(tree) {
print_inorder(tree->left);
printf("%d\n",tree->data);
print_inorder(tree->right);
}
}
void print_postorder(node * tree) {
if(tree) {
print_postorder(tree->left);
print_postorder(tree->right);
printf("%d\n",tree->data);
}
}
好啦!該有的函數都有啦; 我們該寫測試函數啦;
int main(void)
{
node * root;
node * tmp;
//int i;
root = NULL;
/* Inserting nodes into tree */
insert(&root,9);
insert(&root,4);
insert(&root,15);
insert(&root,6);
insert(&root,12);
insert(&root,17);
insert(&root,2);
printf("Pre Order Display\n");
print_preorder(root);
printf("In Order Display\n");
print_inorder(root);
printf("Post Order Display\n");
print_postorder(root);
/* Deleting all nodes of tree */
deltree(root);
}
運行結果如下:
二叉樹的重要性就不用多說啦;
我以前也學習過,但是一直沒有總結;
網上找到的例子,要麼是理論一大堆,然後是偽代碼實現;
要麼是復雜的代碼,沒有什麼解釋;
最終,還是靠FQ找到一些好的文章,參考地址我會在See Also部分給大家貼出來
Problem
假設我們要生成的二叉樹如下圖;
Solution
顯然,我們需要在節點保存的數據只有一個整數;
struct binary_tree {
int data ; // Data area
//TODO
};
所以在結構體裡面,我們的代碼應該類似上面的寫法;
通過觀察我們還發現,每一個節點都指向左右兩邊(除了最後的葉子節點外);
因此,我們需要讓它有兩個指針域;
可能你會想到類似如下的寫法;
struct binary_tree {
int data ; // Data area
void * left;
void * right;
};
上面的定義格式似乎是正確的,但是類型好像並不是我們想要的;
例如:當left指向左邊的子節點時,子節點應該也是一個包涵數據域的節點;
因此我們需要再定義一個與它本身相同的結構體;
struct binary_tree {
int data ; // Data area
struct binary_tree * left;
struct binary_tree * right;
};
所以,我們會這樣去定義它;
顯然,這是一個遞歸定義;
如果我們要實例化一個節點,我們可以:
struct binary_tree * tree;
顯然我們需要定義一個實例寫那麼長的類型名字,實在讓人難受;
因此,我們可以這樣;
typedef struct binary_tree node;
node * tree;
好啦!到此為止我們的數據域就定義好啦!你現在的代碼應該是下面的樣子啦;
復制代碼
struct binary_tree {
int data ; // Data area
binary_tree * left;
binary_tree * right;
};
typedef struct binary_tree node;
復制代碼
接下來我們需要把數據插入到對應的位置上;
我們希望樹左邊分支的的數據總是比樹右邊分支的要小;
至於為什麼我們暫時不解釋;
復制代碼
void insert(node ** tree, int val) {
node * temp = NULL;
if(!(*tree)) {
//TODO
return ;
}
if (val < (*tree)->data) {
//TODO
}else if (val > (*tree)->data) {
//TODO
}
}
復制代碼
因此我們代碼會像上面這樣寫;
第一個if語句判斷這個樹節點是否存在;
若是不存在,我們應該生成一個節點,然後添加到樹上來;
第二個if-else呢,則是判斷這個給定要存入的數據是大於當前節點的呢還是小於;
小於呢,存在左分支。大於存在右分支;
復制代碼
if(!(*tree)) {
temp = (node *)malloc(sizeof(node));
temp->left = temp->right = NULL;
temp->data = val;
*tree = temp;
return ;
}
復制代碼
分析上面代碼片段,我們發現temp的作用是臨時變量正如其名;
malloc分配內存,然後初始化節點左右指針域為空,以及數據域為val;
最後*tree=temp 把節點安裝到樹上;
並且返回上一級;
對於已經存在的樹節點,我們需要往左右兩分子擴展;
因此我們的代碼會是這樣的;
if (val < (*tree)->data) {
insert(&(*tree)->left,val);
}else if (val > (*tree)->data) {
insert(&(*tree)->right,val);
}
從代碼中可以看出,只對小於和大於兩個方向的數據進行操作;
你也許會考慮到萬一等於呢。
注意,在這裡應該是數據的唯一性有要求的,它類似於數學裡的集合,不會有重復的;
它的這種特性對我們往後要寫得單詞統計程序非常有幫助;
那麼這個函數的所有代碼如下:
復制代碼
void insert(node ** tree, int val) {
node * temp = NULL;
if(!(*tree)) {
temp = (node *)malloc(sizeof(node));
temp->left = temp->right = NULL;
temp->data = val;
*tree = temp;
return ;
}
if (val < (*tree)->data) {
insert(&(*tree)->left,val);
}else if (val > (*tree)->data) {
insert(&(*tree)->right,val);
}
}
復制代碼
節點創建好了,注意我們用malloc創建;
因此,我們是在堆中分配的內存,因此我們需要手動釋放;
那顯然需要用到free函數與之對應;
所以我們釋放節點的函數應該是這樣的;
void deltree(node * tree) {
if(tree) {
free(tree);
}
}
這樣似乎也沒有問題啦!但是仔細觀察我們發現;
直接釋放啦free就只是釋放啦根節點;
就好比,我們去拔花生;我們只是簡單的用剪刀把上面的葉子剪斷啦;
沒有想過把花生沿著根一直挖下去是不可能把所有花生弄出來的;
因此,我們需要這樣做;
復制代碼
void deltree(node * tree) {
if(tree) {
deltree(tree->left);
deltree(tree->right);
free(tree);
}
}
復制代碼
這樣我們找到左邊的根啦,又繼續往左邊找;
找不到啦,就往右邊找;
再找不到啦,就執行到free釋放節點然後返回上一級;
好啦!樹也有函數建啦,也有辦法“砍”啦!
接下來是怎麼展示我們的樹啦;
樹的遍歷有三種;
前,中,後;
void print_preorder(node * tree) {
if(tree) {
//TODO
}
}
首先我們需要判斷tree是否空;
要是空的,我們就沒有必要看裡面還有什麼數據啦;
復制代碼
void print_preorder(node * tree) {
if(tree) {
printf("%d\n",tree->data);
print_preorder(tree->left);
print_preorder(tree->right);
}
}
復制代碼
同樣的我們把中序和後序寫出來;
復制代碼
void print_preorder(node * tree) {
if(tree) {
printf("%d\n",tree->data);
print_preorder(tree->left);
print_preorder(tree->right);
}
}
void print_inorder(node * tree) {
if(tree) {
print_inorder(tree->left);
printf("%d\n",tree->data);
print_inorder(tree->right);
}
}
void print_postorder(node * tree) {
if(tree) {
print_postorder(tree->left);
print_postorder(tree->right);
printf("%d\n",tree->data);
}
}
復制代碼
好啦!該有的函數都有啦;
我們該寫測試函數啦;
復制代碼
int main(void)
{
node * root;
node * tmp;
//int i;
root = NULL;
/* Inserting nodes into tree */
insert(&root,9);
insert(&root,4);
insert(&root,15);
insert(&root,6);
insert(&root,12);
insert(&root,17);
insert(&root,2);
printf("Pre Order Display\n");
print_preorder(root);
printf("In Order Display\n");
print_inorder(root);
printf("Post Order Display\n");
print_postorder(root);
/* Deleting all nodes of tree */
deltree(root);
}
復制代碼
運行結果如下:
復制代碼
Pre Order Display
9
4
2
6
15
12
17
In Order Display
2
4
6
9
12
15
17
Post Order Display
2
6
4
12
17
15
9
復制代碼
Discussion
然後這個例子似乎太簡單了!它沒有對樹進行查詢的函數;
也沒有樹的高度進行測量;
但是,它的簡潔是為了更加容易理解;
可是呢!太簡潔了,以至於我們都不知道為什麼要把數據弄成樹形結構;
為什麼,難道線性結構的數據還不能解決我們身邊的問題嗎?
這個問題,不知道大家有沒有問過自己。反正我以前經常問自己;
那麼,為了讓大家理解存在樹形結構的數據的必要性;
我們,設想要統計C語言的關鍵字在代碼中出現的頻率;
我們會怎麼做呢??(這個問題我會在另一篇文章講解)
x Discussion 然後這個例子似乎太簡單了!它沒有對樹進行查詢的函數; 也沒有樹的高度進行測量; 但是,它的簡潔是為了更加容易理解; 可是呢!太簡潔了,以至於我們都不知道為什麼要把數據弄成樹形結構; 為什麼,難道線性結構的數據還不能解決我們身邊的問題嗎? 這個問題,不知道大家有沒有問過自己。反正我以前經常問自己; 那麼,為了讓大家理解存在樹形結構的數據的必要性; 我們,設想要統計C語言的關鍵字在代碼中出現的頻率; 我們會怎麼做呢??(這個問題我會在另一篇文章講解)