描述:
平衡二叉树是二叉搜索树的改进,本文以你已了解二叉搜索树为前提,不了解的朋友请看这里:
当我们使用二叉搜索树时,若我们按递增或递减的顺序插入结点,那么每一次的新结点都会插入在左子树或右子树,这样一来二叉树便退化为了链表,那么搜索的效率就会降低。
为提升二叉排序树的平均效率,可将其升级为平衡二叉树,其特点是:所有根结点的左子树和右子树深度差不超过1。其中左右子树的深度差也称为平衡因子。每插入一个结点,都应向上计算每个结点的平衡因子,若破坏了平衡性,则需要进行平衡调整。
//定义结点
typedef struct BSTree
{
int data=0;
BSTree *ltree=nullptr,*rtree=nullptr,*parent=nullptr;//左右子树及父结点
}BSTree;
判断是否平衡:
每次插入新结点时,从该结点开始,一层一层向上判断,判断二叉树是否平衡;
可以封装一个获取平衡因子的函数,通过判断左右子树的差是否超过1来判断是否失去平衡。
若插入了某个结点后导致二叉树失衡,这里将此结点称为问题结点,为了方便后面的旋转操作,这里还需要找一个结点;
可以再封装一个函数,从问题结点开始向上搜索,与其距离最近的且失衡的结点,则是我们要找的结点,这里称之为最小不平衡结点。
//获取树的深度
int BiTreeDepth(BSTree *&T)
{
//左子树与右子树深度的较大值加1,即为本层的深度。
int dep=0;
if(T==nullptr) return dep;
else
{
int lm=BiTreeDepth(T->ltree);
int rm=BiTreeDepth(T->rtree);
if(lm>=rm) dep=lm+1;
else dep=rm+1;
return dep;
}
}
//获取平衡因子
int get_balance_factor(BSTree *&T)
{
int l=BiTreeDepth(T->ltree);
int r=BiTreeDepth(T->rtree);
int res=l-r;
if(res<0)
return -res;
else
return res;
}
//寻找最小不平衡结点
BSTree* get_node(BSTree *&T,BSTree *&p) //p为问题结点
{
BSTree *t=p;
while(get_balance_factor(t)<=1)
{
t=t->parent;
}
return t;
}
//判断二叉树是否平衡
bool JudgeBalance(BSTree *&T,int val)
{
//从当前结点开始,一层一层向上移动、判断
BSTree *p=search_bstree(T,val);
while(p!=T) //到达根结点时退出循环
{
if(get_balance_factor(p)<=1)
p=p->parent;
else
return false;
}
//判断根结点的平衡性
if(get_balance_factor(T)<=1)
return true;
else
return false;
}
旋转:
若判断出二叉树失衡了,则开始对其进行调整,原则很简单,扁担原理,哪边比较重就往哪边的反方向旋转。
要先找到一个支点,假设其左子树的层数大于其右子树的层数,即左边较重,就让其右边的层数多一些,左边少一些,通过改变指针的指向可以做到这一点。
旋转分为LL型、RR型、LR型和RL型;前两种就是单纯的根据支点向左或向右转;LR型则是先让左子树旋转,然后在根据支点旋转;RL型同理。这里解释一下LL型旋转,其他三种原理一样的。
具体操作:
首先要先找到最小不平衡结点m,以结点m作为支点,假设其左子树为结点t;
原本结点m是结点t的父结点,接下来让结点t成为父结点,结点m则变成结点t的右子树;
结点t原来的右子树则变成结点m的左子树;
结点m原来的右子树和结点t的原来的左子树保持不变。
既然改变了父结点,那么原来结点m的父结点p也需要改变内部指针的指向,结点p原本的子树是结点m,现在也应该改为结点t。
结点m的父结点改为结点t,结点t的父结点改为结点p。
如何决定旋转类型:
若问题结点在最小不平衡结点的左子树的左子树,则为LL型旋转,反之RR型旋转;
若问题结点在最小不平衡结点的左子树的右子树,则为LR型旋转,反之RL型旋转。
//LL型旋转
void Adjust_LL(BSTree *&T,BSTree *&m) //T为根结点(头指针),m为最小不平衡结点
{
BSTree *t=m->ltree;
if(m!=T) //如果最小不平衡结点不是根结点,还需要处理一下其父结点
{
t->parent=m->parent;
if(m->parent->ltree==m)
m->parent->ltree=t;
else
m->parent->rtree=t;
m->parent=t;
}
else
T=t; //更新头指针指向
m->ltree=t->rtree;
t->rtree=m;
}
//RR型旋转
void Adjust_RR(BSTree *&T,BSTree *&m)
{
//RR型:右边较重,就逆时针旋转,操作同理LL
BSTree *t=m->rtree;
if(m!=T)
{
t->parent=m->parent;
if(m->parent->ltree==m)
m->parent->ltree=t;
else
m->parent->rtree=t;
m->parent=t;
}
else
T=t;
m->rtree=t->ltree;
t->ltree=m;
}
//LR型旋转
void Adjust_LR(BSTree *&T,BSTree *&m)
{
//先让最小不平衡结点的左子树进行逆时针旋转,
//操作类似Adjust_RR(),但不可以直接调用,否则会破坏头指针的指向
BSTree *ml=m->ltree; //指针ml操作最小不平衡结点的左子树
BSTree *t=ml->rtree; //指针t操作ml的右子树
//修改父子关系
t->parent=m;
ml->parent=t;
m->ltree=t;
//修改指针指向
ml->rtree=t->ltree;
t->ltree=ml;
//再以最小不平衡结点为支点进行顺时针旋转,操作类似Adjust_LL(),可以直接调用函数。
Adjust_LL(T,m);
}
//RL型旋转
void Adjust_RL(BSTree *&T,BSTree *&m)
{
//RL型的操作同理LR型
//先处理最小不平衡结点的右子树,让其顺时针旋转
//指针mr操作最小不平衡结点的右子树,指针t操作其左子树
BSTree *mr=m->rtree;
BSTree *t=mr->ltree;
//修改父子关系
t->parent=m;
mr->parent=t;
m->rtree=t;
//修改指针指向
mr->ltree=t->rtree;
t->rtree=mr;
//再以最小不平衡结点为支点进行逆时针旋转
Adjust_RR(T,m);
}
//调整二叉树
void AdjustBiTree(BSTree *&T,int val)
{
BSTree *p=search_bstree(T,val);//获取问题结点
BSTree *m=get_node(T,p);//获取最小不平衡结点
//判断类型,然后调用相应的旋转函数:
if(m->ltree!=nullptr) //若左子树存在
{
if(search_bstree(m->ltree->ltree,p->data)!=nullptr) //如果左子树的左子树中能搜索到结点p
{
Adjust_LL(T,m);
return;
}
if(search_bstree(m->ltree->rtree,p->data)!=nullptr) //如果左子树的右子树中能搜索到结点p
{
Adjust_LR(T,m);
return;
}
}
if(m->rtree!=nullptr) //若右子树存在
{
if(search_bstree(m->rtree->rtree,p->data)!=nullptr) //如果右子树的右子树中能搜索到结点p
{
Adjust_RR(T,m);
return;
}
if(search_bstree(m->rtree->ltree,p->data)!=nullptr) //如果右子树的左子树中能搜索到结点p
{
Adjust_RL(T,m);
return;
}
}
}
创建:
不断调用插入函数,将数组中的元素都插入到树中即可。每插入一个结点都要判断是否平衡,若不平衡则调用调整函数。
//创建平衡二叉树
void creat_bbtree(BSTree *&T,const vector<int> &v)
{
T=nullptr;
int n=v.size();
for(int i=0;i<n;++i)
{
//插入结点
insert_bstree(T,v[i]);
//判断插入后是否平衡
if(!JudgeBalance(T,v[i]))
{
AdjustBiTree(T,v[i]);
}
}
}
搜索、插入、删除的操作与二叉搜索树相似,这里就不再写了。