算法学习记录-查找——二叉排序树(Binary Sort Tree)
二叉排序树 也称为 二叉查找数。
它具有以下性质:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
它的左、右子树也分别为二叉排序树。
之前的查找折半查找、斐波那契查找、插值查找的前提条件就是序列为有序,为顺序存储结构。我们在查找一章提过,查找还有动态查找,比如插入和删除操作,
进行这些操作对顺序存储结构效率不那么高。能不能有一种既静态查找效率高,动态查找效率也高呢?
联想之前的堆排序,人们创造出一个堆这样的结构来提高排序效率。(佩服他们的思维)堆结构建立在二叉树上,之前一直都不知道二叉树是怎么用的,
看到这里二叉树的作用就体现出来了,我们定义二叉排序树来实现动、静态查找效率都高的结构。
先举一个例子:
先看看一个无序序列排列好的二叉排序树:
上图中,上面数组是原始数组,下面的二叉排序树是按照上面的原始数组来构建的。
1.我们如果按中序遍历得到的结果就是这个序列的 顺序排序结果。{0,1,2,3,4,5,6,7,8,9}
2.我们实现静态查找,查找关键字8:
从根结点开始,根结点(6),8比6大,往右(到结点(7))。
当前结点(7),8比该结点大,往右(到结点(8))。
找到8。
这里有点类似折半、斐波那契、插值查找的思想。所以它的静态查找效率比较高。
3.动态查找(插入和删除)
如果要插入记录10到这个序列中,我们只需要给它找一个合适的位置,在对插入后的二叉树中序遍历的时候,依旧是一个顺序排列表。
插入10,比根结点(6)大,向右(到结点7)
比当前结点(7)大,向右(到结点8)
比当前结点(8)大,向右(到结点9)
比当前结点(9)大,向右,无孩子,则建立孩子并赋值10.
完成后,该树的中序遍历依然是一个顺序表,对原先树几乎是没有影响,所以其插入操作也非常的高效。
对于其删除:
由于二叉排序树在删除一个记录时候要保证其中序遍历的顺序不受影响,操作要分几种情况,会稍微麻烦点,这个后面再讲。
所以综合看来,这样的二叉排序树对 静动态操作 基本都高效,避免了有序表的插入删除时候的大量记录的移动。
到这里,看到了二叉排序数的好处,
问题就来了!
怎么从一个无序的数列构建这样的二叉排序树?
通过上面过程,基本能够看出无序表到二叉排序树的构建过程。
其实质就是查找与插入操作。先查看结点是否存在,然后比较,再插入。
知道了思想,写程序就不难了。
插入算法程序如下:
1 bool SearchBST(pBinNode T,myDataType key,pBinNode *pNode)//pNode是指针,需要修改,所以用二级指针,该指针用来记录要查找结点,若不存在则返回将插入结点的父结点。 2 { //根结点指针,寻找的值,记录父节点值 3 if (T == NULL)//如果是空树,则父结点指向NULL 4 { 5 *pNode = NULL; 6 return false; 7 } 8 pBinNode crntNode = T; 9 10 while(crntNode)//找到要插入结点的父结点 11 { 12 if (crntNode->data == key) 13 { 14 return true; 15 } 16 else if(crntNode->data > key) 17 { 18 *pNode = crntNode; 19 crntNode = crntNode->lchd; 20 } 21 else if (crntNode->data < key) 22 { 23 *pNode = crntNode; 24 crntNode = crntNode->rchd; 25 } 26 } 27 return false; 28 } 29 30 bool insertBSTNode(pBinNode *T,int key,int index)//第一个参数之所以为二级指针,因为在该函数中有malloc生成的是指针,而要将该指针的值在函数中赋给另一个指针,所以要用指针的指针才能接收 31 { 32 pBinNode n=NULL; 33 pBinNode p=NULL; 34 if (false == SearchBST(*T,key,&p))//找到要插入的结点的父结点p 35 { 36 n = (pBinNode)malloc(sizeof(BinNode)); 37 if (n == NULL) 38 { 39 printf("error:no more memery!\n"); 40 return false; 41 } 42 (*n).data = key; 43 (*n).index = index; 44 (*n).lchd = NULL; 45 (*n).rchd = NULL; 46 47 if (p == NULL) 48 { 49 *T = n; 50 } 51 else if(key < p->data) 52 { 53 p->lchd = n; 54 } 55 else if (key > p->data) 56 { 57 p->rchd = n; 58 } 59 return true; 60 } 61 else 62 { 63 return false; 64 } 65 }
仔细想想这样的Search是不是可以用递归的调用方法。
删除操作,对于删除有三种情况:
- 欲删除的结点是叶子结点,由于删除不破坏树结构,只需要修改其父结点指向为NULL,然后free该欲删除的结点。
- 欲删除的结点只有左子树或者只有右子树,只需要将欲删除的结点的父结点孩子指针指向其(左/右)子树,然后free欲删除的结点。
- 欲删除的结点既有左子树又有右子树,此时不能如上面两种方法处理。
最简单的方法就是将他的左右子树重新排序,按二叉排序树条件组成新的子树,再连接到树上。这样做会非常的复杂,假如删除的是根结点,那么意味着整个树将要重新构成。
我们想想二叉排序树的中序遍历,其中序遍历就是无序序列的有序集合。
我们删除其中一个结点,通过调整方法,尽可能减少破坏树的结构,我们只要在调整后,如果能保证调整后的树的中序遍历依然是有序集合,那么这样的调整就能成功。
举几个例子:
代码:
1 void DeleteNode(pBinNode *fNode,pBinNode *sNode)//参数分别为 父亲 和 孩子 2 { 3 pBinNode q = NULL; 4 pBinNode s = NULL; 5 if ((*sNode)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行 6 { 7 q = *sNode; 8 (*fNode)->rchd = (*sNode)->lchd; 9 free(q); 10 } 11 else if((*sNode)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。 12 { 13 q=*sNode; 14 (*fNode)->rchd = (*sNode)->rchd; 15 free(q); 16 } 17 else //左右都不为空! 18 { 19 q = *sNode; 20 s = (*sNode)->lchd;//其前驱一定是在左子树上的最后一个右分支 21 while(s->rchd) 22 { 23 q = s; 24 s=s->rchd; 25 } 26 (*sNode)->data = s->data; 27 if (q != *sNode) 28 { 29 q->rchd = s->lchd; 30 } 31 else 32 { 33 q->lchd= s->lchd; 34 } 35 free(s); 36 37 } 38 } 39 40 bool DeleteBST(pBinNode *T,int key)//找是否存在关键字 41 { 42 if (!(*T)) 43 { 44 return false; 45 } 46 47 pBinNode s = *T;//son 48 pBinNode f = NULL;//father 49 50 while(s) 51 { 52 if(key == s->data) 53 { 54 //删除 55 DeleteNode(&f,&s); 56 return true; 57 } 58 else if (s->data > key) 59 { 60 f = s; 61 s = s->lchd; 62 } 63 else if (s->data < key) 64 { 65 f = s; 66 s = s->rchd; 67 } 68 } 69 return false;//no value in array 70 }
完整程序:
1 #include <stdlib.h> 2 3 typedef int myDataType; 4 //myDataType src_ary[10] = {9,1,5,8,3,7,6,0,2,4}; 5 //myDataType src_ary[10] = {1,2,3,4,5,6,7,8,9,10}; 6 //myDataType src_ary[10] = {10,9,8,7,6,5,4,3,2,1}; 7 8 myDataType src_ary[10] = {6,1,7,5,3,8,9,0,2,4}; 9 10 //****************树结构***********************// 11 typedef struct BTS{ 12 myDataType data; 13 int index; 14 struct BTS *lchd,*rchd; 15 }BinNode,*pBinNode; 16 17 void prt_ary(myDataType *ary,int len) 18 { 19 int i=0; 20 while(i < len) 21 { 22 printf(" %d ",ary[i++]); 23 } 24 printf("\n"); 25 } 26 27 bool SearchBST(pBinNode T,myDataType key,pBinNode *pNode)//pNode是指针,需要修改,所以用二级指针,该指针用来记录要查找结点,若不存在则返回将插入结点的父结点。 28 { //根结点指针,寻找的值,记录父节点值 29 if (T == NULL)//如果是空树,则父结点指向NULL 30 { 31 *pNode = NULL; 32 return false; 33 } 34 pBinNode crntNode = T; 35 36 while(crntNode)//找到要插入结点的父结点 37 { 38 if (crntNode->data == key) 39 { 40 return true; 41 } 42 else if(crntNode->data > key) 43 { 44 *pNode = crntNode; 45 crntNode = crntNode->lchd; 46 } 47 else if (crntNode->data < key) 48 { 49 *pNode = crntNode; 50 crntNode = crntNode->rchd; 51 } 52 } 53 return false; 54 } 55 56 bool insertBSTNode(pBinNode *T,int key,int index)//第一个参数之所以为二级指针,因为在该函数中有malloc生成的是指针,而要将该指针的值在函数中赋给另一个指针,所以要用指针的指针才能接收 57 { 58 pBinNode n=NULL; 59 pBinNode p=NULL; 60 if (false == SearchBST(*T,key,&p))//找到要插入的结点的父结点p 61 { 62 n = (pBinNode)malloc(sizeof(BinNode)); 63 if (n == NULL) 64 { 65 printf("error:no more memery!\n"); 66 return false; 67 } 68 (*n).data = key; 69 (*n).index = index; 70 (*n).lchd = NULL; 71 (*n).rchd = NULL; 72 73 if (p == NULL) 74 { 75 *T = n; 76 } 77 else if(key < p->data) 78 { 79 p->lchd = n; 80 } 81 else if (key > p->data) 82 { 83 p->rchd = n; 84 } 85 return true; 86 } 87 else 88 { 89 return false; 90 } 91 } 92 93 94 95 void DeleteNode(pBinNode *fNode,pBinNode *sNode)//参数分别为 父亲 和 孩子 96 { 97 pBinNode q = NULL; 98 pBinNode s = NULL; 99 if ((*sNode)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行 100 { 101 q = *sNode; 102 (*fNode)->rchd = (*sNode)->lchd; 103 free(q); 104 } 105 else if((*sNode)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。 106 { 107 q=*sNode; 108 (*fNode)->rchd = (*sNode)->rchd; 109 free(q); 110 } 111 else //左右都不为空! 112 { 113 q = *sNode; 114 s = (*sNode)->lchd;//其前驱一定是在左子树上的最后一个右分支 115 while(s->rchd) 116 { 117 q = s; 118 s=s->rchd; 119 } 120 (*sNode)->data = s->data; 121 if (q != *sNode) 122 { 123 q->rchd = s->lchd; 124 } 125 else 126 { 127 q->lchd= s->lchd; 128 } 129 free(s); 130 131 } 132 } 133 134 bool DeleteBST(pBinNode *T,int key)//找是否存在关键字 135 { 136 if (!(*T)) 137 { 138 return false; 139 } 140 141 pBinNode s = *T;//son 142 pBinNode f = NULL;//father 143 144 while(s) 145 { 146 if(key == s->data) 147 { 148 //删除 149 DeleteNode(&f,&s); 150 return true; 151 } 152 else if (s->data > key) 153 { 154 f = s; 155 s = s->lchd; 156 } 157 else if (s->data < key) 158 { 159 f = s; 160 s = s->rchd; 161 } 162 } 163 return false;//no value in array 164 } 165 166 int _tmain(int argc, _TCHAR* argv[]) 167 { 168 int i; 169 pBinNode T=NULL;//排序树的根结点 170 171 for (i=0;i<10;i++)//创建树 172 { 173 if (false == insertBSTNode(&T,src_ary[i],i)) 174 { 175 printf("error:src_ary[%d]=%d\n",i,src_ary[i]); 176 } 177 } 178 DeleteBST(&T,6);//删除6 179 //DeleteBST(&T,8); 180 getchar(); 181 return 0; 182 }
测试结果:
在178出设置断点。
程序运行到178行之前,变量结果:
运行到179,执行了178行后
另外两种可以自己测试下。
补充,另外还有一种方法写程序,递归
1 //******************************** 2 int DeleteNode_R(pBinNode *p) 3 { 4 // printf("data=%d\n",(*p)->data); 5 // return 0; 6 pBinNode q = NULL; 7 pBinNode s = NULL; 8 if ((*p)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行 9 { 10 q = *p; 11 (*p) = (*p)->lchd; 12 free(q); 13 } 14 else if((*p)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。 15 { 16 q=*p; 17 (*p) = (*p)->rchd; 18 free(q); 19 } 20 else //左右都不为空! 21 { 22 q = *p; 23 s = (*p)->lchd;//其前驱一定是在左子树上的最后一个右分支 24 while(s->rchd) 25 { 26 q = s; 27 s=s->rchd; 28 } 29 (*p)->data = s->data; 30 if (q != *p) 31 { 32 q->rchd = s->lchd; 33 } 34 else 35 { 36 q->lchd= s->lchd; 37 } 38 free(s); 39 } 40 return 1; 41 } 42 43 int DeleteBST_R(pBinNode *T,int key) 44 { 45 if (!(*T)) 46 { 47 return false; 48 } 49 else 50 { 51 if ((*T)->data == key) 52 { 53 return DeleteNode_R(T); 54 } 55 else if(key > (*T)->data) 56 { 57 return DeleteBST_R(&(*T)->rchd,key); 58 } 59 else if (key < (*T)->data) 60 { 61 return DeleteBST_R(&(*T)->lchd,key); 62 } 63 } 64 }