故事是這樣開始的,項目經理有一天終於仍是拍拍我肩膀說:node
不管你的鏈表寫得多麼的好,不管是多麼的靈活,我也得費老半天才查找到想要的數據;數組
這讓個人工做很是苦惱,據說有一種叫作二叉樹的數據結構,你看能不能給我弄來;數據結構
Probelm:函數
看看以下的數據:學習
咱們每次都要從頭至尾的查看咱們的數據鏈表裏面是否存在着目標數據;測試
每次都當心翼翼的懼怕漏掉哪一個,這樣的工做的確讓人煩惱;spa
Solution指針
再看看以下的解決解決方案;code
顯然,咱們很清楚本身要查找的目標大體會在那裏出現;blog
例如查找的目標是6,那麼我知道6小於9因此根本不會去看右邊的數據;
咱們繼續看6大於5因此找到啦目標;
換句話說咱們只對比了兩次找到啦目標;
而對於鏈表,咱們發現6排在了鏈表的尾部;
咱們花啦大量的時間。。
好啦!到此爲止咱們知道這樣的二叉樹的確是高效的;
說幹就幹,咱們來實現它;
typedef struct _node { int data; struct _node *left; struct _node *right; }Node;
節點定義能夠如上面的方式,這也是大多數二叉樹定義的方式;
可是試想一想,左右子樹的差異是什麼呢?
咱們都知道要是存在x節點,那麼x.left.data < x.data < x.right.data這個規律;
咱們爲何不充分的考慮考慮有沒有其餘能夠更好的定義咱們的左右節點呢?
讓它自動的區決定何時是左節點存數據,何時是右節點存數據呢;
咱們都知道,假設a=9;BOOL B = a < 10;那麼布爾值b是什麼呢?
咱們能夠寫下面這段代碼分析分析,注意咱們任然是在學習二叉樹,你沒有走神:)
#include <stdio.h> int main(void) { int a = 9; printf("a < 10 return :%d\n",a < 10); printf("a < 8 return :%d\n",a < 8); return 0; }
結果是:
顯然,咱們但願10存放在右子樹,8存放在左子樹;
因此你會想是這樣寫;
a = 8; new = 10; if (x->a < new){ x->right = new; }else{ x->left = new; }
固然,上面的代碼是接近僞代碼的表述方式啦!相信你不會去運行它的;
然而我想說上面的代碼是很是的繁瑣的,雖然感受很清晰;
試試下面的寫法吧:)
typedef struct _node { int data; struct _node *link[2]; }Node;
咱們從新定義啦節點;
注意咱們把左右節點改爲啦一個指針數組的形式;
咱們這樣寫的好處待會你就能體驗到;
如今咱們繼續定義一個樹;
typedef struct _tree{ struct _node *root; }Tree;
咱們的樹定義得更加簡單,注意咱們是先定義節點,再定義樹;
由於樹的定義須要用到節點結構體;
接下來咱們須要初始化咱們的樹;
Tree * init_tree() { Tree *temp = (Tree*)malloc(sizeof(Tree)); temp->root = NULL; return temp; }
這種寫法是很是簡潔的,可是它也有弊端;
它沒有錯誤處理,例如:萬一malloc分配內存錯誤;
一般咱們可能會這樣寫:
Tree * init_tree() { Tree *temp = (Tree*)malloc(sizeof(Tree)); if(!temp) return NULL; temp->root = NULL; return temp; }
或者這樣寫:
Tree * init_tree() { Tree *temp = (Tree*)malloc(sizeof(Tree)); if(temp){ temp->root = NULL; } return temp; }
都是爲啦!讓程序運行的時候萬一出問題,咱們知道錯誤在哪?
你甚至能夠寫上提示信息,可是我們的核心問題不是這些,因此咱們都假設程序不會出現這些問題;
咱們如今須要作的是建立節點;
Node * make_node(int data) { Node *temp = (Node*)malloc(sizeof(Node)); temp->link[0] = temp->link[1] = NULL; temp->data = data; return temp; }
建立節點也比較容易理解,首先申請內存,而後初始化數據成員;
最後返回該節點;
下面是如何插入這個建立好的節點呢?
咱們須要一個函數大概會叫作insert,但願你可以經過名字大概能找到感受;
Node * insert_recursive(Node *root,int data) { if(root == NULL){ root = make_node(data); }else if(root->data == data){ return root; }else{ int dir = root->data < data; root->link[dir] = insert_recursive(root->link[dir],data); } return root; }
注意,但願你可以一眼看出recursive的意思是什麼。我不會告訴你的,咱們但願你本身查查哈;
咱們分析分析代碼吧;
第一個if語句,告訴咱們若是這個節點爲空,那麼就建立它;
else if語句,告訴咱們若是這個數據與樹上找的一致,那麼就返回這個節點給調用者;
else語句,告訴咱們的就稍微複雜一點啦;
它首先聲明一個dir的整形變量,咱們能夠偷偷告訴你它實際上是一個只有兩種可能值得整形;
咱們一般均可以用BOOL類型替換它;你能夠發現它立刻就露餡啦;
後面立刻賦值,root->data < data;是一個表達式,這個表達式只會返回1,0;
但願你還記得何時返回1,何時返回0;(咱們前面寫過測試的哦)
最後,這個節點已經存放到該存放的位置啦;
咱們不該該直接去調用點,咱們須要的是去操做一顆樹;
所以咱們須要包裝一下咱們的insert函數;
int insert(Tree * tree, int data) { tree->root = insert_recursive(tree->root,data); return 1; }
很簡單就ok啦;
爲啦看到咱們的成果,你必定但願它可以打印些什麼,並且證實它的確是一顆樹;
咱們須要一種叫作中序遍歷的方式打印它,這種方式的好處是你能夠看到它會按照從小到大得方式輸出;
void print_inorder_recursive(Node *root) { if(root){ print_inorder_recursive(root->link[0]); printf("data:%d\n",root->data); print_inorder_recursive(root->link[1]); } return ; }
同理,咱們任然寫一個包裝函數;
void print_inorder(Tree *tree) { print_inorder_recursive(tree->root); return ; }
好啦,最後測試一下咱們的二叉樹;
int main(void) { Tree * tree = init_tree(); insert(tree,9); insert(tree,7); insert(tree,3); insert(tree,19); insert(tree,29); insert(tree,82); insert(tree,1); print_inorder(tree); return 0; }
編譯運行,看看咱們的結果以下;
Discussion
咱們的功能是實現啦!可是這遠遠不夠的;
例如,
1,咱們該證怎麼查找一個目標節點呢?
2,咱們該如何刪除一個節點呢?
3,插入的方式只能用遞歸嗎?
4,遞歸有什麼缺點嗎?
咱們下次再聊!