一篇文章教会你什么是高度平衡二叉搜索(AVL)树

news2024/10/12 8:12:31

高度平衡二叉搜索树

  • AVL树的概念
    • 1.操作
    • 2.删除
    • 3.搜索
    • 4.实现描述
  • AVL树的实现
    • 1.AVL树节点的定义
    • 2.AVL树的插入
    • 3.AVL树的旋转
      • 3.1 新节点插入较高右子树的右侧---右右:左单旋
      • 3.2 新节点插入较高左子树的左侧---左左:右单旋
      • 3.3 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
      • 3.4 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
    • 4.AVL树的验证
      • 4.1 验证其为二叉搜索树
      • 4.2 验证其为平衡树
      • 4.3 验证用例
  • AVL树实现及验证所有代码
    • 1.AVL代码实现
    • 2.AVL验证代码实现

AVL树的概念

AVL树Adelson-Velsky and Landis Tree)是计算机科学中最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logN)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL树得名于它的发明者G. M. Adelson-VelskyEvgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

算法平均最差
空间O(n)O(n)
搜索O(log n)O(log n)
插入O(log n)O(log n)
删除O(log n)O(log n)

1.操作

AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。

以下图表以四列表示四种情况,每行表示在该种情况下要进行的操作。在左左和右右的情况下,只需要进行一次旋转操作;在左右和右左的情况下,需要进行两次旋转操作。

请添加图片描述

下面动画演示了不断将节点插入AVL树时的情况,并且演示了左旋Left Rotation)、右旋Right Rotation)、右左旋转Right-Left Rotation)、左右旋转Left-Right Rotation)以及带子树的右旋Right Rotation with children
在这里插入图片描述

2.删除

从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。

3.搜索

可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树搜索相对立的,它会因为搜索而变更树结构。)

4.实现描述

假设平衡因子是左子树的高度减去右子树的高度所得到的值,又假设由于在二叉排序树上插入节点而失去平衡的最小子树根节点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先节点),则失去平衡后进行的规律可归纳为下列四种情况:

  1. 单向右旋平衡处理LL:由于在*a的左子树根节点的左子树上插入节点,a的平衡因子由1增至2,致使以a为根的子树失去平衡,则需进行一次右旋转操作;
  2. 单向左旋平衡处理RR:由于在*a的右子树根节点的右子树上插入节点,a的平衡因子由-1变为-2,致使以a为根的子树失去平衡,则需进行一次左旋转操作;
  3. 双向旋转(先左后右)平衡处理LR:由于在*a的左子树根节点的右子树上插入节点,a的平衡因子由1增至2,致使以a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。
  4. 双向旋转(先右后左)平衡处理RL:由于在*a的右子树根节点的左子树上插入节点,a的平衡因子由-1变为-2,致使以a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。

在平衡二叉排序树AVL树(Adelson-Velsky and Landis Tree)上插入一个新的数据元素e的递归算法可描述如下

  1. 若AVL树为空树,则插入一个数据元素为e的新节点作为AVL树的根节点,树的深度增1;
  2. 若e的关键字和AVL树的根节点的关键字相等,则不进行;
  3. 若e的关键字小于AVL树的根节点的关键字,而且在AVL树的左子树中不存在和e有相同关键字的节点,则将e插入在AVL树的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:
    1. AVL树的根节点的平衡因子为-1(右子树的深度大于左子树的深度,则将根节点的平衡因子更改为0,BBST的深度不变;
    2. AVL树的根节点的平衡因子为0(左、右子树的深度相等):则将根节点的平衡因子更改为1,BBST的深度增1;
    3. AVL树的根节点的平衡因子为1(左子树的深度大于右子树的深度):则若AVL树的左子树根节点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根节点和其右子树根节点的平衡因子更改为0,树的深度不变;
  4. 若e的关键字大于AVL树的根节点的关键字,而且在AVL树的右子树中不存在和e有相同关键字的节点,则将e插入在AVL树的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。

AVL树的实现

1.AVL树节点的定义

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

这个C++模板结构体,表示一个AVL树的节点(AVLTreeNode),AVL树是一种自平衡二叉搜索树。这个结构体包含了以下成员:

  1. _left:指向左子节点的指针。
  2. _right:指向右子节点的指针。
  3. _parent:指向父节点的指针。
  4. _kv:一个键值对,用来存储节点的关键字和关联的值。
  5. _bf:平衡因子(Balance Factor),用来表示节点的平衡状态。通常,平衡因子是左子树的高度减去右子树的高度。AVL树要求每个节点的平衡因子在[-1, 1]范围内,以保持树的平衡。

这个结构体表示了AVL树中的一个节点,通常在AVL树的实现中,你会有一个指向根节点的指针来访问整个树。AVL树的节点结构包括平衡因子 _bf 是为了帮助维持树的平衡,当插入或删除节点时,需要根据平衡因子来进行相应的旋转操作,以确保树的平衡性。

2.AVL树的插入

这里我们首先定义结构体struct AVLTree

包含下面的成员:

typedef AVLTreeNode<K, V> Node;
private:
	Node* _root = nullptr;

定义插入成员函数:

bool Insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }

    cur->_parent = parent;

    // 控制平衡
    while (parent)
    {
        if (cur == parent->_right)
        {
            parent->_bf++;
        }
        else
        {
            parent->_bf--;
        }

        if (parent->_bf == 0)
        {
            break;
        }
        else if (abs(parent->_bf) == 1)
        {
            parent = parent->_parent;
            cur = cur->_parent;
        }
        else if (abs(parent->_bf) == 2)
        {
            // 说明parent所在子树已经不平衡了,需要旋转处理
            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)
            {
                RotateLR(parent);
            }
            else if (parent->_bf == 2 && cur->_bf == -1)
            {
                RotateRL(parent);
            }
            else
            {
                assert(false);
            }

            break;
        }
        else
        {
            assert(false);
        }
    }

    return true;
}

这段代码的主要功能是往AVL树中插入一个新的键值对 kv,并在插入后维护树的平衡性。下面是代码的主要步骤:

  1. 如果树为空(_root == nullptr),则直接创建一个新的根节点 _root 并插入 kv,然后返回。
  2. 如果树不为空,进入插入节点的逻辑:
    • 使用 parentcur 指针来遍历树,找到应该插入的位置。
    • 如果当前节点 cur 的关键字小于 kv 的关键字,则向右子树移动,否则向左子树移动,直到找到一个空位置插入新节点。
  3. 插入新节点后,需要更新节点的父节点指针 _parent
  4. 接下来是维护树的平衡性的逻辑:
    • 在插入过程中,通过循环向上更新父节点的平衡因子 _bf
    • 如果某个节点的平衡因子为0,表示它的子树高度没有变化,可以停止更新平衡因子,因为父节点的平衡因子也不会改变。
    • 如果某个节点的平衡因子为1或-1,表示它的子树高度发生了变化,需要向上继续更新平衡因子。
    • 如果某个节点的平衡因子为2或-2,表示树已经不平衡了,需要进行旋转操作来恢复平衡。
  5. 旋转操作的选择取决于不平衡节点及其子节点的平衡因子情况。通常,AVL树有四种旋转操作,分别是左旋RotateL)、右旋RotateR)、左右旋RotateLR)和右左旋RotateRL)。

平衡因子的更新规则

  1. 新增在左,parent->bf--;新增在右,parent->bf++
  2. 更新后,parent->bf==1 or -1,说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,需要继续往上更新
  3. 更新后,parent->bf==0,说明parent插入前的平衡因子是1 or -1,说明左右子树一边高一边低,插入后两边一样高,插入填上了矮的那边,parent所在子树高度不变,不需要继续往上更新
  4. 更新后,parent->bf==2 or -2,说明parent插入前的平衡因子是1 or -1,已经平衡临界值,插入变成2 or -2,parent所在子树需要旋转处理
  5. 更新后,parent->bf>2 or <-2,这个条件是不成立的,如果存在,则说明插入前就存在问题,需向前检查

3.AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种 :

3.1 新节点插入较高右子树的右侧—右右:左单旋

在这里插入图片描述

void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;

    Node* ppNode = parent->_parent;

    subR->_left = parent;
    parent->_parent = subR;

    if (_root == parent)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR;
        }
        else
        {
            ppNode->_right = subR;
        }

        subR->_parent = ppNode;
    }

    subR->_bf = parent->_bf = 0;
}
  1. 首先,保存父节点 parent 的右子树 subRsubR 的左子树 subRL 的指针。
  2. parent 的右子树指针 _right 指向 subRL,即将 subRL 作为新的 parent 的右子树。
  3. 如果 subRL 存在(不为 nullptr),则将 subRL 的父节点指针 _parent 指向 parent,以确保树的连接正确。
  4. 获取 parent 的父节点指针 ppNode,以确定如何连接 subR
  5. subR 的左子树指针 _left 指向 parent,同时将 parent 的父节点指针 _parent 指向 subR,完成左旋转。
  6. 如果 parent 是根节点(_root == parent),则需要更新根节点 _rootsubR,并将 subR 的父节点指针 _parent 设置为 nullptr,以确保树的根正确连接。
  7. 否则,如果 parent 不是根节点,根据 parent 在其父节点 ppNode 中的位置,将 subR 连接到正确的位置,更新 subR 的父节点指针 _parentppNode
  8. 最后,将 parentsubR 的平衡因子 _bf 设置为0,因为在左旋转后,它们的高度没有变化。

3.2 新节点插入较高左子树的左侧—左左:右单旋

在这里插入图片描述

void RotateR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    parent->_left = subLR;
    if (subLR)
        subLR->_parent = parent;

    Node* ppNode = parent->_parent;

    subL->_right = parent;
    parent->_parent = subL;

    if (_root == parent)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }

        subL->_parent = ppNode;
    }

    subL->_bf = parent->_bf = 0;
}
  1. 首先,保存父节点 parent 的左子树 subLsubL 的右子树 subLR 的指针。
  2. parent 的左子树指针 _left 指向 subLR,即将 subLR 作为新的 parent 的左子树。
  3. 如果 subLR 存在(不为 nullptr),则将 subLR 的父节点指针 _parent 指向 parent,以确保树的连接正确。
  4. 获取 parent 的父节点指针 ppNode,以确定如何连接 subL
  5. subL 的右子树指针 _right 指向 parent,同时将 parent 的父节点指针 _parent 指向 subL,完成右旋转。
  6. 如果 parent 是根节点(_root == parent),则需要更新根节点 _rootsubL,并将 subL 的父节点指针 _parent 设置为 nullptr,以确保树的根正确连接。
  7. 否则,如果 parent 不是根节点,根据 parent 在其父节点 ppNode 中的位置,将 subL 连接到正确的位置,更新 subL 的父节点指针 _parentppNode
  8. 最后,将 parentsubL 的平衡因子 _bf 设置为0,因为在右旋转后,它们的高度没有变化。

原理同左单旋

3.3 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

在这里插入图片描述

void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;

    RotateL(parent->_left);
    RotateR(parent);

    subLR->_bf = 0;
    if (bf == 1)//上图为例,新增节点在c下方
    {
        parent->_bf = 0;
        subL->_bf = -1;
    }
    else if (bf == -1)//上图为例,新增节点在b下方
    {
        parent->_bf = 1;
        subL->_bf = 0;
    }
    else if (bf == 0)//上图为例,无其他子树,60为新增节点的情况
    {
        parent->_bf = 0;
        subL->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
  1. 首先,保存父节点 parent 的左子树 subLsubL 的右子树 subLR 的指针,以及 subLR 的平衡因子 bf
  2. parent 的左子树 subL 进行左旋转操作,以调整子树的结构。
  3. 然后,对 parent 进行右旋转操作,以将 subL 成为 parent 的右子树。
  4. subLR 的平衡因子 _bf 设置为0,因为它在旋转后的位置高度没有变化。
  5. 根据 subLR 的平衡因子 bf 的不同值来更新节点的平衡因子:
    • 如果 bf 为1,表示 subL 的左子树高度大于右子树,将 parent 的平衡因子设置为0,subL 的平衡因子设置为-1。
    • 如果 bf 为-1,表示 subL 的右子树高度大于左子树,将 parent 的平衡因子设置为1,subL 的平衡因子设置为0。
    • 如果 bf 为0,表示 subL 的左右子树高度相等,将 parentsubL 的平衡因子都设置为0。
  6. 如果 bf 不是1、-1或0,那么会触发 assert(false),表示出现了异常情况。

3.4 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

在这里插入图片描述

void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    int bf = subRL->_bf;

    RotateR(parent->_right);
    RotateL(parent);

    subRL->_bf = 0;
    if (bf == 1)
    {
        subR->_bf = 0;
        parent->_bf = -1;
    }
    else if (bf == -1)
    {
        subR->_bf = 1;
        parent->_bf = 0;
    }
    else if (bf == 0)
    {
        parent->_bf = 0;
        subR->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
  1. 首先,保存父节点 parent 的右子树 subRsubR 的左子树 subRL 的指针,以及 subRL 的平衡因子 bf
  2. parent 的右子树 subR 进行右旋转操作,以调整子树的结构。
  3. 然后,对 parent 进行左旋转操作,以将 subR 成为 parent 的左子树。
  4. subRL 的平衡因子 _bf 设置为0,因为它在旋转后的位置高度没有变化。
  5. 根据 subRL 的平衡因子 bf 的不同值来更新节点的平衡因子:
    • 如果 bf 为1,表示 subRL 的左子树高度大于右子树,将 subR 的平衡因子设置为0,parent 的平衡因子设置为-1。
    • 如果 bf 为-1,表示 subRL 的右子树高度大于左子树,将 subR 的平衡因子设置为1,parent 的平衡因子设置为0。
    • 如果 bf 为0,表示 subRL 的左右子树高度相等,将 parentsubR 的平衡因子都设置为0。
  6. 如果 bf 不是1、-1或0,那么会触发 assert(false),表示出现了异常情况。

原理同先左单旋再右单旋

总结
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

当pSubR的平衡因子为1时,执行左单旋
当pSubR的平衡因子为-1时,执行右左双旋

  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

当pSubL的平衡因子为-1是,执行右单旋
当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

4.AVL树的验证

4.1 验证其为二叉搜索树

中序遍历可得到一个有序的序列,就说明为二叉搜索树

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

        _InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << endl;
        _InOrder(root->_right);
    }
  1. 首先,检查当前节点 root 是否为空(即树是否为空)。如果为空,则返回,结束递归。
  2. 接着,递归地调用 _InOrder 函数,遍历左子树 root->_left。这将按照升序访问左子树中的节点。
  3. 然后,输出当前节点 root 的关键字和关联的值,通常使用 cout 输出到控制台。
  4. 最后,再次递归调用 _InOrder 函数,遍历右子树 root->_right。这将按照升序访问右子树中的节点。

4.2 验证其为平衡树

  1. 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)

  2. 节点的平衡因子是否计算正确

高度成员函数

int Height(Node* root)
{
    if (root == nullptr)
        return 0;

    return max(Height(root->_left), Height(root->_right)) + 1;
}
  1. 首先,检查当前节点 root 是否为空(即树是否为空)。如果为空,则返回高度0,表示空树的高度为0。
  2. 如果当前节点 root 不为空,那么递归地调用 Height 函数来计算左子树的高度和右子树的高度。
  3. 使用 max 函数比较左子树和右子树的高度,然后加上1(当前节点的高度),得到整棵树的高度。
  4. 返回树的高度作为函数的结果。

平衡树检测函数

bool IsBalance()
{
    return _IsBalance(_root);
}
private:
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		int diff = rightHT - leftHT;

		if (diff != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(diff) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
  1. 首先,检查当前节点 root 是否为空(即树是否为空)。如果为空,则返回 true,因为空树是平衡的。
  2. 如果当前节点 root 不为空,那么首先计算左子树和右子树的高度,分别存储在 leftHTrightHT 中。
  3. 然后,计算左子树和右子树高度差(右子树高度减去左子树高度),存储在 diff 变量中。
  4. 检查当前节点的平衡因子 _bf 是否等于 diff,如果不相等,表示平衡因子异常,输出错误信息并返回 false
  5. 继续检查当前节点是否满足AVL树的平衡条件,即平衡因子的绝对值不超过1,以及递归检查左子树和右子树是否也是平衡的(调用 _IsBalance 函数)。
  6. 如果所有条件都满足,返回 true 表示当前子树是平衡的。

4.3 验证用例

常规场景1

{16, 3, 7, 11, 9, 26, 18, 14, 15}

特殊场景2

{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}  

在这里插入图片描述

AVL树实现及验证所有代码

1.AVL代码实现

AVL.hpp

#pragma once
#include <iostream>
#include <algorithm>
#include <assert.h>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

template<class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

private:
	int BalanceFactor(Node* node)
	{
		if (node == nullptr)
		{
			return 0;
		}
		return Height(node->_left) - Height(node->_right);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		int diff = rightHT - leftHT;

		if (diff != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(diff) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		return max(Height(root->_left), Height(root->_right)) + 1;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

		subR->_bf = parent->_bf = 0;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

		subL->_bf = parent->_bf = 0;
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		subLR->_bf = 0;
		if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

2.AVL验证代码实现

AVLTEST.cpp

#include "AVL.hpp"
int main()
{
    //int a[]={16, 3, 7, 11, 9, 26, 18, 14, 15};
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    AVLTree<int, int> avl1;
    for (auto e : a)
        avl1.Insert(make_pair(e, e));

    avl1.InOrder();

    cout << "IsBlance:" << avl1.IsBalance() << endl;
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/992977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【牛客面试必刷TOP101】Day3.BM10两个链表的第一个公共结点和BM13判断一个链表是否为回文结构

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;牛客面试必刷TOP101 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&…

机器学习入门教学——决策树

1、简介 决策树算法是一种归纳分类算法&#xff0c;它通过对训练集的学习&#xff0c;挖掘出有用的规则&#xff0c;用于对新数据进行预测。决策树算法属于监督学习方法。决策树归纳的基本算法是贪心算法&#xff0c;自顶向下来构建决策树。 贪心算法&#xff1a;在每一步选择…

LeetCode-80. 删除有序数组中的重复项 II-⭐⭐

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 提示&…

解决vue3类型“{}”上不存在属性

刚创建的一个Vue3和Ts的项目&#xff0c;结果使用Vscode打开后&#xff0c;修改了index.vue文件就报错了: 修改tsconfig.json文件 在tsconfig.json文件中添加一行代码&#xff1a;就是让ts识别vue文件 "include": ["src/**/*.ts", "src/**/*.d.ts&q…

机器学习入门教学——过拟合、欠拟合、模型验证、样本拆分

1、过拟合 定义 过拟合指的是模型对训练数据拟合的太好&#xff0c;以至于无法很好地泛化到新数据。原因 训练数据不足模型太复杂&#xff08;如深层神经网络&#xff09;训练时间太长后果 模型在训练数据上表现良好&#xff0c;但在测试数据上表现较差。解决方法 增加训练数据…

今天,外滩大会“开发者创新营地”成为技术爱好者打卡点

9月9日&#xff0c;上海&#xff0c;黄浦江边。室外温度31度&#xff0c;依然挡不住技术爱好者们的热情&#xff01;他们涌入了外滩大会最值得期待的打卡点&#xff1a;开发者创新营地。 开发者创新营地是本次外滩大会搭建的技术人交流平台&#xff0c;以闪电演讲&#xff08;…

山西电力市场日前价格预测【2023-09-10】

日前价格预测 预测明日&#xff08;2023-09-10&#xff09;山西电力市场全天平均日前电价为359.80元/MWh。其中&#xff0c;最高日前电价为412.31元/MWh&#xff0c;预计出现在18: 45。最低日前电价为322.32元/MWh&#xff0c;预计出现在13: 45。 价差方向预测 1&#xff1a; 实…

【实例项目:基于多设计模式下的日志系统(同步异步)】

一、项目简介 1.日志的概念&#xff08;白话版&#xff09; 日志类似于日记&#xff0c;通常是指对完成某件事情的过程中状态等的记录&#xff0c;而计算机中的日志是指日志数据&#xff0c;是有价值的信息宝库&#xff0c;各种操作系统、应用程序、设备和安全产品的日志数据能…

Baichuan2开源大模型正式发布,王小川:性能超过LLaMA2

大数据产业创新服务媒体 ——聚焦数据 改变商业 ChatGPT火爆出圈之后&#xff0c;社交巨头Meta奋力追赶&#xff0c;分别在3月、7月发布开源的LLaMA、LLaMA2大模型&#xff0c;引领一场大模型开源运动。 LLaMA2大模型&#xff0c;包含了70亿、130亿、700亿参数三个版本&#x…

对Transformer中的Attention(注意力机制)的一点点探索

摘要&#xff1a;本文试图对 Transformer 中的 Attention 机制进行一点点探索。并就 6 个问题深入展开。 ✅ NLP 研 1 选手的学习笔记 简介&#xff1a;小王&#xff0c;NPU&#xff0c;2023级&#xff0c;计算机技术 研究方向&#xff1a;文本生成、摘要生成 文章目录 一、为啥…

强大的JTAG边界扫描(3):常用边界扫描测试软件

文章目录 1. 功能强大的XJTAG2. 小巧简洁的TopJTAG3. TopJTAG安装4. TopJTAG基本使用 本文介绍两款常用的边界扫描测试软件&#xff1a;XJTAG和TopJTAG&#xff0c;前者收费、功能强大&#xff0c;后者免费&#xff08;和谐后&#xff09;&#xff0c;功能简洁。 如果只是要进…

深入探讨梯度下降:优化机器学习的关键步骤(三)

文章目录 &#x1f340;引言&#x1f340;随机、批量梯度下降的差异&#x1f340;随机梯度下降的实现&#x1f340;随机梯度下降的调试 &#x1f340;引言 随机梯度下降是一种优化方法&#xff0c;主要作用是提高迭代速度&#xff0c;避免陷入庞大计算量的泥沼。在每次更新时&a…

【图神经网络 01】

图的基本构成&#xff1a; V&#xff1a;Vertex (or node) attributes E&#xff1a;Edge (or link) attributes and directions U&#xff1a;Global (or master node) attributes 图的邻接矩阵&#xff1a;文本数据也可以表示图的形式&#xff0c;邻接矩阵表示的连接关系。 以…

计算机竞赛 基于深度学的图像修复 图像补全

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学的图像修复 图像补全 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-se…

牛客网项目-第一章-笔记

牛客网项目-第一章 环境配置 java maven idea Spring Intializr 搜索jar包的网站&#xff1a;https://mvnrepository.com/ https://start.spring.io/ 缺少的aop包&#xff0c;手动在pom.xml中加入依赖 <dependency><groupId>org.springframework.boot</gro…

OpenRoads Designer导入文本格式水平路线、路线纵断面

ORD可以用以文本文件进行定义水平几何路线及纵断面几何路线直接导入来完成几何路线的定义&#xff1a; 水平路线 平面几何路线示例 “平面几何路线.txt”文件内容&#xff1a; BP-1,54376.169,1816.914 BP,54376.101,1817.912 JD01,54358.369,2081.452,0 JD02,54810.789,477…

linux 基础命令 cd /xxx 和 cd xxx 的区别

cd 命令&#xff1a;用于改变当前工作目录的命令&#xff0c;作用&#xff1a;切换当前目录至其它目录 用 cd 命令去 home目录&#xff1a; cd home/ 用cd 命令 去tony 目录下 cd ../ 返回上级目录 cd ../ tony / 返回上级目录进入和hom 同级的tony 目录 这里要讲 linux …

树(一)树和二叉树的基本概念

文章目录 一、树1、什么是树2、树的相关概念3、树的表示 二、二叉树1、二叉树的概念2、二叉树的几种情况3、特殊二叉树4、二叉树的性质5、二叉树的存储结构 一、树 1、什么是树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个…

C++学习——vector类的使用

目录 vector类的介绍&#xff1a; vector类的构造函数: operator operator [ ] begin & end size & resize capacity & reserve push_back & pop_back insert & erase vector类的介绍&#xff1a; vector是C标准模板库中的部分内容&#xff0c;中文偶尔…

【Python】OpenCV立体相机配准与三角化代码实现

下面的介绍了使用python和OpenCV对两个相机进行标定、配准,同时实现人体关键点三角化的过程 import cv2 as cv import glob import numpy as np import matplotlib.pyplot as pltdef calibrate_camera(images_folder):images_names = glob.glob(images_folder