写在前面
平衡二叉树相关实现有点复杂,请先看视频学习基础知识
基本知识视频讲解请看王道考研
链接如下
【王道计算机考研 数据结构】 https://www.bilibili.com/video/BV1b7411N798/?p=76&share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
它主要是,手动去实现,因此有些不全面
然后代码实现还请看我本人的东西
还有一个 b站 蓝不过海呀 它动画做的不错
链接如下
【平衡二叉树(AVL树)】 https://www.bilibili.com/video/BV1tZ421q72h/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
主要是学习数据结构,真的做什么请直接使用map、set等容器
平衡二叉树的定义
平衡二又树(Balanced Binary Tree),简称平衡树(AVL树)
树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子=左子树高-右子树高
显然,平衡二叉树是特殊的二叉排序树,即平衡二叉排序树。
我这里直接在二叉树的结点中加入平衡因子
template<class ElemType>
struct BTNode
{
//平衡二叉树使用
int balance;//平衡因子
//关键值,查找使用
ElemType data;//保存的数据
BTNode<ElemType>* father;//父亲
BTNode<ElemType>* lchild;//左子树 left child
BTNode<ElemType>* rchild;//右子树 right child
//无参构造函数
BTNode();
//有参构造
BTNode(const ElemType& theElement, BTNode* the_father = NULL, BTNode* the_left = NULL, BTNode* the_right = NULL);
};
然后,继承二叉排序树
//平衡二叉树
template<class ElemType>
class AVL :public BST<ElemType>
{
//省略一些代码
}
前面的准备工作
实现插入、删除需要先做这些准备
左旋,右旋
左旋和右旋的操作不破坏二叉查找树(中序遍历不变),因此可以用来调整。
后面要使用这种方法!
注意目标是B!
有点晕,所以直接定义四个指针,b指向主目标,gf,a,br指向相关的,然后根据图中的结果去修改
template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
/*功能:左旋处理
* 输入:
* b:要处理的结点指针
* 返回:
* 其它:
* 此函数只管左旋处理,别的不管
* 请看清要处理的结点
*/
void L_Rotate(BTNode<ElemType>* b);
/*功能:右旋处理
* 输入:
* b:要处理的结点指针
* 返回:
* 其它:
* 此函数只管右旋处理,别的不管
* 请看清要处理的结点
*/
void R_Rotate(BTNode<ElemType>* b);
//省略一些代码……
}
template<class ElemType>
void AVL<ElemType>::L_Rotate(BTNode<ElemType>* b)//过程很难写注释,请看的图片
{
//排除一些错误情况
if (b == NULL) return;
if (b == this->root) return;
BTNode<ElemType>* a = b->father;
BTNode<ElemType>* gf = a->father;
BTNode<ElemType>* bl = b->lchild;
if (gf != NULL)//如果A不是根结点
{
//“从上往下”处理
if (gf->lchild == a)
{
gf->lchild = b;
}
else
{
gf->rchild = b;
}
b->father = gf, b->lchild = a;
a->father = b, a->rchild = bl;
if (bl != NULL)
{
bl->father = a;
}
}
else//请注意根节点没有“爹”这种特殊情况
{
this->root = b;
b->father = NULL, b->lchild = a;
a->father = b, a->rchild = bl;
if (bl != NULL)
{
bl->father = a;
}
}
}
template<class ElemType>
void AVL<ElemType>::R_Rotate(BTNode<ElemType>* b)//过程很难写注释,请看图片
{
//排除一些错误情况
if (b == NULL) return;
if (b == this->root) return;
BTNode<ElemType>* a = b->father;
BTNode<ElemType>* gf = a->father;
BTNode<ElemType>* br = b->rchild;
if (gf != NULL)//如果A不是根结点
{
//“从上往下”处理
if (gf->lchild == a)
{
gf->lchild = b;
}
else
{
gf->rchild = b;
}
b->father = gf, b->rchild = a;
a->father = b, a->lchild = br;
if (br != NULL)
{
br->father = a;
}
}
else//请注意根节点没有“爹”这种特殊情况
{
this->root = b;
b->father = NULL, b->rchild = a;
a->father = b, a->lchild = br;
if (br != NULL)
{
br->father = a;
}
}
}
关于更新平衡因子
插入删除啥的,要更新平衡因子
显然,对于正常的平衡二叉树,平衡因子只能是-1、0、1,当出现-2、2时就要进行调节
先说一个非常笨的办法,就直接遍历整个二叉树,每一个结点都去看它左右子树高度,然后得到平衡值
当然这太笨了,“看它左右子树高度”还得用递归
这时,二叉树结点中有指向父亲的指针的好处尤其凸显,并不需要“从上到下”,能直接找父亲,就可以“从下往上”,用循环就行
1、子树变高时
子树H+1,
他爹要变化,他爷爷、太爷爷……看情况,存在不需要继续往上调整的情况,
我们需要找出什么情况下,不用继续往上调整
请看下面
左子树变高1
平衡因子+1
结论如果出现–2->-1 或1->0就不用在往上变化了
右子树变高1
平衡因子-1
结论如果出现 2->1 或1->0就不用在往上变化了
子树变高时的代码
template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
//这几个函数在,插入删除啥的时候要用,不能随便调用,所以设置为私有
/*功能:认为某个结点的子树高度增加1,向上调整平衡因子
* 输入:
* theAdd:认为高度增加1的子树
* leftOrRight:开始变高的是左子树还是右子树,左请传入true,右请传入false
* 返回:
* 其它:包含这个结点
*/
void adjustBlanceAdd(BTNode<ElemType>* theAdd, bool leftOrRight);
//省略一些代码……
}
template<class ElemType>
void AVL<ElemType>::adjustBlanceAdd(BTNode<ElemType>* theAdd, bool leftOrRight)
{
if (leftOrRight == true)//如果“左面”变高
{
theAdd->balance += 1;
if (theAdd->balance == -1 || theAdd->balance == 0)//-2->-1 -1->0,不会向上传导,跳出循环
return;
}
else//如果“右面”变高
{
theAdd->balance -= 1;
if (theAdd->balance == 1 || theAdd->balance == 0)//2->1 1->0,不会向上传导,跳出循环
return;
}
if (theAdd == this->root) return;//根节点,直接结束
//往上调整平衡因子,直到不用再调整(请看笔记中的图片,实在不知道怎么说道理了)
//注意第一个根据leftOrRight调整过了,接下来弄上面
BTNode<ElemType>* n = theAdd;//遍历用的指针,作为“孩子”
BTNode<ElemType>* nf = theAdd->father;//遍历用的指针,作为n的“父亲”
while (nf != NULL)
{
//其它情况会向上传导,继续循环
if (nf->lchild == n)//对于nf来说,“左面”变高
{
nf->balance += 1;
if (nf->balance == -1 || nf->balance == 0)//-2->-1 -1->0,不会向上传导,跳出循环
break;
}
else//对于nf来说,“右面”变高
{
nf->balance -= 1;
if (nf->balance == 1 || nf->balance == 0)//2->1 1->0,不会向上传导,跳出循环
break;
}
n = n->father;
nf = nf->father;
}
}
2、子树变矮
同样的
他爹要变化,他爷爷、太爷爷……看情况,存在不需要继续往上调整的情况,
我们需要找出什么情况下,不用继续往上调整
请看下面
左子树变矮1
平衡因子-1
结论如果出现 0->-1 或-1->-2就不用在往上变化了
右子树变矮1
平衡因子+1
结论如果出现 0->1 或1->2就不用在往上变化了
子树变矮时的代码
template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
/*功能:认为某个结点的子树高度减少1,向上调整平衡因子
* 输入:
* theSub:认为高度减小1的子树
* leftOrRight:开始变矮的是左子树还是右子树,左请传入true,右请传入false
* 返回:
* 其它:包含这个结点
*/
void adjustBlanceSub(BTNode<ElemType>* theSub, bool leftOrRight);
//省略一些代码……
}
template<class ElemType>
void AVL<ElemType>::adjustBlanceSub(BTNode<ElemType>* theSub, bool leftOrRight)
{
if (leftOrRight == true)//如果“左面”变矮
{
theSub->balance -= 1;
if (theSub->balance == -1 || theSub->balance == -2)//0->-1或-1->-2,不会向上传导,结束
return;
}
else//如果“右面”变矮
{
theSub->balance += 1;
if (theSub->balance == 1 || theSub->balance == 2)//0->1或1->2,不会向上传导,跳出循环
return;
}
if (theSub == this->root) return;
//注意第一个根据leftOrRight调整过了,接下来弄上面
//往上调整平衡因子,直到不用再调整(请看笔记中的图片,实在不知道怎么说道理了)
BTNode<ElemType>* n = theSub;//遍历用的指针,作为“孩子”
BTNode<ElemType>* nf = theSub->father;//遍历用的指针,作为n的“父亲”
while (nf != NULL)
{
if (nf->lchild == n)//对于nf来说,“左面”变矮
{
nf->balance -= 1;
if (nf->balance == -1 || nf->balance == -2)//0->-1或-1->-2,不会向上传导,结束
break;
//其它情况会向上传导,继续循环
}
else//对于nf来说,“右面”变矮
{
nf->balance += 1;
if (nf->balance == 1 || nf->balance == 2)//0->1或1->2,不会向上传导,跳出循环
break;
//其它情况会向上传导,继续循环
}
n = n->father;
nf = nf->father;
}
}
寻找最小不平衡子树
从某个结点开始向上,寻找最小不平衡子树
这个比较简单,就一直往上,找到2或-2就行
template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
/*功能:从某结点(包含这个)开始,向上查找最小不平衡子树
* 输入:
* start:开始查找的结点指针
* s:保存最小不平衡子树的指针
* 返回:
* true:发现最小不平衡子树
* false:没有出现不平衡
* 其它:
*/
bool findUnblance(BTNode<ElemType>* start, BTNode<ElemType>*& s);
//省略一些代码……
}
template<class ElemType>
bool AVL<ElemType>::findUnblance(BTNode<ElemType>* start, BTNode<ElemType>*& s)
{
bool the_find = false;
BTNode<ElemType>* n = start;//遍历用的指针
while (n != NULL)
{
if (n->balance >= 2 || n->balance <= -2)
{
the_find = true;
s = n;//记录最小不平衡子树位置
break;
}
n = n->father;
}
return the_find;
}
调整最小不平衡子树(LL、LR、RR、RL)
也就是平衡因子2 -2的
主要关注平衡因子!
L?类,A的平衡因子为+2
A的平衡因子为2,A的左子树比右子树高
1)LL平衡旋转(右单旋转)
LL:A的左孩子(记为B)的左子树比右子树高
需要一次向右的旋转操作。
情况1:B的平衡因子+1
图中高度H大于等于0
最小不平衡子树高度H+3->调整后H+2
情况2:B的平衡因子0
图中高度H大于等于1
2)LR平衡旋转(先左后右双旋转)
记A为最小不平衡子树根结点
LR:A的左孩子的右子树比左子树高
需要进行两次旋转操作,先左旋转后右旋转。
记A的左孩子为B,记B的右孩子为C
先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,
然后再把该C结点向右上旋转提升到A结点的位置
最小不平衡子树高度H+3->调整后H+2
情况1:C的平衡因子-1
C的右子树比左子树高
图中高度H大于等于1
情况2:C的平衡因子+1
C的左子树比右子树高
图中高度H大于等于1
情况3:C平衡因子为0
图中高度H大于等于0
H为0时代表,BL为空,C本身就是新插入的如图所示
R?类,A的平衡因子为-2
3)RR平衡旋转(左单旋转)
RR:在A的右孩子的右子树中插入导致不平衡
将A的右孩子B向左上旋转代替子由-1减至-2,
A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树
情况1:B的平衡因子-1
图中高度H大于等于0
最小不平衡子树高度H+3->调整后H+2
情况2:B的平衡因子0
4)RL平衡旋转(先右后左双旋转)
记A为最小不平衡子树根结点
RL:在A的右孩子的左子树中插入导致不平衡
由于在A的右孩子 ®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,
先右旋转后左旋转。
记A的右孩子为B,记B的左孩子为C
先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,
然后再把该C结点向左上旋转提升到A结点的位置
情况1:C的平衡因子+1
最小不平衡子树高度H+3->调整后H+2
情况2:C的平衡因子-1
情况3:C平衡因子为0
图中高度H大于等于0
H为0时代表,BL为空,C本身就是新插入的如图所示
打代码提示:
找到最小不平衡子树后,可以直接根据初始时A、B、C的平衡因子判断是那种“不平衡的类型”
平衡值的调整结果,也可以直接使用图中的结论
并不需要去看子树到底多高
调整最小不平衡子树代码
template<class ElemType>
class AVL :public BST<ElemType>
{
/*功能:调整最小不平衡子树
* 输入:
* A:最小不平衡子树的根结点指针
* 返回:
* 调整后的,子树的根节点指针
* 其它:
* 最小不平衡子树调整后,高度-1
* 调整时,左旋或右旋的目标时A的孩子,而不是A
* 最后A就不是子树的根指针了,指不定旋转到哪里去了!
*/
BTNode<ElemType>* adjustMinUnblance(BTNode<ElemType>* A);
//省略一些代码
}
template<class ElemType>
BTNode<ElemType>* AVL<ElemType>::adjustMinUnblance(BTNode<ElemType>* A)
{
//判断不平衡类型,并调整(原理请看笔记中的图片)
//L?型
if (A->balance == 2)
{
BTNode<ElemType>* B = A->lchild;
//LL型
if (B->balance != -1)
{
this->R_Rotate(B);
if (B->balance == 1)//图中的LL情况1
{
B->balance = 0;
A->balance = 0;
//cout << "LL情况1" << endl;
}
else if (B->balance == 0)//图中的LL情况2
{
B->balance = -1;
A->balance = 1;
//cout << "LL情况2" << endl;
}
return B;
}
//LR型
else if (B->balance == -1)
{
BTNode<ElemType>* C = B->rchild;
this->L_Rotate(C);//注意这个函数没有调整过平衡因子
this->R_Rotate(C);//注意这个函数没有调整过平衡因子
if (C->balance == -1)//图中的LR情况1
{
C->balance = 0;
B->balance = 1;
A->balance = 0;
//cout << "LR情况1" << endl;
}
else if (C->balance == 1)//图中的LR情况2
{
C->balance = 0;
B->balance = 0;
A->balance = -1;
//cout << "LR情况2" << endl;
}
else if (C->balance == 0)//图中的LR情况3
{
C->balance = 0;
B->balance = 0;
A->balance = 0;
//cout << "LR情况3" << endl;
}
return C;
}
}
//R?型
else if (A->balance == -2)
{
BTNode<ElemType>* B = A->rchild;
//RR型
if (B->balance != 1)
{
this->L_Rotate(B);
if (B->balance == -1)//图中的RR情况1
{
B->balance = 0;
A->balance = 0;
//cout << "RR情况1" << endl;
}
else if (B->balance == 0)//图中的RR情况2
{
B->balance = 1;
A->balance = -1;
//cout << "RR情况2" << endl;
}
return B;
}
//RL型
else if (B->balance == 1)
{
BTNode<ElemType>* C = B->lchild;
this->R_Rotate(C);
this->L_Rotate(C);
if (C->balance == 1)//图中的RL情况1
{
C->balance = 0;
B->balance = -1;
A->balance = 0;
//cout << "RL情况1" << endl;
}
else if (C->balance == -1)//图中的RL情况2
{
C->balance = 0;
B->balance = 0;
A->balance = 1;
//cout << "RL情况2" << endl;
}
else if (C->balance == 0)//图中的RL情况3
{
C->balance = 0;
B->balance = 0;
A->balance = 0;
//cout << "RL情况3" << endl;
}
return C;
}
}
return NULL;//莫名奇妙的返回空
}
平衡二叉树的插入
有了前面的全部准备工作,插入就可以容易实现
步骤如下:
1.进行普通的二叉排序树插入
2.调整平衡因子
3.寻找最小不平衡子树
有:调节最小不平衡子树,调整平衡因子
没有:结束
template<class ElemType>
class AVL :public BST<ElemType>
{
public:
/*功能:在本平衡二叉树中插入元素e
* 输入:
* e:要插入的元素
* 返回:
* 成功:true
* 失败:false(原本存在元素e)
* 其它:
*/
bool Insert(ElemType e);
//省略一些代码
}
template<class ElemType>
bool AVL<ElemType>::Insert(ElemType e)
{
//插入,调用普通二叉排序树的插入
BTNode<ElemType>* theNew = BST<ElemType>::Insert_p(e);
if (theNew == NULL) return false;//原本存在元素e,直接返回false
if (theNew == this->root) return true;//如果新插入的是根节点(原本树为空),直接返回true
//向上调整平衡值(用长高的)
if (theNew->father != NULL)//当它有爹,即不是根节点,如果是根节点,就不用调整啥了
{
this->adjustBlanceAdd(theNew->father, leftOrRight(theNew));//从它爹开始调整平衡值
}
//寻找最小不平衡子树
BTNode<ElemType>* A = NULL;//保存最小不平衡子树的指针
bool theFind = this->findUnblance(theNew, A);
if (theFind == false) return true;//没有出现不平衡,直接返回true
/*注意,调整最小不平衡子树后,子树高度-1*/
//调整最小不平衡子树
BTNode<ElemType>* newA = NULL;//保存原最小不平衡子树根指针
newA = adjustMinUnblance(A);//完事之后,最小不平衡子树的平衡因子也就调整好了
//向上调整平衡因子(用变矮的)
if (newA->father != NULL)//当它有爹,即不是根节点,如果是根节点,就不用调整啥了
{
this->adjustBlanceSub(newA->father, leftOrRight(newA));//从它爹开始调整平衡值
}
return true;
//利用插入时的性质:插入时,只要调整最小不平衡子树,其它部分就不受影响,可以优化一点
}
平衡二叉树的删除
平衡二叉树的删除操作:
删除结点后,要保持二叉排序树的特性不变(左<中<右)
若删除结点导致不平衡,则需要调整平衡
请注意:删除中调整最小不平衡子树后,可能出现新的不平衡,应当继续调整
有了前面的准备工作后,删除也容易实现
1、删除结点 (方法同“二叉排序树”),调整平衡值
2、一路向上寻找最小不平衡子树
找不到就结束
如果找到重复以下步骤,知道不在出现最小不平衡子树
3、调整最小不平衡子树
4、从此最小不平衡子树父结点往上调整平衡值,然后从最小不平衡子树父结点向上寻找最小不平衡子树
如果出现,重复3、4
template<class ElemType>
class AVL :public BST<ElemType>
{
public:
/*功能:在本平衡二叉排序树中删除元素e
* 输入:
* e:要删除的元素
* 返回:
* true:成功
* false:失败(原本就不存在元素e)
* 其它:会保证排序树结构不被破坏
*/
bool Delete(ElemType e);
//省略一些代码
}
template<class ElemType>
bool AVL<ElemType>::Delete(ElemType e)
{
bool isSuccess = false;
BTNode<ElemType>* realDeleteFather = NULL;//保存真实删除的结点的父亲结点
bool LoR = false;//保存真实删除的结点是左孩子还是右孩子
isSuccess = BST<ElemType>::Delete_p(e, realDeleteFather, LoR);//调用平衡二叉树的相关删除函数
if (isSuccess == false) return false;//删除失败,直接返回失败
if (realDeleteFather == NULL) return true;//把根节点删了,直接返回成功
//调整平衡因子
this->adjustBlanceSub(realDeleteFather, LoR);
BTNode<ElemType>* n = realDeleteFather;//用来遍历的结点指针
BTNode<ElemType>* unBlance = NULL;//保存最小不平衡
while (findUnblance(n, unBlance) == true)//寻找最小不平衡子树,如果有最小不平衡子树,就循环
{
n = this->adjustMinUnblance(unBlance);//调整最小不平衡子树,并将调整后子树根节点赋给n
if (n->father != NULL)//调整平衡因子
{
this->adjustBlanceSub(n->father, leftOrRight(n));//从它爹开始调整平衡值
}
}
return true;
}