平衡二叉树之二(删除节点)

类似于添加操作,从平衡二叉树中删除节点也分为两步,第一步完成节点的删除,第二步找到因为删除而导致不满足平衡二叉树要求的子树并对其进行调整。

一、 删除节点

从平衡二叉树中删除节点更为复杂。首先第一步需要找到要删除的节点x,并分情况进行处理:

  1. 如果要删除的节点为叶子节点,就找到了要删除的节点
  2. 如果要删除的节点为只有一棵子树的节点就找到了要删除的节点
  3. 如果要删除的节点既有左子树,又有右子树,则
  • 如果该节点的平衡因子为0或者1,则找到其左子树中具有最大值的节点max(我们只讨论有序平衡二叉树,并且有序平衡二叉树中任意一个节点的左子树上的所有节点的值小于该节点的值,右子树上所有节点的值大于该节点的值),将max的内容与x的内容交换(只替换保存的真正的数据,不替换指针,平衡因子等用于管理目的的信息),并且max即为新的要删除的节点。由于树是有序的,因而这样找到的节点要么是一个叶子节点,要么是一个没有右子树的节点。
  • 如果该节点的平衡因子为-1,则找到其右节点中具有最小值的节点min,将min的内容与x的内容交换,并且min即为新的要删除的节点。由于树是有序的,因而这样找到的节点要么是一个叶子节点,要么是一个没有左子树的节点。

在找到要删除的节点后下一步就是删除节点,假设要删除的节点为delete,其父节点为parent,显然此时delete要么是一个叶子节点,要么是一个只有一棵子树的节点:

  • 如果delete是一个叶子节点,则直接删除它
  • 如果delete是一个只有一棵子树的节点,则将delete删除,delete节点的子树的根将成为parent的子节点

无论是这两种情形中的哪一种都会导致节点parent的以delete节点为根节点的子树的高度降低,因此下一步就是处理高度降低的影响。假设删除节点导致以节点A为根的子树的高度降低了,并且A的父节点为B则:

  • 如果B为NULL,即A已经为根节点,则删除完成
  • 否则考察B的平衡因子:
    • 平衡因子为0,只需要调整平衡因子即可完成删除
    • 平衡因子表示以A为子树的根的高度较高,则B的新的平衡因子为0,以B为根的子树的高度降低,需要回溯往前继续处理高度降低的影响
    • 平衡因子表示以A为子树的根的高度较矮,则以B为根的子树违背了平衡二叉树的要求,需要对以B为根的子树进行调整,如果调整完后得到的新的子树的高度和原来以B为根的子树的高度相同,则删除完成,否则新得到的子树的高度与原来的以B为根的子树的高度相比必然是减小了,因而还需要继续往前回溯处理高度降低的影响
比如要下从下图中删除节点20


首先找到替换20被删除的节点15,并将二者内容替换,如下图所示:


然后删除节点20得到下图:


在删除节点20后,节点10违反了平衡二叉树的性质,对以10为根节点的子树进行调整(类似于插入时,需要先做一次左旋再做一次右旋)可得下图:


二、删除中的调整

删除节点时的调整基本上类似于插入时的调整,但是也有些区别,以从节点A的左子树删除节点导致其左子树高度降低而需要调整为例进行分析。并分以下情况:

2.1 A的右子树的平衡因子为-1

该情形如下图所示:


该情形很简单,只需要做一次左旋即可。

首先分析调整对以A为根的子树的高度的影响,显然调整后子树的高度降低了。

再分析调整对参与调整的节点的平衡因子的变化,此时只需要考虑孩子域被修改的节点,因为只有这种节点的平衡因子才会变化。在这种调整中只有A以及其右子树的根节点的孩子域会发生变化,因而只需要分析它们两个的平衡因子的变化(以上图为例来分析,要分析的两个节点为20,30):

  • 假设以节点20为根的子树的高度为h1,则删除节点前:
    • 由于节点20的平衡因子为-1(否则从左子树删除节点导致其左子树高度降低就不需要进行调整),因此其右子树(以30为根节点的子树)的高度为h1-1,其左子树(以10为根节点的子树)的高度为h1-2
    • 对于节点20的右孩子节点30来说,由于它的平衡因子为-1,因此它的左子树(以25为根节点的子树)的高度为(h1-1)- 2,它的右子树(以40为根节点的子树)的高度为(h1-1)- 1
  • 删除节点后:
    • 节点20的左子树(以10为根节点的子树)的高度变为h1-2 -1
  • 调整后
    • 节点10和节点25分别成为节点20的左右子树,分别以二者为根的子树的高度差为h1 - 2 - 1 - ((h1-1)- 2) = 0
    • 以节点20为根节点的子树的高度为h1-2 -1 + 1
    • 节点20和节点40分别成为节点30的左右子树,分别以二者为根的子树的高度差为(h1-2 -1 + 1) -  ( (h1-1)- 1 ) = 0
因而该种情形的最终结果是子树高度降低,参与调整的节点A以及其右子树的根节点在调整后的平衡因子都为0,其它节点平衡因子不变。

2.2 A的右子树的平衡因子为0

该情形如下图所示:


类似于上一种情形,该情形只需要做一次左旋即可。

首先分析调整对以A为根的子树的高度的影响,显然调整后子树的高度不变。

再分析调整对参与调整的节点的平衡因子的变化,此时只需要考虑孩子域被修改的节点,因为只有这种节点的平衡因子才会变化。在这种调整中只有A以及其右子树的根节点的孩子域会发生变化,因而只需要分析它们两个的平衡因子的变化(以上图为例来分析,要分析的两个节点为20,30):

  • 假设以节点20为根的子树的高度为h1,则删除节点前:
    • 由于节点20的平衡因子为-1(否则从左子树删除节点导致其左子树高度降低就不需要进行调整),因此其右子树(以30为根节点的子树)的高度为h1-1,其左子树(以10为根节点的子树)的高度为h1-2
    • 对于节点20的右孩子节点30来说,由于它的平衡因子为0,因此它的左右子树(以25为根节点的子树和以40为根节点的子树)的高度都为(h1-1)- 1
  • 删除节点后:
    • 节点20的左子树(以10为根节点的子树)的高度变为h1-2 -1
  • 调整后
    • 节点10和节点25分别成为节点20的左右子树,分别以二者为根的子树的高度差为h1 - 2 - 1 - ((h1-1)- 1) = -1
    • 以节点20为根节点的子树的高度为h1-1 -1 + 1
    • 节点20和节点40分别成为节点30的左右子树,分别以二者为根的子树的高度差为(h1- 1 -1 + 1) -  ( (h1-1)- 1 ) = 1
因而该种情形的最终结果是子树高度不变,参与调整的节点A以及其右子树的根节点在调整后的平衡因子分别为-1,1,其它节点平衡因子不变。

2.3 A的右子树的平衡因子为1

该情形如下图所示:


和前两种情形不同,该种情形下需要做两次调整,一次右旋、一次左旋。

首先分析调整对以A为根的子树的高度的影响,调整后子树的高度降低。

再分析调整对参与调整的节点的平衡因子的变化,此时只需要考虑孩子域被修改的节点,因为只有这种节点的平衡因子才会变化。在这种调整中只有A,A的右孩子,A的右孩子的左孩子的孩子域会发生变化,因而只需要分析它们的平衡因子的变化(以上图为例来分析,要分析的三个节点为20,30,25):

  • 假设以节点20为根的子树的高度为h1,则删除节点前:
    • 由于节点20的平衡因子为-1(否则从左子树删除节点导致其左子树高度降低就不需要进行调整),因此其右子树(以30为根节点的子树)的高度为h1-1,其左子树(以10为根节点的子树)的高度为h1-2
    • 对于节点20的右孩子节点30来说,由于它的平衡因子为1,因此它的左子树(以25为根节点的子树)的高度为h1-1-1,它的右子树(以40为根节点的子树)的高度为(h1-1)- 2
  • 删除节点后:
    • 节点20的左子树(以10为根节点的子树)的高度变为h1-2 -1
  • 调整后,节点10和节点25的左孩子分别成为节点20的左右子树,节点25的右孩子和节点40分别成为节点30的左右孩子,节点20和节点30分别成为节点25(即新的根)的左右子树。则:
    • 如果节点10不存在,即删除节点后20的左子树消失,则此种情形下,删除完节点后,节点23,25,40都不能存在,调整完后三者平衡因子都为0
    • 如果节点10存在,则节点25的左右子树至少有一个不能为空,此时:
      • 如果删除前节点25的平衡因子为1,则此时
        • 节点20的平衡因子为: “以节点10为根的子树的高度 - 以节点25的左孩子为根的子树的高度”= h1-2 -1 - ((h1 -1 -1) -1)= 0
        • 节点30的平衡因子为: “以节点25的右孩子为根的子树的高度 - 以节点40为根的子树的高度”= ((h1-1-1)- 2)- ((h1 -1) - 2)= -1
        • 节点25的平衡因子为:“以节点20为根的子树的高度 - 以节点30为根的子树的高度”= ((h1-2 -1) + 1)- (((h1 -1) - 2 )+ 1) = 0
      • 如果删除前节点25的平衡因子为0,则此时:
        • 节点20的平衡因子为: “以节点10为根的子树的高度 - 以节点25的左孩子为根的子树的高度”= h1-2 -1 - ((h1 -1 -1) -1)= 0
        • 节点30的平衡因子为: “以节点25的右孩子为根的子树的高度 - 以节点40为根的子树的高度”= ((h1-1-1)- 1)- ((h1 -1) - 2)= 0
        • 节点25的平衡因子为:“以节点20为根的子树的高度 - 以节点30为根的子树的高度”= ((h1-2 -1) + 1)- (((h1 -1) - 2 )+ 1) = 0
      • 如果删除前节点25的平衡因子为-1,则此时:
        • 节点20的平衡因子为: “以节点10为根的子树的高度 - 以节点25的左孩子为根的子树的高度”= h1-2 -1 - ((h1 -1 -1) -2)= 1
        • 节点30的平衡因子为: “以节点25的右孩子为根的子树的高度 - 以节点40为根的子树的高度”= ((h1-1-1)- 1)- ((h1 -1) - 2)= 0 
        • 节点25的平衡因子为:“以节点20为根的子树的高度 - 以节点30为根的子树的高度”= ((h1-2 -1) + 1)- (((h1 -1) - 2 )+ 1) = 0
因而该种情形的最终结果是子树高度降低,假设被调整的子树的根节点为A,其右孩子为B,B的左孩子为C,则调整后A,B,C的平衡因子分别为:

  • C的平衡因子为0
  • 如果删除前C的平衡因子为0,则A和B的平衡因子为0
  • 如果删除前C的平衡因子为1,则A的平衡因子为0,B的平衡因子为-1
  • 如果删除前C的平衡因子为-1,则A的平衡因子为1,B的平衡因子为0

从右子树删除和从左子树是对称的。

从上述分析可以看出从平衡二叉树中删除节点的时间复杂度和添加相同也是O(logn)

  • 12
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
#include <iostream> #include<stack> #include<queue> using namespace std; const int LH=1; //左子比右子高1 const int EH=0; //左右子一样高 const int RH=-1;//右子比左子高1 const int MAX_NODE_NUM=20; //结点数目上限 class AvlNode { int data; int bf; //平衡因子 AvlNode *lchild; AvlNode *rchild; friend class AVL_Tree; }; class AVL_Tree { public: int Get_data(AvlNode *p) { return p->data; } void Create_AVl(AvlNode *&T) //建 { cout<<"输入平衡二叉树的元素,输入-1代表结束输入:"; int num[MAX_NODE_NUM]; int a,i=0; while(cin>>a && a!=-1) { num[i]=a; i++; } if(num[0]==-1) { cout<<"平衡为空"<<endl; T=NULL; return; } int k=i; bool taller=false; for(i=0;i<k;i++) Insert_Avl(T,num[i],taller);//逐个进行插入,插入过程看下面的示意图 cout<<"_____建完成____"<<endl; } void L_Rotate(AvlNode *&p) { //以p为根节点的二叉排序进行单向左旋处理 AvlNode *rc=p->rchild; p->rchild=rc->lchild; rc->lchild=p; p=rc; } void R_Rotate(AvlNode *&p) { //以p为根节点的二叉排序进行单向右旋处理 AvlNode *lc=p->lchild; p->lchild=lc->rchild; lc->rchild=p; p=lc; } void Left_Balance(AvlNode *&T) { //以T为根节点的二叉排序进行左平衡旋转处理 AvlNode *lc,*rd; lc=T->lchild; switch(lc->bf) { case LH: //新结点插在T的左孩子的左子上,做单向右旋处理 T->bf=lc->bf=EH; R_Rotate(T); break; case RH: //新结点插在T的左孩子的右子上,要进行双旋平衡处理(先左后右) rd=lc->rchild; switch(rd->bf) { case LH: //插在右子的左孩子上 T->bf=RH; lc->bf=EH; break; case EH: T->bf=lc->bf=EH; break; case RH: T->bf=EH; lc->bf=LH; break; } rd->bf=EH; L_Rotate(T->lchild);//先对T的左子进行单向左旋处理 R_Rotate(T); //再对T进行单向右旋处理 } } void Right_Balance(AvlNode *&T) { //以T为根节点的二叉排序进行右平衡旋转处理 AvlNode *rc,*ld; rc=T->rchild; switch(rc->bf) { case RH: //新结点插在右孩子的右子上,进行单向左旋处理 T->bf=rc->bf=EH; L_Rotate(T); break; case LH: //新结点插在T的右孩子的左子上,要进行右平衡旋转处理(先右再左) ld=rc->lchild; switch(ld->bf) { case LH: T->bf=LH; rc->bf=EH; break; case EH: T->bf=rc->bf=EH; break; case RH: T->bf=EH; rc->bf=RH; break; } ld->bf=EH; R_Rotate(T->rchild);//先对T的右子进行单向右旋处理 L_Rotate(T); //再对T进行单向左旋处理 } } bool Insert_Avl(AvlNode *&T,int num,bool &taller) //插入 { //若在平衡二叉树中不存在结点值和num一样大小的结点 //则插入值为num的新结点,并返回true //若因为插入而使得二叉排序失去平衡,则做平衡旋转处理 //taller反映是否长高 if(!T) { //插入新结点长高,taller为true T=new AvlNode; T->data=num; T->lchild=T->rchild=NULL; T->bf=EH; taller=true; } else { if(num==T->data) { //不重复插入 taller=false; return false; } if(num<T->data) //继续在T的左子中进行搜索 { if(!Insert_Avl(T->lchild,num,taller))//插入不成功 return false; if(taller) //已插入T的左子,且左子长高 { switch(T->bf) { case LH: /*————————————————————— / 插入前左子高于右子,需要进行做平衡处理 / 不管是单向左旋处理,还是先左后右平衡处理 / 处理结果都是使得插入新结点后,的高度不变 /—————————————————————*/ Left_Balance(T); taller=false; break; case EH: //插入前左右子等高,现在插入新街点后,左子比右子高 T->bf=LH; taller=true; break; case RH: //插入前右子比左子高,现在新结点插入左子后,变为左右子等高 T->bf=EH; taller=false; break; } } } else { //num>T->data 在T的右子中继续搜索 if(!Insert_Avl(T->rchild,num,taller)) return false; if(taller) { switch(T->bf) { case LH: //插入前左子比右子高,现在插入T的右子后,左右子等高 T->bf=EH; taller=false; break; case EH: //插入前左右子等高,现在插入后,右子比左子高 T->bf=RH; taller=true; break; case RH: //插入前右子比坐子高,插入后,排序失去平衡,需要进行右平衡处理 Right_Balance(T); taller=false; break; } } } } return true; } bool Search_Avl(AvlNode *T,int num,AvlNode *&f,AvlNode *&p) //搜索 { //用p带回查找到的顶点的地址,f带回p的双亲结点 p=T; while(p) { if(p->data==num) return true; if(p->data>num) { f=p; p=p->lchild; } else { f=p; p=p->rchild; } } return false; } void Delete_AVL(AvlNode *&T,int num) //删除删除后没有回溯到根节点,算法有错,待日后修改完善,有心的朋友可以自己加一个栈或者其他方式来实现 { /*--------------------------------------------------------- / 从删除一个节点后,要保证删后的还是一棵平衡二叉树, / 删除前,首先是在中查找是否有这个结点,用p指向该结点, / 用f指向p的双亲结点,这个结点中的位置有下面四种情况: / / 1:如果p指向的结点是叶子结点,那么直接将f指针的左子或者 / 右子置空,然后删除p结点即可。 / / 2:如果p指向的结点是只有左子或右子,那么只需要让p结点 / 原来在f中的位置(左子或右子)用p的子代替即可。 / 代替后,要修改f的平衡因子,在失去平衡的时候,要调用相应的 / 做平衡旋转或右平衡旋转进行恢复. / / 3:如果p所指向的结点是根节点,那么直接将根节点置空 / / 4:如果p所指向的结点左右子都非空,为了删除p后原序列的顺 / 序不变,就需要在原序列中先找出p的直接前驱(或者直接后继) / 结点用那个结点的值来代替p结点的值,然后再删掉那个直接前 / 驱(或者直接后继)结点。 / 其中s指向的是要删除结点,也就是p的直接前驱,q指向的是 / s的双亲结点,此时,应该看s的平衡因子,在会出现失去平衡的 / 情况时,就要根据实际情况采用左平衡旋转或是右平衡旋转,让 / 恢复平衡,这点和插入操作时是相对应的。 / / 在中序遍历序列中找结点的直接前驱的方法是顺着结点的左孩子 / 的右链域开始,一直到结点右孩子为空为止。 /---------------------------------------------------------*/ AvlNode *f=NULL; AvlNode *p=NULL; AvlNode *q=NULL; AvlNode *s=NULL; if(Search_Avl(T,num,f,p)) { if(p->lchild && p->rchild) //左右子均存在时 { q=p; s=p->lchild; while(s->rchild) { q=s; s=s->rchild; } p->data=s->data; if(q!=p) { //q结点的右子高度减少1 //所以平衡因子会+1 q->rchild=s->lchild; switch(q->bf) { //删除前右子高,现在就变成一样高 case RH: q->bf=EH; break; //删除前等高,现在就变成左子比右子高 case EH: q->bf=LH; break; //删除前左子高,现在左子又高了一,所以失去平衡 case LH: q->bf=EH; Left_Balance(q); break; } } else { //p的左子的右子为空时 //q结点也就是p结点,由于s的右子为空 //所以q结点的左子高度降低1 //平衡因子-1 q->lchild=s->lchild; switch(q->bf) { case LH: q->bf=EH;break; case EH: q->bf=RH;break; case RH: q->bf=EH; Right_Balance(q); break; } } delete s; cout<<"删除结点成功"<<endl; return ; } else { if(!p->lchild) { q=p; p=p->rchild; } else { q=p; p=p->lchild; } if(!T) { T->bf=EH; T=p; } else if(q==f->lchild) { f->lchild=p; switch(f->bf) { case LH: f->bf=EH; break; case EH: f->bf=RH; break; case RH: f->bf=EH; Right_Balance(f); break; } } else { f->rchild=p; switch(f->bf) { case RH: f->bf=EH; break; case EH: f->bf=LH; break; case LH: f->bf=EH; Left_Balance(f); break; } } delete q; cout<<"删除结点成功"<<endl; return; } } else { cout<<"要删除结点不存在"<<endl; return; } } InOrder_Traverse(AvlNode *T) //中序遍历 { stack<AvlNode *> s; AvlNode *p=T; while(p || !s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p=s.top(); s.pop(); cout<<p->data<<" "; p=p->rchild; } } } void Level_Traverse(AvlNode *T) //层次遍历 { queue<AvlNode *> q; AvlNode *p=T; q.push(p); while(!q.empty()) { p=q.front(); q.pop(); cout<<p->data<<" "; if(p->lchild) q.push(p->lchild); if(p->rchild) q.push(p->rchild); } } }; //测试文件"main.cpp" //#include"tree.h" int main() { AVL_Tree tree; AvlNode *root=NULL; cout<<"____建立平衡二叉树____"<<endl; tree.Create_AVl(root); cout<<"中序遍历二叉为:"; tree.InOrder_Traverse(root); cout<<endl; cout<<"层次遍历二叉为:"; tree.Level_Traverse(root); cout<<endl; int num; bool taller=false; cout<<"输入你要插入的结点的值:"; cin>>num; tree.Insert_Avl(root,num,taller); cout<<"中序遍历二叉为:"; tree.InOrder_Traverse(root); cout<<endl; AvlNode *f=NULL; AvlNode *p=NULL; cout<<"输入你要搜索的结点的值:"; cin>>num; if(tree.Search_Avl(root,num,f,p)) { cout<<"查找得到的结点值为:"<<tree.Get_data(p)<<"的地址为:"<<p<<endl; if(f==NULL) cout<<"因为结点"<<tree.Get_data(p)<<"是根结点,所以没有双亲结点"<<endl; else cout<<"该结点的双亲结点的值为:"<<tree.Get_data(f)<<endl; } else cout<<"查找的结点不存在"<<endl; cout<<"输入你要删除结点的值:"; cin>>num; tree.Delete_AVL(root,num); cout<<"中序遍历二叉为:"; tree.InOrder_Traverse(root); cout<<endl; return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值