首页 > 代码库 > 平衡二叉树
平衡二叉树
什么是平衡二叉树?
平衡二叉树是一种特殊的二叉排序树,又称AVL树,它可以是一棵空树,或者是具有下列性质的二叉排序树:左子树与右子树的深度之差的绝对值不超过1,且它的左右子树都是平衡二叉树。
二叉树上结点的平衡因子BF(BalanceFactor)定义为改结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能为-1,0或者1。
为什么要引申出平衡二叉树的概念?
为了解决二叉排序树的不平衡性导致时间复杂度大大下降的问题,AVL可以保持住(BST)的最好时间复杂度O(logn)
如何添加、删除、查找操作平衡二叉树?
平衡二叉树的查找与二叉排序树完全相同,创建和删除同二叉排序树的创建和删除类似,只是在创建和删除的过程中每增加或删除一个结点时增加一个判断,判断这个操作是否引起了树的失衡,若引起了失衡则进行平衡调整,更新被调整结点的平衡因子,那么便有了下面两个问题:
1. 如何判断树是否失衡
2. 失衡后如何进行平衡调整和更新结点的平衡因子
因为要记录每个结点的平衡因子,先引入一个结点结构:
struct TNode { int nKey; int bf;//平衡因子 struct TNode* lchild; struct TNode* rchild; };
如何判断树是否失衡
什么情况下树会失衡?当插入一个新结点或删除一个已存在的结点时都有可能造成树的失衡。
如果在插入新的结点p或者删除一个已存在的结点p时,造成离p最近的某个祖先结点q的平衡因子的绝对值等于2,那么以结点q为根结点的树就失衡了,从而造成整棵树都失去平衡。以离结点p最近的平衡因子为2的祖先结点q为根结点的树称为最小失衡二叉树,那么怎么找到这个最小失衡二叉树T呢?当我们在插入或者删除一个结点时我们肯定有一个查找操作,需要查找到新结点插入到什么位置或待删除的结点在什么位置,我们查找的过程肯定有且只有一条路径,那么这条路径上的所有结点的平衡因子都有可能会随着我们的插入或删除操作而变化,因此我们需要在插入或者删除之后记录这个操作动作(置标志),然后沿着刚刚我们查找来的那条路径反方向往上走,一直跟新更新完路径上所有结点的平衡因子直到路径上的某个结点的平衡因子因操作变化更新后等于2,这时树就失衡了,不需要继续往下更新,以这个结点为根结点的树就是我们要找的最小失衡二叉树,接下来调整这棵失衡的树,去除这个操作标志(说明对路径后续未更新的结点已经没有影响),操作结束,整棵树就平衡了,这时平衡调整之后路径上剩余结点的平衡因子不需要调整,因为他们不会变。
通过上面的操作描述我们可以发现我们需要在一条路径上正着走一遍,又需要反着走一遍,因此我们可以使用递归的方法来实现,优点是这样的代码最简洁,缺点是要求路径上的结点个数有限制,不能太多,结点的个数也就是递归的深度,结点太多递归就有可能溢出。
失衡后如何平衡调整和更新平衡因子
我们已经找到了最小失衡二叉树T,现在只需要对这个树T进行平衡调整,T平衡了,那么整棵树也就平衡了。
树T失衡也就是它的左子树深度与它的右子树深度之差的绝对值等于2。那么也就是两种情况,左子树深度大于右子树深度和右子树深度大于左子树深度,下面分别就这两种情况讨论(主要讨论插入结点情况,删除类似):
1. 左子树TL深度大于右子树TR深度,且TL-TR=2
这时TL未失衡,平衡因子取值有三种情况1,-1,0
a. BF(TL)=1,参照图1和图2
TL的左子树深度大于TL的右子树深度,造成失衡的结点New在TL的左子树上,通过观察我们可发现可同过结点TL和结点T右旋来平衡整棵树,所谓右旋是指结点T调整为TL的右结点,原本TL的右结点作为T的左子树,这样整棵树就平衡了。如图:
平衡因子的调整:现在整棵树平衡了,但是我们还需要调整原本参与旋转的结点平衡因子
结点TLR实际没有参与旋转操作,所以平衡因子不变;结点TL由于右子树增加了一个结点(结点T)所以平衡因子由1变为了0;结点T的左子树由于减少了两个结点(结点TL结点和结点TL左子树上新插入的结点)所以由2变为了0。通过图2,我们也可以数出来。
b. BF(TL)=-1,参照图3和图4
TL的右子树深度大于TL的左子树深度,造成失衡的结点New在TL的右子树上,如果向a中一样右旋,New就转移到了结点T上,最终树还是失衡的,不应当让New转移,通过观察如图3我们可发现可以先将结点TLR和结点TL左旋,这时就变成了和a中同样的情形,可再通过结点TLR和结点T右旋操作达到平衡的目的。
平衡因子的调整:
目前为止我们知道,不管是左旋还是右旋,都会有一个结点接收另外一个结点的子树作为自己的子树,由图3和图4中观察可知左旋中TL右子树的右子树与TL的右子树的左子树的深度之差决定了左旋后TL和TLR的平衡因子大小,在右旋中同样是这样。所以我们需要考虑TL右子树的平衡因子(1,-1,0):
i. BF(TL_R)=1
由图可知调整后:BF(TL)=0,BF(TL_R)=0,BF(T)=-1。
ii. BF(TL_R)=-1
由图可知调整后:BF(TL)=1,BF(TL_R)=0,BF(T)=0。iii. BF(TL_R)=0
这种情况比较特殊,在插入操作时这种操作完全不可能出现,只有在删除结点才会出现,如图5
由图可知调整后:BF(T)=0,BF(TL)=0,BF(TL_R)=0。
c. BF(TL)=0, 见图c
这种情况好像好多书本中都没有提到,但是确实存在,可做LL型处理,也可做LR型处理,不过最终得到的树结构不一样,平衡因子也不一样,不过我选择用LL型,旋转简单,右旋。
平衡因子:BF(T)=1,BF(TL)=-1。
2. 右子树TR深度大于左子树TL深度,且TR-TL=2
同TL-TR=2时所述,TR的BF也有三取值-1,1,0:
d. BF(TR)=-1, 见图6
进行右旋操作,原理同a相同。
平衡因子调整
BF(T)=0,BF(TR)=0。
e. BF(TR)=1 , 见图7。
如图7,先进行右旋再进行左旋操作,原理同b相同。
平衡因子调整
分析原理同b中关于平衡因子的分析相同,通过下图7也可以数出最终平衡因子大小,这里只给出最终结论:
i. BF(TR_L)=1
结果:BF(TR)=-1,BF(TR_L)=0,BF(T)=0。
ii. BF(TR_L)=-1
结果:BF(TR)=0,BF(TR_L)=0,BF(T)=1。
iii. BF(TR_L)=0
结果:BF(TR)=0,BF(TR_L)=0,BF(T)=0。
e. BF(TR)=0, 图略
与c同理,左旋
如何代码实现?
C++代码:
#include <stdio.h> struct TNode { int nKey; int bf; struct TNode* lchild; struct TNode* rchild; }; //+++++++++++++左旋++++++++++++++++++/ void LRotate(TNode* &T) { TNode* pTmp = T->rchild; T->rchild = pTmp->lchild; pTmp->lchild = T; T = pTmp; } //+++++++++++++右旋旋++++++++++++++++++/ void RRotate(TNode* &T) { TNode* pTmp = T->lchild; T->lchild = pTmp->rchild; pTmp->rchild = T; T = pTmp; } //++++++++++++左树偏高造成失衡+++++++++/ void LeftBalance(TNode* &T) { if ( T->lchild->bf == 1 ) { T->bf = 0; T->lchild->bf = 0; RRotate(T); } else if ( T->lchild->bf == -1 ) { switch( T->lchild->bf ) { case 1: T->bf = -1; T->lchild->bf = 0; break; case -1: T->bf = 0; T->lchild->bf = 1; break; case 0://这种情况只有在删除操作中才出现 T->bf = 0; T->lchild->bf = 0; break; default:; } T->lchild->rchild->bf = 0; LRotate(T); RRotate(T); } else//T->lchild->bf == 0,这种情况只有在删除操作时会出现 { T->bf = 1; T->lchild->bf = -1; RRotate(T); } } //++++++++++++右树偏高造成失衡+++++++++/ void RightBalance(TNode* &T) { if ( T->rchild->bf == -1 ) { T->bf = 0; T->rchild->bf = 0; LRotate(T); } else if ( T->rchild->bf == 1 ) { switch( T->rchild->lchild->bf ) { case 1: T->bf = 0; T->rchild->bf = -1; break; case -1: T->bf = 1; T->rchild->bf = 0; break; case 0: T->bf = 0; T->rchild->bf = 0; break; default:; } T->rchild->lchild->bf = 0; RRotate(T); LRotate(T); } else//T->rchild->bf == 0,这种情况只有在删除操作时会出现 { T->bf = 0; T->rchild->bf = 1; LRotate(T); } } //++++++++++++++++++++++++++插入结点+++++++++++++++++++++++++++++/ bool InsertAVL(TNode* &AVL,int nKey,TNode* &pSearch,bool &bChange) { if ( !AVL ) { TNode* pNode = new TNode; pNode->nKey = nKey; pNode->bf = 0; pNode->lchild = NULL; pNode->rchild = NULL; AVL = pNode; bChange = true; return true; } if ( nKey == AVL->nKey ) { pSearch = AVL; bChange = false; return false; } if ( nKey > AVL->nKey ) { if ( !InsertAVL(AVL->rchild,nKey,pSearch,bChange) ) return false; if ( bChange ) { switch( AVL->bf ) { case 1: AVL->bf = 0; bChange = false; break; case -1: RightBalance(AVL); bChange = false; break; case 0: AVL->bf = -1; bChange = true; break; default:; } } } else { if( !InsertAVL(AVL->lchild,nKey,pSearch,bChange) ) return false; if ( bChange ) { switch( AVL->bf ) { case 1: LeftBalance(AVL); bChange = false; break; case -1: AVL->bf = 0; bChange = false; break; case 0: AVL->bf = 1; bChange = true; break; default:; } } } return true; } //++++++++++++++++++++++删除结点+++++++++++++++++/ bool DeleteAVL(TNode* &AVL,int nKey,bool &bChange) { if ( !AVL ) return false; if ( nKey == AVL->nKey ) { delete AVL; AVL = NULL; bChange = true; return true; } if ( nKey > AVL->nKey ) { if ( !DeleteAVL(AVL->rchild,nKey,bChange) ) return false; if ( bChange ) { switch( AVL->bf ) { case 1: LeftBalance(AVL); bChange = false; break; case -1: AVL->bf = 0; bChange = true; break; case 0: AVL->bf = 1; bChange = false; break; default:; } } } else { if ( !DeleteAVL(AVL->lchild,nKey,bChange) ) return false; if ( bChange ) { switch( AVL->bf ) { case 1: AVL->bf = 0; bChange = true; break; case -1: RightBalance(AVL); bChange = false; break; case 0: AVL->bf = -1; bChange = false; break; default:; } } } return true; } //+++++++++++++++++++++++搜索树+++++++++++++++++/ bool SearchAVL(TNode* AVL,int key,TNode* &pSearch) { if ( !AVL ) return false; if ( AVL->nKey == key ) { pSearch = AVL; return true; } else if ( key < AVL->nKey ) { return SearchAVL(AVL->lchild,key,pSearch); } else { return SearchAVL(AVL->rchild,key,pSearch); } return false; } //+++++++++++++++++++++++中序输出+++++++++++++++++/ void PrintAVL(TNode* AVL) { if( !AVL ) return; PrintAVL(AVL->lchild); printf("%d\n",AVL->nKey); PrintAVL(AVL->rchild); } //+++++++++++++++++++++++测试+++++++++++++++++/ int main(int argc, char* argv[]) { TNode* AVL = NULL; TNode* pSearch = NULL; bool bChange = false; int InterArray[] = {100,50,300,20,80,400,10,30,70,90}; for ( int i=0;i<sizeof(InterArray)/sizeof(int);i++ ) { InsertAVL(AVL,InterArray[i],pSearch,bChange); } PrintAVL(AVL); printf("删除后\n"); DeleteAVL(AVL,400,bChange); PrintAVL(AVL); if ( !SearchAVL(AVL,400,pSearch) ) { printf("查找400失败,已被成功删除!\n"); } getchar(); return 0; }
Python代码:
待实现。。。
参考:数据结构(C语言版) 严蔚敏 吴为民
平衡二叉树