话不多说,先上代码再逐步分析。
完整代码:主函数已省略,仅包括删除操作
typedef struct BiTNode{
int data;
BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
bool Delete(BiTree *p){
BiTree q,s;
if((*p)->lchild == NULL){
q = *p,*p=(*p)->rchild,delete q;
}
else if((*p)->rchild == NULL){
q = *p,*p=(*p)->lchild,delete p;
}
else{
q = *p,s = (*p)->lchild;
while(s->rchild != NULL){
q=s,s=s->rchild;
}
(*p)->data = s->data;
if(q==*p){
q->lchild = s->lchild;
}
else{
q->rchild = s->lchild;
}
}
return true;
}
bool DeleteBST(BiTree *T,int key){
/*T为指向二叉链表的指针*/
if(*T == NULL) return false;
else{
if(key == (*T)->data){
return Delete(T);
}
else if(key < (*T)->data){
return DeleteBST(&(*T)->lchild,key);/*运用递归继续寻找目标值,下同*/
}
else{
return DeleteBST(&(*T)->rchild,key);
}
}
}
运用递归查找二叉排序树中的key,找到时删除:
bool DeleteBST(BiTree *T,int key){
/*T为指向二叉链表的指针*/
if(*T == NULL) return false;
else{
if(key == (*T)->data){
return Delete(T);
}
else if(key < (*T)->data){
return DeleteBST(&(*T)->lchild,key);/*运用递归继续寻找目标值,下同*/
}
else{
return DeleteBST(&(*T)->rchild,key);
}
}
}
注:①形参为指向二叉链表的指针,BiTree可以理解为一个二叉链表(比较抽象),也可以理解为一个指向二叉树结点的指针(因为使用了typedef工具),如果理解BiTree为一个指向二叉树结点的指针,那么形参即为 “ 指向二叉树结点的指针的指针 ”,因此使用 " * " 符号为解除指针,使其变为一个指向二叉树结点的指针(即( *T )为一个指向二叉树结点的指针 )。
②函数的返回值为bool型变量,而return中用到了递归,因此return行所调用的Delete函数返回值也需要是bool型。
③举return DeleteBST(&(*T)->rchild,key);
一句为例,传入实参时不要忘记为加取地址符&,因为形参是指向结点的指针的指针,rchild为指向结点的指针。
对二叉排序树进行删除操作的主体函数
对删除结点进行操作的三种情况:
- 叶子结点。
- 仅有左或右子树的结点。
- 左右子树都有的结点。
上述第一,二种情况非常简单,在下放代码中的前两个if条件句当中的操作已经有所体现,难点在于第三种情况——左右子树均不为空,对应下放代码中的else代码块。
bool Delete(BiTree *p){
BiTree q,s;
if((*p)->lchild == NULL){
q = *p,*p=(*p)->rchild,delete q;
}
else if((*p)->rchild == NULL){
q = *p,*p=(*p)->lchild,delete p;
}
else{
q = *p,s = (*p)->lchild;
while(s->rchild != NULL){
q=s,s=s->rchild;
}
(*p)->data = s->data;
if(q==*p){
q->lchild = s->lchild;
}
else{
q->rchild = s->lchild;
}
delete s;
}
return true;
}
此处应对第三种情况的方法是,找到删除结点的直接前驱,用它的直接前驱来替代删除节点,并删除直接前驱。此处的直接前驱指的是什么呢?对二叉排序树进行中序遍历,可以的得到一个数据的升序打印结果,倘若要删除的结点,它的数据为47,打印结果为 …12 37 47 59 …,那么数据为37的结点即为它的直接前驱。模拟二叉排序树的中序遍历过程,我们便可以得出,要寻找这个直接前驱,先要把指针挪到待删结点的左孩子结点,之后不断寻找这个左孩子的右孩子结点,直到某个右孩子的右孩子结点为空,方才寻找成功(通俗地说,即为为想办法模拟中序遍历,想明白中序遍历的操作,便能轻松地理解)。
以下片段为寻找之直接前驱的操作:
BiTree q,s;
q = *p,s = (*p)->lchild;
while(s->rchild != NULL){
q=s,s=s->rchild;
}
之后有一步赋值操作(*p)->data = s->data;
,它的意图是用直接前驱来代替待删结点,即用直接前驱的数据覆盖待删结点,相当于已经抹除了待删结点,接下来则需要删除直接前驱结点(理解为重新连接二叉排序树更好,这个重新连接仅为局部的重连,并且仅仅需要连一次)。
重连操作如下:
if(q==*p){
q->lchild = s->lchild;
}
else{
q->rchild = s->lchild;
}
delete s;
一定要理清此处的q与s的关系,q指向的是寻找路径上s的前驱,即为s的 “ 前一个 ” 结点,准确的说,s一定是q的右孩子结点。由于最终的s没有右孩子(详见前面的while循环),而可能有左孩子,而s是前驱结点,倘若删除s,他的左孩子不能被一同删除,因此需重连回q。如果 q 和 ( *p ) 相等,即 ( *p ) 的左孩子即为最终的 s ,那么直接将 s 的左孩子与 q 的左孩子重连即可。如果不相等,则将 q 的右孩子与 s 的左孩子重连即可,如下图: