故事是这样开始的,项目经理有一天终于仍是拍拍我肩膀说: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,递归有什么缺点吗?
咱们下次再聊!