大话数据结构——二叉排序树的删除操作~2020.8.15

话不多说,先上代码再逐步分析。

完整代码:主函数已省略,仅包括删除操作

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 的左孩子重连即可,如下图:
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值