当前位置: 首页 > article >正文

AVL树调整平衡及旋转详解

目录

一.AVl树概述

二.AVl树的旋转

二叉搜索树需要旋转的情况分析

左单旋

左单旋总结

右单旋

右单旋总结 

左右双旋

右左双旋

三.AVL树插入及旋转实现

AVL树节点

AVL树插入

insert部分整体代码

RotateL左单旋函数

RotateRL右左双旋函数

左右双旋

四.判断平衡二叉树

五.完整代码如下


一.AVl树概述

AVl树算是二叉搜索树的一种补充版,普通的二叉搜索树在最坏的情况下可以达到O(n)的时间复杂度,也就是歪脖子树。为了解决二叉搜索树的歪脖子现象,需要对插入的节点进行调整以达到相对平衡,这种经过调整的二叉搜索树叫二叉平衡树,而AVl树是平衡树的一种。AVl树总的来说就是所有节点的左右子树高度差不超过1

所以AVL树有两个性质,第一必须满足二叉搜索树的规则,也就是所有节点的右边子树节点值都比根节点值大,左边节点值都比根节点值小。第二所以节点左右子树高度差(通常称为平衡因子)不超过1

下图中每个节点的数字是平衡因子,也就是当前节点左右子树高度差

二.AVl树的旋转

对于二叉搜索树的一边高的情况,AVl树进行的处理是旋转,旋转完了后这颗树就相对平衡了(左右高度差不超过1),操作分别为左单旋,右单旋,左右双旋,右左双旋

二叉搜索树需要旋转的情况分析

如果一颗树所有的节点左右高度差都是0,那么压根就不需要旋转,因为无论在哪里插入一个节点左右高度差都不会超过1。如下图,在13那个位置插入,从根开始一直到13那个节点所以祖先节点的平衡因子都发生变化,因为从整体来看整条路径上的节点子树高度都发生了改变,但是整体平衡因子高度差绝对值都没有超过1,所以压根就不需要旋转

上图右孩子插入节点的情况

左孩子插入节点的情况

 

第二种情况如果这颗树某个节点(暂且标记为parent)的平衡因子是1或者-1,也就是说这个节点只有一个孩子,另一边为空,如果只在空的那一边插入节点,那么此时parent的平衡因子就变为0,也就是两边平衡了。与第一种情况不同的是,此时parent节点的平衡因子变为0不需要再往上更新祖先节点了,因为高度压根就没增加,只是补齐了空白的位置。二叉树的高度是左右子树当中最大的那一个高度,补齐空白的子树顶多和不空白的子树高度相等,所以整体parent的高度 不会变化

比如说在下图中12的右子树插入节点13,此时12的平衡因子变为了0,但是高度依旧是不变的,所以8的平衡因子不用改变

第三种情况,第三种情况是在第一种情况下又加了一层,也就是说某一个节点的左右高度差绝对值超过了1,达到了2,这时就需要旋转了

 比如下图,15在下面又加了一层节点,13的平衡因子直接变为2了(这时候12和8就不需要进行更新平衡因子了,因为子树已经不平衡了,需要进一步操作才轮的到父节点进行操作),此时针对13及其子树就需要进行旋转处理了

 总结一下,如果要插入节点的上一层节点平衡因子为0插入完后变为1或者-1的话就需要一直往上找祖先节点更新平衡因子,如果插入完依旧是0的话就不需要做任何处理了。如果插入前爷爷节点(当前插入节点的上一层节点的上一层)平衡因子是1或者-1,插入后变为2或者-2就需要进行旋转处理,但是不需要再往上更新祖先节点的平衡因子

左单旋

因为此时有节点的平衡因子绝对值超过了1,变为了2或者-2,所以旋转的操作实际上只需要往下降一层节点就可以了

左单旋是针对新插入的节点在较高右子树的右侧,通俗来讲就是从根出发一直到新插入的那个节点都是右子树比左子树高,这种一边高就需要向左旋转。

左单旋情况的抽象图,为什么不在b上面插入节点呢,在b上插入节点触发的是双旋,另外在下面考虑

为什么要用抽象图来表示旋转呢,这是因为旋转有很多种组合情况,压根就不能完全表示出来,只能抽象来表示,然后提取适用于所有情况的规律出来

当h等于0时,只有两种情况插入会引发旋转,要么在60的左孩子进行插入,要么在60的右孩子进行插入。

那么怎么在不违反二叉搜索树的规则下进行合理平衡旋转呢

让60上来做根就可以了,让30下去做它的右孩子的左孩子就行了。这种依旧是符合二叉搜索树的规则,因为根肯定比右子树的值小,所以根节点完全可以当做孩子

当h等于1时,如下图,新节点在90的下面位置进行插入,只有两种情况,要么在90的左边插入,要么在90的右边插入。在40那个地方插入会触发双旋,暂时不考虑,20的任意位置插入都不会导致任意节点的左右子树高度差超过1或者-1

那么这种h=1的情况怎么进行旋转呢

让60的左子树当作30的右子树,因为此时60的左子树整体都是30的右子树上,所以让整体上肯定比30大,所以可以把他们挂在30的右子树上。而30就算挂上了60的左子树,但是整体上依旧比60小,所以可以全部挂在60的左子树上。

但是当h=2时就有很多种情况了

h=2时,a,b总共有三种样式

一般来讲C只能是第三种样式

如果是C是第一种样式如下图

如果在100的右孩子位置上插入10的平衡因子变成0,往上祖先节点不改变(因为高度没变化),如果在90的左右孩子位置上插入如下图,无论是左孩子插入还是右孩子插入,100的平衡因子都会变成2,在100那个节点上就已经要发生旋转了,暂时不会影响到祖先节点60和30.100,90,80旋转正好是h=0的情况,只不过现在是左边高,所以要右旋。可是右旋完c部分也变成了第三种样式了,依旧是有旋转的风险

右旋完的结果

如果c是第二种样式的情况

如果此时在90的左边插入节点的话,90平衡因子变为0,不会触发旋转。如果在100的左右孩子插入节点90的平衡因子变为2,此时90开始就得发生旋转,旋转完c也变成了第三种样式了

旋转完的情况

如果c直接就是第三种样式如下图

 

 在110节点的孩子的任意位置插入,最上面的根节点30的平衡因子变为2了,所以触发旋转。

为了符合搜索树的规则所以让平衡因子为2的节点(也就是30)的右孩子(也就是60)提上去当根节点,让原根节点(也就是30)下去当60的左孩子,原先60的左孩子给30当右孩子(因为这一部分原先整体位置来看还是在30的右边,所以当30的右孩子不会破坏规则)

左单旋总结

依旧用抽象图来举例

 左单旋不过就是整体右边比左边高1层,此时往右边高的那一层插入新节点,此时整体上右边就比左边高两层了就会触发旋转

而旋转的规则也可以用抽象图总结一下一般规律

要动的地方就三个地方,根节点,根节点的右孩子subR,根节点右孩子的左孩子subRL,parent的右孩子变成subRL,subR的左孩子变为parent。平衡因子也就是aprent和subR发生变化了,其他的都不会变化,其实通俗来讲也就是让根往左边降了一层

抽象图旋转后的情况

但是值得注意的是subR和parent是一点不为空的,但是subRL是有可能为空的,如下图

 

右单旋

 右单旋是左边整体比右边整体高一层,此时再加新节点的情况,只能加到a上面才能触发右单旋,加在b上面触发的是双旋

右单旋抽象图

当h等于0时

h=1时

h=2时

 

右单旋总结 

右单旋抽象图

右单旋就是根节点的左边在未插入之前就已经比右边整体上高一层了,在最高的左侧也就是a插入就会导致左边比右边高两层(根的平衡因子变为-2),此时就需要进行旋转把整棵树压下去一层 

旋转的规律用抽象图来进行总结一下

要动的地方就三个地方,根节点,根节点的左孩子subL,根节点左孩子的右孩子subLR,parent的左孩子变成subLR,subL的右孩子变为parent。平衡因子也就是paprent和subL发生变化了,其他的都不会变化,其实通俗来讲也就是让根往右边降了一层。同样右单旋的subLR也是有可能为空的情况

左右双旋

右单旋是整体是左边高所以将根节点往下调整一层就可以平衡了,但是也有一种情况右单旋了之后变成左单旋了依旧不平衡,如下图。从下图可以看出来虽然都是往左边最高的那一层节点上面去加节点,但是往左孩子上加节点触发的单旋,往右孩子上加节点触发的是双旋(30经过单旋依旧平衡因子变为了2,依旧需要旋转)。总结一下规律就是如果从根开始一直到新插入的节点整条路径都是左边高,那么就是单旋,如果中途有节点是右边高那么就需要双旋了

右单旋和左右双旋对比图

 那么怎么解决双旋呢

也很简单,它不是整体左边高但是路径上有节点是右边高吗,那么直接把它变成整体左边高就行了,就又可以使用右单旋的规则了

 左右双旋抽象图

左右双旋与右单旋对比

在右单旋抽象图b的位置上任意左右孩子插入都会触发双旋,为什么不继续用右单旋的抽相图而是把右单旋图的b部分分离出一个单独的节点和左右孩子各h-1高度的抽象子图,这是因为在右单旋图的b左右孩子插入的平衡因子调整不一样,所以要拆开出左右孩子(左孩子为b,右孩子为c)分别讨论。

在b插入新节点的情况

在c插入新节点情况

其实通过观察上面两张图可以很容易看出来,subLR直接上去当根节点了,subLR的左右两个孩子部分(b和c),左孩子部分给了subL,右孩子部分给了parent,所以要分别讨论b和c哪个位置上插入了新节点。如果是在b位置上插入,此时subL左右子树的节点刚好都是高度h,所以subL平衡因子就变为了 0,相对的parent的左子树少一个节点,所以parent的平衡因子是1。反过来在c位置上插入,c是给parent的,正好parent的两边都是h,因此parent的平衡因子是0,而subL右孩子少一个节点所以平衡因子是-1

右左双旋

右左双旋与左右双旋正好相反,右左双旋是整体上右边高需要左旋,但是从根节点出发到新插入的节点上有节点的左子树比右子树高,所以需要先局部进行右旋然后才能整体左旋

举个例子

上图采用局部先右旋然后整体左旋的情况

右左双旋抽象图

右左双旋旋转后的情况

局部先右旋然后整体左旋

右左双旋就是subRL上去当根节点,它的左子树(也就是b)给parent当右子树,它的右子树(也就是c)给subR当左子树。所以在b上面插入节点和在c上面插入节点调整平衡因子是不一样的,如果是在b上面插入新节点,那么parent的平衡因子等于0,subR平衡因子等于1;如果在c上面插入新节点,那么subR的平衡因子等于0,parent的平衡因子等于-1

三.AVL树插入及旋转实现

AVL树是针对二叉搜索树进行调整以达到平衡,所以它很多东西与操作都和二叉搜索树类似

AVL树节点

AVL树的节点大致与二叉搜索类似,只是多了parent节点指针和bf平衡因子方便后续操作

struct AVLTreeNode
{
    T value;
    AVLTreeNode<T>* left;
    AVLTreeNode<T>* right;
    AVLTreeNode<T>* parent;//当前节点的父节点
    T bf;//平衡因子
    AVLTreeNode(const T& key) :value(key), left(nullptr), right(nullptr), parent(nullptr), bf(0) {};
};

AVL树插入

insert部分整体代码

bool insert(const T& key)  
{  
    // 如果树为空,则创建根节点  
    if (root == nullptr)  
    {  
        root = new Node(key);  
        return true;  
    }  

    Node* cur = root; // 当前节点指针,初始化为根节点  
    Node* _parent = nullptr; // 父节点指针,用于记录当前节点的父节点  

    // 查找插入位置  
    while (cur)  
    {  
        if (key > cur->value) // 如果插入的键大于当前节点的值  
        {  
            _parent = cur; // 更新父节点为当前节点  
            cur = cur->right; // 移动到右子树  
        }  
        else if (key < cur->value) // 如果插入的键小于当前节点的值  
        {  
            _parent = cur; // 更新父节点为当前节点  
            cur = cur->left; // 移动到左子树  
        }  
        else // 如果键已存在,返回 false  
        {  
            return false;  
        }  
    }  

    // 创建新节点并设置其父节点  
    cur = new Node(key);  
    cur->parent = _parent;  

    // 将新节点插入到父节点的左或右子树  
    if (key > _parent->value)  
    {  
        _parent->right = cur; // 插入到右子树  
    }  
    else  
    {  
        _parent->left = cur; // 插入到左子树  
    }  




    // 更新平衡因子并检查是否需要旋转  
    while (_parent)  
    {  
        // 更新父节点的平衡因子  
        if (_parent->left == cur)  
            _parent->bf--; // 如果新节点是左子节点,减少平衡因子  
        if (_parent->right == cur)  
            _parent->bf++; // 如果新节点是右子节点,增加平衡因子  

        // 如果平衡因子为 0,说明树仍然平衡,直接返回  
        if (_parent->bf == 0)  
            return true;  

        // 如果平衡因子为 1 或 -1,继续向上检查  
        else if (_parent->bf == 1 || _parent->bf == -1)  
        {  
            cur = _parent; // 更新当前节点为父节点  
            _parent = _parent->parent; // 更新父节点为其父节点  
        }  
        else // 当平衡因子为 2 或 -2 时,需要进行旋转  
        {  
            // 进行相应的旋转以保持 AVL 树的平衡  
            if (_parent->bf == 2 && cur->bf == 1)  
            {  
                RotateL(_parent); // 右重,进行左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == -1)  
            {  
                RotateR(_parent); // 左重,进行右旋  
            }  
            else if (_parent->bf == 2 && cur->bf == -1)  
            {  
                RotateRL(_parent); // 右左重,进行右旋再左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == 1)  
            {  
                RotateLR(_parent); // 左右重,进行左旋再右旋  
            }  
            else  
            {  
                assert(false); // 不应该到达这里,表示逻辑错误  
            }  
            return true; // 插入成功  
        }  
    }  
    return false; // 如果没有插入成功,返回 false  
}

 AVL树插入与二叉搜索树之间也只是加了平衡因子进行辅助旋转,所以插入代码与原先的二叉搜索树代码都类似,只是多了旋转的代码(具体旋转操作及进行旋转判断条件)。  while (_parent)  以这个循环为分界岭,之前的都是二叉搜索树的插入规则,往下的都是AVL加的调整规则,插入还是正常插入只是要按AVL树的规则进行调整以达到相对平衡

让我来分解和解释一下这段代码

while (_parent)
{
	if (_parent->left == cur)
		_parent->bf--;
	if (_parent->right == cur)
		_parent->bf++;
	if (_parent->bf == 0)
		return true;
	else if (_parent->bf == 1 || _parent->bf == -1)
	{
		cur = _parent;
		_parent = _parent->parent;
	}
	else//当_parent->bf==2||_parent->bf==-2就需要旋转了
	{
		if (_parent->bf == 2 && cur->bf == 1)
		{
			RotateL(_parent);
		}
		else if (_parent->bf == -2 && cur->bf == -1)
		{
			RotateR(_parent);
		}
		else if (_parent->bf == 2 && cur->bf == -1)
		{
			RotateRL(_parent);
		}
		else if(_parent->bf==-2&&cur->bf==1)
		{
			RotateLR(_parent);
		}
		else
		{
			assert(false);
		}
		return true;
	}
}

if (_parent->left == cur)
        _parent->bf--;
    if (_parent->right == cur)
        _parent->bf++;

这段代码就是刚插入之后调整父节点的平衡因子,左边插入相对的左边就搞了一层,所以_parent->bf就减减,右边插入_parent->bf就加加。

 上面平衡因子调整完了,下面的操作就全是判断是否应该进行旋转,如果_parent等于0,说明原先_parent的左右子树是一边高一边低,新插入的节点正好插在低的那部分了,所以直接就平衡了,所以直接return true就可以了。如果等于1或者-1说明原先这颗树是平衡的,插入新节点后使这颗树的高度变高了一层,所以整条路径上高度得更新了。如果等于2或者-2说明直接就不平衡了,需要进行旋转

// 如果平衡因子为 0,说明树仍然平衡,直接返回  
        if (_parent->bf == 0)  
            return true;  

        // 如果平衡因子为 1 或 -1,继续向上检查  
        else if (_parent->bf == 1 || _parent->bf == -1)  
        {  
            cur = _parent; // 更新当前节点为父节点  
            _parent = _parent->parent; // 更新父节点为其父节点  
        }  
        else // 当平衡因子为 2 或 -2 时,需要进行旋转  
        {  
            // 进行相应的旋转以保持 AVL 树的平衡  
            if (_parent->bf == 2 && cur->bf == 1)  
            {  
                RotateL(_parent); // 右高,进行左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == -1)  
            {  
                RotateR(_parent); // 左高,进行右旋  
            }  
            else if (_parent->bf == 2 && cur->bf == -1)  
            {  
                RotateRL(_parent); // 右左高,进行右旋再左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == 1)  
            {  
                RotateLR(_parent); // 左右高,进行左旋再右旋  
            }  
            else  
            {  
                assert(false); // 不应该到达这里,表示逻辑错误  
            }  
            return true; // 插入成功  
        }  
    }  

那么是怎么看出来需要哪种旋转的呢

以右单旋图为例,右单旋说明整条路径上都是整体左边高,所以subL作为子树肯定也是左边高,如果它不是左边高说明这颗树不是整体左边高了(这就是双旋的处理情况)

右单旋抽象图

以再以左右双旋来举个例子,左右双旋是整体左边高,但是子树是右边高,所以要先左旋使整体左边才能进行右单旋。parent这颗不平衡树的根节点(不一定是整颗树的根节点,只是这一部分不平衡的部分的最高节点)所以它肯定为负数,因为整体上它的左子树比右子树高一层。但是它的孩子subL就不一定也是左边高了,因为subL的右孩子高也可以使以subL整颗树高一层。

在b插入新节点的情况

总结一下,不平衡树判断应该使用单旋还是多旋都是看平衡因子节点为2或者-2的节点与它的子节点是否同号来进行处理的,如果为parent平衡因子正数说明这颗不平衡树整体上是右边高,反正整体上是左边高,parnet的子节点cur的平衡因子为正数,说明parent子树也是右边高,反正是左边高。整体右边高子树左边高就说明需要双旋也就是parent和cur平衡因子不同号的情况。

反正单旋双旋看同不同符号就完事了,左旋右旋看平衡因子正数还是负数

 if (_parent->bf == 2 && cur->bf == 1)  
            {  
                RotateL(_parent); // 右高,进行左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == -1)  
            {  
                RotateR(_parent); // 左高,进行右旋  
            }  
            else if (_parent->bf == 2 && cur->bf == -1)  
            {  
                RotateRL(_parent); // 右左高,进行右旋再左旋  
            }  
            else if (_parent->bf == -2 && cur->bf == 1)  
            {  
                RotateLR(_parent); // 左右高,进行左旋再右旋  
            }  
            else  
            {  
                assert(false); // 不应该到达这里,表示逻辑错误  
            }  

RotateL左单旋函数

怎么旋转在前面都讲解过了,就不多加赘述了

唯一值得注意的有两点,一方面subRL是有可能为空的,所以要先判空才能改变它的父节点parent是谁。另一方面如果parent正好是root根节点,那么经过选择parent会转到下面去,而新的根节点会变为subR,所以新root也需要调整

void RotateL(Node* &_parent) // 左单旋  
{  
    Node* subR = _parent->right; // 右子节点  
    Node* subRL = subR->left; // 右子节点的左子节点  
    Node* pparentnode = _parent->parent; // 父节点  

    // 进行旋转  
    _parent->right = subRL; // 将 _parent 的右子树指向 subRL  
    subR->left = _parent; // 将 subR 的左子树指向 _parent  
    _parent->parent = subR; // 更新 _parent 的父节点为 subR  

    // 如果 subRL 存在,更新其父节点  
    if (subRL)  
    {  
        subRL->parent = _parent;  
    }  

    // 更新根节点  
    if (pparentnode == nullptr)  
    {  
        root = subR; // 如果 _parent 是根节点,更新根节点为 subR  
        root->parent = nullptr; // 根节点的父节点为 nullptr  
    }  
    else  
    {  
        // 根据父节点的值更新父节点的左或右子树  
        if (pparentnode->value > subR->value)  
        {  
            pparentnode->left = subR; // 更新父节点的左子树  
            subR->parent = pparentnode; // 更新 subR 的父节点  
        }  
        else  
        {  
            pparentnode->right = subR; // 更新父节点的右子树  
            subR->parent = pparentnode; // 更新 subR 的父节点  
        }  
    }  

    // 调整平衡因子  
    subR->bf = _parent->bf = 0; // 旋转后两个节点的平衡因子都设为 0  
}  

RotateR右单旋函数

情况基本同左单旋就不多赘述了

 

void RotateR(Node*& _parent) // 右单旋  
{  
    Node* subL = _parent->left; // 左子节点  
    Node* subLR = subL->right; // 左子节点的右子节点  
    Node* pparentnode = _parent->parent; // 父节点  

    // 进行旋转  
    _parent->left = subLR; // 将 _parent 的左子树指向 subLR  
    subL->right = _parent; // 将 subL 的右子树指向 _parent  
    _parent->parent = subL; // 更新 _parent 的父节点为 subL  

    // 如果 subLR 存在,更新其父节点  
    if (subLR)  
    {  
        subLR->parent = _parent;  
    }  

    // 更新根节点  
    if (pparentnode == nullptr)  
    {  
        root = subL; // 如果 _parent 是根节点,更新根节点为 subL  
        root->parent = nullptr; // 根节点的父节点为 nullptr  
    }  
    else  
    {  
        // 根据父节点的值更新父节点的左或右子树  
        if (pparentnode->value > subL->value)  
        {  
            pparentnode->left = subL; // 更新父节点的左子树  
            subL->parent = pparentnode; // 更新 subL 的父节点  
        }  
        else  
        {  
            pparentnode->right = subL; // 更新父节点的右子树  
            subL->parent = pparentnode; // 更新 subL 的父节点  
        }  
    }  

    // 调整平衡因子  
    subL->bf = _parent->bf = 0; // 旋转后两个节点的平衡因子都设为 0  
}  

RotateRL右左双旋函数

双旋不用另外再写旋转函数,直接调用就行,双旋关键的是调整平衡因子,因为在左右孩子不同位置插入平衡因子调整都不同。而究竟在左孩子插入还是在右孩子插入直接看subLR的平衡因子就可以了

在b插入新节点的情况
在c插入新节点情况

 

void RotateRL(Node*& _parent) // 右左旋  
{  
    Node* subR = _parent->right; // 右子节点  
    Node* subRL = subR->left; // 右子节点的左子节点  
    int RLbf = subRL->bf; // 保存 subRL 的平衡因子  

    // 先进行右旋,再进行左旋  
    RotateR(subR);  
    RotateL(_parent);  

    // 根据 subRL 的平衡因子调整平衡因子  
    if (RLbf == 0)  
    {  
        subR->bf = 0; // 旋转后平衡因子设为 0  
        subRL->bf = 0;  
        _parent->bf = 0;  
    }  
    else if (RLbf == 1)  
    {  
        subR->bf = 0; // 旋转后平衡因子设为 0  
        subRL->bf = 0;  
        _parent->bf = -1; // 更新 _parent 的平衡因子  
    }  
    else  
    {  
        subR->bf = 1; // 更新 subR 的平衡因子  
        subRL->bf = 0;  
        _parent->bf = 0; // 更新 _parent 的平衡因子  
    }  
}  

左右双旋

大致情况与上面类似就不加赘述了

void RotateLR(Node* &_parent) // 左右旋  
{  
    Node* subL = _parent->left; // 左子节点  
    Node* subLR = subL->right; // 左子节点的右子节点  
    int LRbf = subLR->bf; // 保存 subLR 的平衡因子  

    // 先进行左旋,再进行右旋  
    RotateL(subL);  
    RotateR(_parent);  

    // 根据 subLR 的平衡因子调整平衡因子  
    if (LRbf == 0)  
    {  
        _parent->bf = 0; // 旋转后平衡因子设为 0  
        subL->bf = 0;  
        subLR->bf = 0;  
    }  
    else if (LRbf == 1)  
    {  
        _parent->bf = 0; // 更新 _parent 的平衡因子  
        subL->bf = -1; // 更新 subL 的平衡因子  
        subLR->bf = 0;  
    }  
    else  
    {  
        _parent->bf = 1; // 更新 _parent 的平衡因子  
        subL->bf = 0;  
        subLR->bf = 0;  
    }  
}

四.判断平衡二叉树

一方面判断左右子树高度差是否大于1,另一方面判断平衡因子是否正确,平衡因子是否正确只要看父平衡因子是否等于左右高度差就可以了

bool banlancetree() // 检查整棵树是否平衡  
{  
    return _banlancetree(root); // 调用私有递归函数检查平衡  
}  

int treeheight(Node* root) // 计算树的高度  
{  
    if (root == nullptr) // 如果节点为空,返回高度 0  
        return 0;  

    // 递归计算左子树和右子树的高度  
    int left = treeheight(root->left);  
    int right = treeheight(root->right);  

    // 返回较大高度加 1  
    return left > right ? left + 1 : right + 1;  
}  

bool _banlancetree(Node* root) // 递归检查树的平衡性  
{  
    if (root == nullptr) // 如果节点为空,返回 true(空树是平衡的)  
        return true;  

    // 计算当前节点的左子树和右子树的高度  
    int left = treeheight(root->left);  
    int right = treeheight(root->right);  

    // 检查当前节点的平衡因子是否异常  
    if (right - left != root->bf)  
    {  
        // 输出异常信息  
        cout << "平衡因子异常:" << root->value << endl;  
        cout << "平衡因子" << root->bf << endl;  
        cout << "left高度 " << left << endl;  
        cout << "right高度 " << right << endl;  

        // 输出左右子节点的值(如果存在)  
        if (root->left) cout << root->left->value << endl;  
        if (root->right) cout << root->right->value << endl;  

        return false; // 返回 false,表示树不平衡  
    }  

    // 检查当前节点的左右子树是否平衡,并返回结果  
    return abs(right - left) < 2 && _banlancetree(root->left) && _banlancetree(root->right);  
}

五.完整代码如下

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

template<class T>
struct AVLTreeNode
{
	T value;
	AVLTreeNode<T>* left;
	AVLTreeNode<T>* right;
	AVLTreeNode<T>* parent;
	T bf;
	AVLTreeNode(const T& key) :value(key), left(nullptr), right(nullptr), parent(nullptr), bf(0) {};
};


template<class T>
class AVLTree
{
public:
	typedef AVLTreeNode<T> Node;
	void RotateL(Node* &_parent)//左单旋
	{
		Node*subR = _parent->right;
		Node*subRL = subR->left;
		Node* pparentnode = _parent->parent;

		_parent->right = subRL;
		subR->left = _parent;
		_parent->parent = subR;
		if (subRL)
		{
			subRL->parent = _parent;
		}

		if (pparentnode == nullptr)
		{
			root = subR;
			root->parent = nullptr;
		}
		else
		{
			if (pparentnode->value > subR->value)
			{
				pparentnode->left = subR;
				subR->parent = pparentnode;
			}
			else
			{
				pparentnode->right = subR;
				subR->parent =pparentnode;
			}
			
		}
		subR->bf = _parent->bf = 0;//调整平衡因子
	}

	void RotateR(Node*& _parent)//右单旋
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* pparentnode = _parent->parent;
		_parent->left = subLR;
		subL->right = _parent;
		_parent->parent = subL;
		if (subLR)
		{
			subLR->parent = _parent;
		}
		if (pparentnode==nullptr)
		{
			root = subL;
			root->parent = nullptr;
		}
		else
		{
			if (pparentnode->value > subL->value)
			{
				pparentnode->left = subL;
				subL->parent = pparentnode;
			}
			else
			{
				pparentnode->right = subL;
				subL->parent = pparentnode;
			}
		}
		subL->bf = _parent->bf = 0;
	}


	void RotateRL(Node*&_parent)
	{
		Node*subR = _parent->right;
		Node* subRL = subR->left;
		int RLbf = subRL->bf;
		RotateR(subR);
		RotateL(_parent);
		if (RLbf == 0)
		{
			subR->bf = 0;
			subRL->bf = 0;
			_parent->bf = 0;
		}
		else if (RLbf == 1)
		{
			subR->bf = 0;
			subRL->bf = 0;
			_parent->bf = -1;
		}
		else
		{
			subR->bf = 1;
			subRL->bf = 0;
			_parent->bf = 0;
		}
	}

	void RotateLR(Node* &_parent)
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		int LRbf=subLR->bf;
		
		RotateL(subL);
		RotateR(_parent);
		if (LRbf == 0)
		{
			_parent->bf = 0;
			subL->bf = 0;
			subLR->bf = 0;
		}
		else if (LRbf == 1)
		{
			_parent->bf = 0;
			subL->bf = -1;
			subLR->bf = 0;
		}
		else
		{
			_parent->bf = 1;
			subL->bf = 0;
			subLR->bf = 0;
		}
	}

	bool insert(const T& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		Node* cur = root;
		Node* _parent = nullptr;
		while (cur)
		{
			if (key > cur->value)
			{
				_parent = cur;
				cur = cur->right;
			}
			else if(key<cur->value)
			{
				_parent = cur;
				cur = cur->left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		cur->parent = _parent;
		if (key > _parent->value)
		{
			_parent->right = cur;
		}
		else
		{
			_parent->left = cur;
		}
		while (_parent)
		{
			if (_parent->left == cur)
				_parent->bf--;
			if (_parent->right == cur)
				_parent->bf++;
			if (_parent->bf == 0)
				return true;
			else if (_parent->bf == 1 || _parent->bf == -1)
			{
				cur = _parent;
				_parent = _parent->parent;
			}
			else//当_parent->bf==2||_parent->bf==-2就需要旋转了
			{
				if (_parent->bf == 2 && cur->bf == 1)
				{
					RotateL(_parent);
				}
				else if (_parent->bf == -2 && cur->bf == -1)
				{
					RotateR(_parent);
				}
				else if (_parent->bf == 2 && cur->bf == -1)
				{
					RotateRL(_parent);
				}
				else if(_parent->bf==-2&&cur->bf==1)
				{
					RotateLR(_parent);
				}
				else
				{
					assert(false);
				}
				return true;
			}
		}
		return false;
	}

	void InOrder()
	{
		_InOrder(root);
		cout << endl;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->left);
		cout << root->value << " ";
		_InOrder(root->right);
	}

	bool banlancetree()
	{
		return _banlancetree(root);
	}
	int treeheight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int left = treeheight(root->left);
		int right = treeheight(root->right);
		return left > right ? left + 1 : right + 1;
	}
	bool _banlancetree(Node* root)
	{
		if (root == nullptr)
			return true;
		int left = treeheight(root->left);
		int right = treeheight(root->right);
		if (right - left != root->bf)
		{
			cout << "平衡因子异常:" << root->value<<endl;
			cout << "平衡因子" << root->bf << endl;
			cout << "left高度 "<<left << endl;
			cout << "right高度 "<<right << endl;
			cout << root->left->value << endl;
			cout << root->right->value << endl;
			return false;
		}
		return abs(right - left) < 2 && _banlancetree(root->left) && _banlancetree(root->right);
	}
private:
	Node* root=nullptr;
};


http://www.kler.cn/a/290248.html

相关文章:

  • 985研一学习日记 - 2024.11.12
  • Apache ECharts
  • 2-UML概念模型测试
  • 【Linux篇】面试——用户和组、文件类型、权限、进程
  • C++《stack与queue》
  • ubuntu下安装 git 及部署cosyvoice(2)
  • MATLAB-绘图系列(第一期)
  • 线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
  • 性能、成本与 POSIX 兼容性比较: JuiceFS vs EFS vs FSx for Lustre
  • ElasticSearch和Kibana的安全设置以及https设置
  • Vue 3 的性能提升具体体现在哪些方面?
  • 达梦数据库事务管理
  • 深入解析浏览器与Web服务器的通信机制:从URL输入到页面渲染的全过程
  • glsl着色器学习(七)
  • 多线程篇(基本认识 - 锁优化)(持续更新迭代)
  • HCIP笔记12-交换(1)
  • Mysql高级篇(上)—— Mysql架构介绍(二)
  • SprinBoot+Vue图书馆预约与占座微信小程序的设计与实现
  • C++string类相关OJ练习(2)
  • 【32项目】基于stm32f103c8t6的智能拐杖(文章末尾含完整代码)
  • MAC打开IDA Pro意外退出
  • 论文辅助笔记:LP_BERT
  • 【60天备战软考高级系统架构设计师——第一天:软件工程概述】
  • ListBox等控件的SelectedItem,SelectedValue,SelectedValuePath属性详解
  • 0904,关联式容器针对于自定义形式的写法(
  • 华为数据之道-读书笔记