1,为什么要分析二叉查找树(又名:二叉排序树)?
正真分析开源代码或软件开发中,基于树的查找是难免会有的,现在多用的是
红黑树,而红黑树是基于平衡树的,而平衡树是基于查找树的,而查找树是基于二叉
树的,二叉树大家都很熟悉,所以先从查找树开始分析。这样才便于分析平衡树,最终
的分析对象将会是红黑树。
——————————————————————————————-
2,什么是二叉查找树?
递归定义如下:
(1),若左子树不为空,则左子数上所有结点的值均小于根结点的值。
(2),若右子树不为空,则右子数上所有结点的值均大于根结点的值。
(3),它的左右子树也分别是二叉查找树。
下面就是一个二叉查找树,任何一个子树,都是右孩子最大,根节点其次,左孩子最小。
——————————————————————————————-
3,二叉查找树的建立。
二叉查找树的创建和插入是联系在一起的,树的创建就是使用的插入方式。从一颗空树开始
不停地插入,直到称为一颗完整的树。
例如:
- 39 int create_bst(bstree**bst)
- 40 {
- 41 int a[]={24,12,53,28,45,90};
- 42 int length= ARRAY_SIZE(a);
- 43 int i = 0;
- 44
- 45 *bst = NULL;
- 46
- 47 for (i=0;i<length;i++){
- 48 insert_bstree(bst,a[i]);
- 49 }
- 50
- 51 return 0;
- 52 }
现在要创建一个6个节点的数,插入的顺序是24,12,53,28,45,90.
创建树的图示如下:
——————————————————————————————–
4,二叉查找树的插入。
实例代码:
- 20 void insert_bstree(bstree**root,int key)
- 21 {
- 22 if ((*root)==NULL) {
- 23 (*root)=(bstree *)malloc(sizeof(bstree));
- 24 if ((*root)==NULL)
- 25 printf(“file %s,line %d:malloc error\n”,__FILE__,__LINE__);
- 26 (*root)–>key= key;
- 27 (*root)–>lchild=NULL;
- 28 (*root)–>rchild=NULL;
- 29 } else {
- 30 if (key ==(*root)–>key){
- 31 ;
- 32 } else if (key<(*root)–>key){
- 33 insert_bstree(&((*root)–>lchild),key);
- 34 } else
- 35 insert_bstree(&((*root)–>rchild),key);
- 36 }
- 37 }
该插入的地方只可能是叶子节点,所以这儿使用了一个递归,先要找到插入的位置,
如果key值小,则往左走,key值大则往右走,直到为空,然后才找到位置,创建节
点将节点插入其中。
————————————————————————————————————-
5,二叉查找树的删除。
二叉查找树的删除比较麻烦,因为删除一个节点后还要保证其仍然是一个二叉查找树。
现在根据要删除节点的类型将情况分为四类。
情况1:删除的节点为叶子节点。
例如,上面的树上有三个叶子,分别是12,45,90,无论删除那个节点仍然是二叉查找树,
故只需将该节点释放,然后将其父节点的指针域做一些“善后”工作即可。实现代码如下:
- case 1:
- free(pnode);//释放节点。
- if (father–>lchild== pnode)//对其父节点做出指针域的善后工作。
- father–>lchild=NULL;
- else
- father–>rchild=NULL;
- goto out2;//退出
情况2:删除的节点有右子树没有左子树。
该情况也不算复杂,首先要考虑要删除的节点是否可能是根节点,如果是根节点,也好办,
因为这是一个只有“右半边”的查找树,直接把根给删了,根节点的右子树作为根节点。
如果要删除的节点不是根节点,也好办,如图中的28,直接将右子树交给自己的父亲
“照看”。实现代码如下:
- case 2:
- if (father ==NULL){//如果要删除的节点为根节点情况。
- *root= pnode–>rchild;//将要删除节点的右子树作为新的根。
- free(pnode);//释放要删除的节点。
- goto out1;//退出。
- }
- if (father–>lchild== pnode) //不是根节点的情况,
- father–>lchild= pnode–>rchild;//如果删除的节点是父节点的左孩子,现在就吧自己的右子树作为
- else //父节点的左孩子。
- father–>rchild= pnode–>rchild;//如果删除的节点是父节点的右孩子,现在就把自己的右子树作为
- free(pnode); //父节点的右孩子。
- goto out2;//释放节点后退出
情况3:删除的节点有左子树没有右子树。
该情况和情况2类似,故不作详细的分析了。
实现代码如下:
- case 3:
- if (father ==NULL){
- *root= pnode–>lchild;
- free(pnode);
- goto out1;
- }
- if (father–>lchild== pnode)
- father–>lchild= pnode–>lchild;
- else
- father–>rchild= pnode–>lchild;
- free(pnode);
- goto out2;
情况4:删除的节点有左子树有右子树。
该情况是这四种情况中最复杂的一种了,如何删除该节点后仍然保证其是二叉查找树呢。
例如上图的24和53.
算法思想如下:
<1>,找到要删除节点pnode的中序序列的直接前驱s。(53的就是45,24的就是12)
<2>,将pnode的左子树交给pnode的父亲“照顾”。
<3>,将pnode的右子树交给pnode的直接前驱S”照顾”。
经过以上的三步,就可以保证Pnode的左右子树各有“所养”。
算法实现代码如下:
- 132 case 4:
- 133 s = pnode–>lchild; //球pnode中序序列的直接前驱。
- 134 while (s–>rchild)
- 135 s = s–>rchild;
- 136 if (father ==NULL){ //如果删除的节点是根节点
- 137 *root = pnode–>lchild; //左子树成为新的根。
- 138 s–>rchild= pnode–>rchild;//右子树交给直接前驱S
- 139 free(pnode);
- 140 goto out2;
- 141 }
- 142 if (father–>rchild== pnode){
- 143 father–>rchild= pnode–>lchild;//左子树交给父节点。
- 144 s–>rchild= pnode–>rchild;//右子树交给直接前驱S
- 145 free(pnode);
- 146 goto out2;
- 147 }else{
- 148 father–>lchild= pnode–>rchild;//左子树交给父节点。
- 149 s–>lchild= pnode–>lchild;//右子树交给直接前驱S
- 150 free(pnode);
- 151 goto out2;
- 152
———————————————————————————————
6,实例代码。(完整代码)
bstree.rar