首先是二叉排序树的概念:
- 二叉排序树是一个二叉树,对每一个节点来说,右子树上的值都比根节点大,左子树上的值都比根节点小。
那么在查询某个值key时,就需要遵循以下的步骤
- 首先和当前节点的值比较,相等则退出,当前节点的地址就是要查询的地址。
- key比当前节点值小,那么key如果存在的话一定在当前节点的左子树中,继续查询左子树。
- key比当前节点值大,那么key如果存在的话一定在当前节点的右子树中,继续查询右子树。
非常类似二叉树遍历的描述,可以用递归来实现,注意设置退出条件:
typedef struct A
{
int data;
struct A *lchild;
struct A *rchild;
}BSTNode;
bool SearchBST(BSTNode* T,int key,BSTNode* pTreePre,BSTNode *&pResult)
{
if (T==NULL)
{
//如果当前查询的节点是空的,那么返回当前节点的父节点,方便进行插入操作
pResult=pTreePre;
return false;
}
if (T->data==key)
{
pResult=T;
return true;
}
if (key<T->data)
{
//比当前节点小,继续查询当前节点的左子树
return SearchBST(T->lchild,key,T,pResult);
}
if (key>T->data)
{
//比当前节点大,继续查询当前节点右子树
return SearchBST(T->rchild,key,T,pResult);
}
}
在查询函数中,除了树的根节点和要查询的值key,还传入了两个参数,一个当前查询节点的父节点pTreePre和查询后用于返回地址的pResult,根节点的父节点设置为NULL,为了以后插入方便,
pResult在调用时加了引用,这是因为函数中要修改pResult的值,当然也可以用指针的指针,只是那样看着就不舒服了。
可以看出来查询函数在查询成功的时候,pResult的值就是当前节点的值,那么在查询失败的时候呢,返回的就是最后一次查询的节点的地址。如果key比这个节点小,这个节点肯定没有左儿子,如果key比这个节点大,这个节点肯定没有右儿子,否则查询函数会继续查询下去。那么如果key是要插入的值呢,直接把key放在这个节点的左儿子或右儿子中就可以了。
代码:
bool InsertBST(BSTNode* &T,int key)
{
BSTNode *pFind=NULL;
BSTNode *pInsert=NULL;
//SearchBST()操作后,pFind的值就是当最后一次查询的节点的地址
if (!SearchBST(T,key,NULL,pFind))
{
pInsert=(BSTNode*)malloc(sizeof(BSTNode));
pInsert->data=key;
pInsert->lchild=pInsert->rchild=NULL;
if (!pFind)
T=pInsert;
else if (key<pFind->data)
pFind->lchild=pInsert;
else if (key>pFind->data)
pFind->rchild=pInsert;
return true;
}
return false;
}
那么删除操作呢,和查询类似,首先要查询到当前的节点,然后删除,所以删除函数的代码和查询时很像的,关键是删除节点比较麻烦,后面再讲
代码:
bool DeleteBST(BSTNode* &T,int key)
{
if (T==NULL)
return false;
if (key==T->data)
return Delete(T);
if (key<T->data)
return DeleteBST(T->lchild,key);
if (key>T->data)
return DeleteBST(T->rchild,key);
}
下面讲解删除当前节点的操作,即delete函数的流程。
- 如果没有左子树,那么直接把当前节点去掉,右子树根节点代替当前节点
- 如果没有右子树,那么直接把当前节点去掉,左子树根节点代替当前节点
- 如果左右子树都存在,那么就要比较麻烦了,可以找到以当前节点为根节点的树中,左子树中最大的点或者右子树的最小的点来代替当前的节点的值,同时删除左子树中最大的点(无右儿子)或者右子树的最小的点(无左儿子),同时要注意左子树中最大的点和右子树的最小的点为子树根节点的情况。
- 具体看代码
bool Delete(BSTNode* &T)
{
BSTNode* p=NULL;
BSTNode* rLittle=NULL;
//没有右子树,直接将左子树根节点当作T。
if (T->rchild==NULL)
{
p=T;
T=T->lchild;
free(p);
}
//没有左子树,直接将右子树根节点当作T。
else if (T->lchild==NULL)
{
p=T;
T=T->rchild;
free(p);
}
//左右子树都有,将右子树中最小的节点代替T,并删除右子树中最小的节点(这个最小节点肯定没有左子树)
else
{
p=T;
rLittle=T->rchild;
while (rLittle->lchild)
{
p=rLittle;
rLittle=rLittle->lchild;
}
//找到最小节点,替换
T->data=rLittle->data;
//删除最小节点,要注意最小节点是右子树根节点的情况。
if (p!=T)
p->lchild=rLittle->rchild;
else
p->rchild=rLittle->rchild;
free(rLittle);
}
return true;
}
最后是测试函数
int main(void)
{
BSTNode *T=NULL;
char a[10]={0};
int t=0;
while (1)
{
printf("输入要插入的结点:(n表示结束插入):");
gets(a);
if (*a=='n')
{
break;
}
t=atoi(a);
InsertBST(T,t);
printf("当前树:");
InTraverseBST(T);
printf("\n");
}
while (1)
{
printf("输入要删除的结点:(n表示结束删除):");
gets(a);
if (*a=='n')
{
break;
}
t=atoi(a);
DeleteBST(T,t);
printf("当前树:");
InTraverseBST(T);
printf("\n");
}
return 0;
}