平衡二叉树(C/C++)

描述:

平衡二叉树是二叉搜索树的改进,本文以你已了解二叉搜索树为前提,不了解的朋友请看这里:

二叉搜索树(C/C++)

当我们使用二叉搜索树时,若我们按递增或递减的顺序插入结点,那么每一次的新结点都会插入在左子树或右子树,这样一来二叉树便退化为了链表,那么搜索的效率就会降低。

为提升二叉排序树的平均效率,可将其升级为平衡二叉树,其特点是:所有根结点的左子树和右子树深度差不超过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]);
        }
    }
}

搜索、插入、删除的操作与二叉搜索树相似,这里就不再写了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值