二叉樹的重要性就不用多說啦;
我以前也學習過,但是一直沒有總結;
網上找到的例子,要么是理論一大堆,然后是偽代碼實現;
要么是復雜的代碼,沒有什么解釋;
最終,還是靠翻牆找到一些好的文章,參考地址我會在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語言的關鍵字在代碼中出現的頻率;
我們會怎么做呢??(這個問題我會在另一篇文章講解)
See Alson
http://www.thegeekstuff.com/2013/02/c-binary-tree/