0


手把手教你实现AVL树、平衡二叉树

今天,小编带大家一起来学习平衡二叉树(AVL树)吧。以下就简称AVL树了。

想必能点开这篇博客的朋友都是极度深爱计算机的,那今天就让我们一起揭开AVL树的神秘面纱吧!

目录


一.基本概念

AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis。

AVL树是搜索二叉树的一种特殊形式。本质上还是一棵搜索二叉树,遵循着左小右大的原则。

但与普通搜索二叉树不同的是,它每个节点的左右子树高度差都不超过1。

为什么要这么设计呢? 我们举个例子便理解了。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

这里要是进行查找65这个数的时候,我们需要一遍遍的递归,直到这颗树的树底。但这样一棵树递归次数太多,高度低还好,如果一棵树太高,就有可能造成递归次数太多,引起栈溢出。

就算不会引起栈溢出,这种树有多高就递归几次的方式,效率也是极低的。

所以我们需要在保持左小右大的前提下,让树的高度尽可能的低,达到一种节点的左右子树高度差不大于1的效果,即是AVL树。

那以上图为例,AVL树就是这样:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

这里,我们就能看出来了,要是找到65这个数子,我们只需要递归2次,而原树则需要5次。

所以,AVL树可以理解为是搜索二叉树的优化版本。

二.实现原理

我们首先要知道节点怎么左旋转和右旋转。

(一)右旋转

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

以上图为例,将L右旋转的话,就是L的右子树变成A,L原来的右子树变成A的左子树即可。

(二)左旋转

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

与右旋转相反,将L左旋转,就是L左子树变成A,L原来的左子树变成A的右子树。

(三)整体思路

既然要实现AVL树,那么我们就需要用到几个参数。

1.struct AVLTree ,这是AVL树的结构体,结构体里除了装数据的val和连接左右子树的指针外,还应该有一个用来判断该节点左右子树高度差的参数bf。bf参数设为int类型,-1代表左子树高,0代表树一般高,1代表右子树高。同时也应注意:结构体的每个节点都应该是指针类型的,所以在一开始定义结构体变量的时候,就要定义一个指针类型的结构体。

2.bool judge_hight,这是一个bool类型的参数,用来判断这一次插入节点后,该节点的子树是否有长高的。judge_hight 初始化为true即可,因为第一次插入数据,树一定会长高。

下面,我们就可以创建AVL树了!

函数声明:void AVLTree_Insert(AVLTree* at, bool judge_hight);

AVL树元素的插入方式与搜索二叉树大体类似,但有些许不同:

假设我们现在有一个数据,按照左小右大的规则插入搜索二叉树后,当找到一个位置插入数据后,我们需要判断树是否长高了,这里就需要分为两大类了:

插入左子树和插入右子树。

(四)数据插入左子树

如果数据是插入了节点a的左子树导致长高,那么我们就需要判断a的左右子树高度差(bf)这里分为三种情况:

bf = 0 左右子树平衡,将bf改为1即可。

bf = -1 右边高, 加入左节点,树就平衡了,bf改为0,judge_hight置为false。

bf = 1 左边高,需要调用函数来判断该节点a是否还平衡,之后judge_hight置为false。设调用的函数为:void Treebalance_left(AVLTree* at)。在这个函数里,我们就需要继续判断是插入了l的左节点还是右节点。

①插入左子树左子节点

插入左子节点的含义是:节点插入a的左子树后,使a不再平衡,原因是a的左子树的左边变高。

以下三种均为插入左子节点的情况,这三种看似不同,其实一致,均是l的左边高。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

图上看已经很清楚了, a的已经不平衡了,我们就需要进行右旋转a的左子节点来使a平衡。

相当于直接把l提到a的位置。

旋转后结果如图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

这里可以看出,a的bf改为0,l的bf改为0。

②插入左子树右子节点

与插入左子节点相反,插入右子节点是指:节点a不平衡是因为左边高,而a左边高是因为a的左子节点的右边高。也就是在a的左子节点插入了右子树。

同理,以下三种均为插入右子节点的情况,即a不平衡,l的右边高。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

对于a而言,这种也是不平衡的情况,那么我们需要先对l进行左旋转,再对n进行右旋转

这里值得注意的是:l左旋转后n右旋转,其实就是使n先转到l的位置,再转到a的位置。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

第一步:l左旋转(n转到l位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

第二步:n右旋转(n转到a位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

这里我们需要注意的是:此时a与l的bf值三棵树并不相同。为什么呢,因为一开始的时候三棵树n的左右子树高度并不相同。

下面给出n的bf值与a、l的bf值的规律:
第一棵第二棵第三棵n的BF值01-1a和l的BF值a=0 l=0a=-1 l=0a=0 l=1
我们需要注意的是,三棵树的旋转方式一样,但BF值不同,所以在代码里要先判断n的BF值再旋转。

(五)数据插入右子树

数据插入右子树,我们也需要判断此时节点的平衡情况,再做下一步打算。同样的,也是先判断插入数据前节点的bf值来判断,也分三种情况:

BF=0:插入右子树后BF=-1

BF=1:插入右子树后,节点平衡,BF=0,judge_hight置为false。

BF=-1:插入右子树后节点可能不平衡,需要调用函数判断一下。之后judge_hight置为false。

可以设调用的函数为void Treebalance_right(AVLTree* at);这个函数里,我们要传入该节点的右子树,判断数据是插入它的左子节点还是右子节点。

①插入右子树右子节点

以下三棵树均为插入右子节点的情况。三棵树平衡方式一样,均为l左旋转,使l转到a的位置。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

l左旋转后:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

②插入右子树左子节点

以下三棵树均为插入左子节点的情况,旋转方式均为l右旋转后n左旋转,即n先跳到l位置后跳到a位置。

但是与插入左子树右子节点类似,要先判断n的BF值,写出相应的a与l旋转后的BF值再旋转。这里小编先给大家旋转完再判断BF值。(代码里一定要先判断!)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

l右旋转(n跳到l的位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

n左旋转(n跳到a的位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

到这里,n的BF值与a、l的BF值的关系也就出来了。
n的BF值01 -1a的BF值001l的BF值0 -10

(六)思维导图

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

三.代码实现

#define LH 1//左树高
#define NH 0//一般高
#define RH -1//右树高
typedef int AVLTreetype;
typedef struct Tree
{
    AVLTreetype val;
    struct Tree* leftchild, * rightchild;
    int bf;//此节点子树的高度差
}*AVLTree, Tree;
void AVLTree_Insert(AVLTree& at, AVLTreetype n, bool& judge_hight);//插入AVL树
void AVLTreebalance_left(AVLTree& at);//判断左树是否平衡
void AVLTreebalance_right(AVLTree& at);//判断右树是否平衡
void L_Roll(AVLTree& at);//左旋
void R_Roll(AVLTree& at);//右旋
void AVLTree_Insert(AVLTree& at, AVLTreetype n, bool& judge_hight)//插入
{
    if (at == NULL)
    {
        AVLTreeinit(at, n);
        judge_hight = true;
        return;
    }
    else
    {
        if (n == at->val)
        {
            cout << "\nHave Insert:" << n << endl;
            judge_hight = false;
            return;
        }
        else if (n < at->val)
        {
            if (at->leftchild)
            {
                AVLTree_Insert(at->leftchild, n, judge_hight);//递归
            }
            else// at->leftchild == NULL
            {
                AVLTreeinit(at->leftchild, n);
                judge_hight = true;
            }
            if (judge_hight)
            {
                switch (at->bf)
                {
                case LH:
                    judge_hight = false;
                    AVLTreebalance_left(at);//判断是否平衡
                    break;
                case NH:
                    at->bf = LH;
                    break;
                case RH:
                    at->bf = NH;
                    judge_hight = false;
                    break;
                }
            }
        }
        else
        {
            if (at->rightchild)
            {
                AVLTree_Insert(at->rightchild, n, judge_hight);
            }
            else// at->rightchild == NULL
            {
                AVLTreeinit(at->rightchild, n);
                judge_hight = true;
            }
            if (judge_hight)
            {
                switch (at->bf)
                {
                case RH:
                    judge_hight = false;
                    AVLTreebalance_right(at);
                    break;
                case NH:
                    at->bf = RH;
                    break;
                case LH:
                    at->bf = NH;
                    judge_hight = false;
                    break;
                }
            }
        }
    }
}
void AVLTreebalance_left(AVLTree& at)
{
    AVLTree l = at->leftchild, lr = l->rightchild;
    switch (l->bf)
    {
    case LH:
        at->bf = l->bf = NH;
        R_Roll(at);
        break;
    case RH:
        switch (lr->bf)
        {
        case LH:
            at->bf = RH;
            l->bf = NH;
            break;
        case NH:
            at->bf = l->bf = NH;
            break;
        case RH:
            at->bf = NH;
            l->bf = LH;
            break;
        }
        L_Roll(at->leftchild);
        R_Roll(at);
        break;
    }
}
void AVLTreebalance_right(AVLTree& at)//判断树右边是否平衡
{
    AVLTree r = at->rightchild, rl = r->leftchild;
    switch (r->bf)
    {
    case RH:
        at->bf = r->bf = NH;/
        L_Roll(at);
        break;
    case LH:
        switch (rl->bf)
        {
        case LH:
            at->bf = NH;
            r->bf = RH;
            break;
        case NH:
            at->bf = r->bf = NH;
            break;
        case RH:
            at->bf = LH;
            r->bf = NH;
            break;
        }
        R_Roll(at->rightchild);
        L_Roll(at);
        break;
    }
}
void L_Roll(AVLTree& at)//左旋
{
    AVLTree r = at->rightchild;
    at->rightchild = r->leftchild;
    r->leftchild = at;
    at = r;
}
void R_Roll(AVLTree& at)//右旋
{
    AVLTree l = at->leftchild;
    at->leftchild = l->rightchild;
    l->rightchild = at;
    at = l;
}

小编呕心沥血之作,恳请点赞关注支持一下吧~😃 如有错误,敬请斧正


本文转载自: https://blog.csdn.net/weixin_61857742/article/details/123982278
版权归原作者 就要 宅在家 所有, 如有侵权,请联系我们删除。

“手把手教你实现AVL树、平衡二叉树”的评论:

还没有评论