数据与结构--AVL树

目录

AVL树的概念

AVL树的性质

AVL树结点的定义 

AVL树的插入 

AVL树的旋转 

左单旋

右单旋

左右双旋

右左单旋

 AVL树的验证

AVL树的查找 

AVL树的修改

 AVL树的删除


AVL树的概念

二叉搜索树虽然可以提高我们查找数据的效率,但如果插入二叉搜索树的数据是有序或接近有序的,此时二叉搜索树会退化为单支树,在单支树当中查找数据相当于在单链表当中查找数据,效率是很低下的。

AVL树是一种自平衡二叉搜索树,由Adelson-Velsky和Landis于1962年发明,因其发明者的名字而得名。AVL树通过在每次插入或删除节点后进行旋转操作来保持树的平衡,从而保证其高度始终保持在O(log n)的范围内,从而实现高效的查找、插入和删除操作。

AVL树的性质

  1. 二叉搜索树的性质:AVL树首先是一棵二叉搜索树,所以它必须满足二叉搜索树的性质:对于每个节点N,左子树中所有节点的值都小于N的值,右子树中所有节点的值都大于N的值。

  2. 平衡因子:对于每个节点,AVL树的左子树和右子树的高度差不超过1。这种高度差称为平衡因子(balance factor),即

    平衡因子=左子树高度-右子树高度

    平衡因子只能是-1、0或1。

在AVL树中,平衡因子左子树高度右子树高度是用来评估树的平衡状态的重要指标。

  1. 平衡因子(Balance Factor):平衡因子是指某个节点的左子树高度与右子树高度之差。它表示了该节点子树的平衡情况。平衡因子的计算公式为:

平衡因子=左子树高度-右子树高度

如果平衡因子为正数,则表示左子树高度大于右子树高度;如果为负数,则表示右子树高度大于左子树高度;如果为零,则表示左右子树高度相等。

     2.左子树高度(Height of Left Subtree):左子树的高度是指以某个节点为根的左子树的最大深度,即左子树中从根节点到最深叶子节点的路径长度。

     3.右子树高度(Height of Right Subtree):右子树的高度是指以某个节点为根的右子树的最大深度,即右子树中从根节点到最深叶子节点的路径长度。

假设我们有一个 AVL 树的节点,如下所示:

现在让我们给这个节点的左子树和右子树分别设置一些节点,如下所示:

现在我们来计算平衡因子、左子树高度和右子树高度:

  1. 平衡因子:平衡因子等于左子树的高度减去右子树的高度。 平衡因子 = 左子树高度 - 右子树高度 在这个例子中,平衡因子等于 2 - 2 = 0。

  2. 左子树高度:左子树的高度是指从节点 节点 开始,一直向左走到最底层的高度。 在这个例子中,左子树高度为2,因为从节点 节点 到节点 L2 的路径经过了2个节点。

  3. 右子树高度:右子树的高度是指从节点 节点 开始,一直向右走到最底层的高度。 在这个例子中,右子树高度为2,因为从节点 节点 到节点 R2 的路径经过了2个节点。

如果一棵二叉搜索树的高度是平衡的,它就是AVL树。如果它有n个结点,其高度可保持O(logN),搜索时间复杂度也是O(logN)。

注意: 这里所说的二叉搜索树的高度是平衡的是指,树中每个结点左右子树高度之差的绝对值不超过1,因为只有满二叉树才能做到每个结点左右子树高度之差均为0。

AVL树结点的定义 

    在这个示例中,我们定义了一个AVL树节点的模板结构体 AVLTreeNode。结构体包含了三个指针 _left_right_parent,分别指向左子节点、右子节点和父节点,还有存储键值对的 _kv 成员变量,以及用于平衡因子的 _bf 成员变量。构造函数初始化了这些成员变量,同时将平衡因子初始化为0,因为新构造的节点的左右子树均为空树。

// 定义AVL树节点模板结构体
template<class K, class V>
struct AVLTreeNode {
    AVLTreeNode<K, V>* _left;    // 左子节点指针
    AVLTreeNode<K, V>* _right;   // 右子节点指针
    AVLTreeNode<K, V>* _parent;  // 父节点指针
    std::pair<K, V> _kv;         // 存储的键值对
    int _bf;                      // 平衡因子

    // 构造函数
    AVLTreeNode(const std::pair<K, V>& kv)
        : _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _kv(kv)
        , _bf(0) // 平衡因子初始设置为0
    {}
};

AVL树的插入 

 AVL树插入结点时有以下三个步骤:

1.按照二叉搜索树的插入方法,找到待插入位置。
2.找到待插入位置后,将待插入结点插入到树中。
3.更新平衡因子,如果出现不平衡,则需要进行旋转。
因为AVL树本身就是一棵二叉搜索树,因此寻找结点的插入位置是非常简单的,按照二叉搜索树的插入规则:

1.待插入结点的key值比当前结点小就插入到该结点的左子树。
2.待插入结点的key值比当前结点大就插入到该结点的右子树。
3.待插入结点的key值与当前结点的key值相等就插入失败。

如此进行下去,直到找到与待插入结点的key值相同的结点判定为插入失败,或者最终走到空树位置进行结点插入。

由于一个结点的平衡因子是否需要更新,是取决于该结点的左右子树的高度是否发生了变化,因此插入一个结点后,该结点的祖先结点的平衡因子可能需要更新。

所以我们插入结点后需要倒着往上更新平衡因子,更新规则如下:

1.新增结点在parent的右边,parent的平衡因子+ + 。
2.新增结点在parent的左边,parent的平衡因子− − 。
3.每更新完一个结点的平衡因子后,都需要进行以下判断:

1.如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子。
2.如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了。
3.如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。
判断理由说明:

将节点插入到 AVL 树中时,最初遵循与插入二叉搜索树相同的过程。如果树为空,则将节点作为树的根插入。如果树不为空,则我们沿着根目录向下走,然后递归地沿着树向下搜索插入新节点的位置。此遍历由比较函数引导。在这种情况下,节点始终替换树中外部节点的 NULL 引用(左或右),即该节点要么成为外部节点的左子节点,要么成为外部节点的右子节点。

在此插入之后,如果树变得不平衡,则只有新插入节点的祖先不平衡。这是因为只有这些节点的子树发生了变化。因此,有必要检查每个节点的祖先是否与AVL树的不变量一致:这称为“回溯”。这是通过考虑每个节点的平衡因子来实现的。

由于单次插入时 AVL 子树的高度不能增加超过 1,因此插入后节点的临时平衡因子将在 [–2,+2] 范围内。对于检查的每个节点,如果临时平衡因子保持在 –1 到 +1 的范围内,则只需更新平衡因子,无需旋转。但是,如果临时平衡因子为 ±2,则根于此节点的子树是 AVL 不平衡的,需要轮换。 

在图中,通过插入新节点 Z 作为节点 X 的子节点,该子树 Z 的高度从 0 增加到 1。

代码如下:

#include <cassert> // for assert

template<class K, class V>
bool AVLTree<K, V>::Insert(const std::pair<K, V>& kv) {
    // 如果树为空,则将新节点作为根节点插入
    if (_root == nullptr) {
        _root = new Node(kv); // 创建新节点作为根节点
        return true; // 插入成功
    }

    // 在树中按照二叉搜索树的规则找到待插入位置
    Node* cur = _root; // 从根节点开始查找
    Node* parent = nullptr; // 记录当前节点的父节点
    while (cur) {
        if (kv.first < cur->_kv.first) { // 如果新节点的键值小于当前节点的键值
            parent = cur; // 记录当前节点为父节点
            cur = cur->_left; // 继续在左子树中查找
        } else if (kv.first > cur->_kv.first) { // 如果新节点的键值大于当前节点的键值
            parent = cur; // 记录当前节点为父节点
            cur = cur->_right; // 继续在右子树中查找
        } else {
            // 不允许重复插入相同的键值对
            return false; // 插入失败
        }
    }

    // 创建新节点,并插入到树中
    cur = new Node(kv); // 创建新节点
    if (kv.first < parent->_kv.first) { // 如果新节点的键值小于父节点的键值
        parent->_left = cur; // 插入到父节点的左子树
        cur->_parent = parent; // 设置新节点的父节点
    } else { // 如果新节点的键值大于父节点的键值
        parent->_right = cur; // 插入到父节点的右子树
        cur->_parent = parent; // 设置新节点的父节点
    }

    // 更新从插入节点到根节点的平衡因子,并进行平衡调整
    while (cur != _root) {
        if (cur == parent->_left) { // 如果新节点在父节点的左子树中
            parent->_bf--; // 父节点的平衡因子减1
        } else if (cur == parent->_right) { // 如果新节点在父节点的右子树中
            parent->_bf++; // 父节点的平衡因子加1
        }

        if (parent->_bf == 0) { // 如果父节点的平衡因子为0
            break; // 更新结束
        } else if (parent->_bf == -1 || parent->_bf == 1) { // 如果父节点的平衡因子为-1或1
            cur = parent; // 更新当前节点为父节点
            parent = parent->_parent; // 更新父节点为爷爷节点
        } else if (parent->_bf == -2 || parent->_bf == 2) { // 如果父节点的平衡因子为-2或2
            if (parent->_bf == -2) { // 如果父节点的平衡因子为-2
                if (cur->_bf == -1) { // 如果新节点在父节点的左子树的左子树中
                    RotateR(parent); // 进行右单旋
                } else { // 如果新节点在父节点的左子树的右子树中
                    RotateLR(parent); // 进行左右双旋
                }
            } else { // 如果父节点的平衡因子为2
                if (cur->_bf == -1) { // 如果新节点在父节点的右子树的左子树中
                    RotateRL(parent); // 进行右左双旋
                } else { // 如果新节点在父节点的右子树的右子树中
                    RotateL(parent); // 进行左单旋
                }
            }
            break; // 平衡调整完成
        } else {
            // 树的平衡因子不符合规范
            assert(false); // 报错
        }
    }

    return true; // 插入成功
}

AVL树的旋转 

显示将多个元素插入到 AVL 树中的动画。它包括左、右、左-右和右-左旋转。

左单旋

当AVL树中的节点的左子树高度比右子树高度多两层以上时,就需要进行左单旋操作,以恢复AVL树的平衡。左单旋操作是AVL树中的一种旋转操作,用于减小左子树的高度,增加右子树的高度,以达到平衡。

下面是左单旋的具体步骤:

  1. 找到需要进行左单旋的节点:首先,需要找到AVL树中需要进行左单旋的节点。这个节点的左子树高度必须比右子树高度多两层以上。

  2. 执行左单旋

    • 让该节点的左孩子成为新的根节点。
    • 将新的根节点的右孩子变为原根节点的左孩子。
    • 原根节点成为新根节点的右孩子。
  3. 更新高度:左单旋操作后,需要更新所有受影响节点的高度信息,以确保树的平衡性。

下面通过示例图来展示左单旋的过程: 

现在,我们来计算每个节点的平衡因子:

  • A的平衡因子为2(右子树的高度为0,左子树的高度为2)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • C的平衡因子为0(左右子树的高度均为0)。

现在我们明确了为什么需要左单旋操作:节点A的平衡因子为2,而左子树的平衡因子为2。这导致了不平衡,因此我们需要通过左单旋来解决这个问题。

现在我们来执行左单旋操作:

在执行左单旋后,树的结构变为这样。现在,让我们重新计算每个节点的平衡因子:

  • B的平衡因子为0(左右子树的高度均为1)。
  • A的平衡因子为0(左右子树的高度均为0)。
  • C的平衡因子为0(左右子树的高度均为0)。

现在,整棵树重新达到平衡,每个节点的平衡因子都在合理范围内。左单旋操作通过将节点A的左孩子B提升为根节点,调整了树的结构,使得树重新平衡。

代码如下:

//左单旋
void RotateL(Node* parent)
{
    // 1、声明临时指针变量
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* parentParent = parent->_parent;

    // 2、建立新的父子关系
	parent->_parent = subR;
	subR->_left = parent;

    // 3、建立新的父子关系
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

    // 4、建立新的祖父子关系
	if (parentParent == nullptr)
	{
		_root = subR;
		subR->_parent = nullptr; //subR的_parent指向需改变
	}
	else
	{
		if (parent == parentParent->_left)
		{
			parentParent->_left = subR;
		}
		else //parent == parentParent->_right
		{
			parentParent->_right = subR;
		}
		subR->_parent = parentParent;
	}

    // 5、更新平衡因子
	subR->_bf = parent->_bf = 0;
}

这个函数实现了左单旋操作,将以指定节点 parent 为根的子树进行左单旋,以调整AVL树的结构并保持平衡。下面是对每一行代码的解释:

  1. 声明临时指针变量:subR 指向父节点的右孩子,subRL 指向 subR 的左孩子,parentParent 指向父节点的父节点。
  2. 建立新的父子关系:将 parent 的父节点指针指向 subR,将 subR 的左孩子指针指向 parent,完成了新的根节点和其左子节点的关系建立。
  3. 建立新的父子关系:将 parent 的右孩子指针指向 subRL,如果 subRL 不为空,则将 subRL 的父节点指针指向 parent,这样完成了新的父节点与其右子节点的关系建立。
  4. 建立新的祖父子关系:如果 parent 的父节点为空,说明 parent 是根节点,更新树的根节点指针 _rootsubR,否则根据 parent 在其父节点中的位置,更新其父节点的左孩子或右孩子为 subR,同时更新 subR 的父节点为 parentParent
  5. 更新平衡因子:将被旋转的节点(原先的父节点 parent)以及新的根节点 subR 的平衡因子都设置为0,因为它们的左右子树高度相等,树重新达到平衡状态。

右单旋

右单旋是AVL树中的一种旋转操作,用于解决树中某个节点的右子树高度比左子树高度多两层以上的情况。右单旋通过将当前节点的右孩子向上提升为新的根节点,以减小右子树的高度,增加左子树的高度,从而保持树的平衡。

下面是右单旋的具体步骤:

  1. 找到需要进行右单旋的节点:首先,需要找到AVL树中需要进行右单旋的节点。这个节点的右子树高度必须比左子树高度多两层以上。

  2. 执行右单旋

    • 让该节点的右孩子成为新的根节点。
    • 将新的根节点的左孩子变为原根节点的右孩子。
    • 原根节点成为新根节点的左孩子。
  3. 更新高度:右单旋操作后,需要更新所有受影响节点的高度信息,以确保树的平衡性。

现在,让我们通过一个示例来演示右单旋的过程:

示例:

假设我们有以下的AVL树结构,其中节点A需要进行右单旋:

初始状态:

 

现在我们来计算每个节点的平衡因子:

  • A的平衡因子为-2(右子树的高度为1,左子树的高度为0)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • C的平衡因子为0(左右子树的高度均为0)。

节点A的平衡因子为-2,表示右子树的高度比左子树的高度高2。这导致了不平衡,因此我们需要通过右单旋来解决这个问题。

现在我们来执行右单旋操作:

在执行右单旋后,树的结构变为这样。现在,让我们重新计算每个节点的平衡因子:

  • B的平衡因子为0(左右子树的高度均为1)。
  • A的平衡因子为0(左右子树的高度均为0)。
  • C的平衡因子为0(左右子树的高度均为0)。

现在,整棵树重新达到平衡,每个节点的平衡因子都在合理范围内。右单旋操作通过将节点A的右孩子B提升为根节点,调整了树的结构,使得树重新平衡。

代码如下:

//右单旋
void RotateR(Node* parent)
{
    // 1、声明临时指针变量
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* parentParent = parent->_parent;

    // 2、建立新的父子关系
	subL->_right = parent;
	parent->_parent = subL;

    // 3、建立新的父子关系
	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;

    // 4、建立新的祖父子关系
	if (parentParent == nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == parentParent->_left)
		{
			parentParent->_left = subL;
		}
		else //parent == parentParent->_right
		{
			parentParent->_right = subL;
		}
		subL->_parent = parentParent;
	}

	// 5、更新平衡因子
	subL->_bf = parent->_bf = 0;
}

解释:

  1. 声明临时指针变量:subL 指向父节点的左孩子,subLR 指向 subL 的右孩子,parentParent 指向父节点的父节点。
  2. 建立新的父子关系:将 subL 的右孩子指针指向 parent,将 parent 的父节点指针指向 subL,完成了新的根节点和其右子节点的关系建立。
  3. 建立新的父子关系:将 parent 的左孩子指针指向 subLR,如果 subLR 不为空,则将 subLR 的父节点指针指向 parent,这样完成了新的父节点与其左子节点的关系建立。
  4. 建立新的祖父子关系:如果 parent 的父节点为空,说明 parent 是根节点,更新树的根节点指针 _rootsubL,否则根据 parent 在其父节点中的位置,更新其父节点的左孩子或右孩子为 subL,同时更新 subL 的父节点为 parentParent
  5. 更新平衡因子:将被旋转的节点(原先的父节点 parent)以及新的根节点 subL 的平衡因子都设置为0,因为它们的左右子树高度相等,树重新达到平衡状态。

左右双旋

左右双旋是AVL树中的一种旋转操作,用于解决某个节点的左子树的右子树高度大于左子树高度,右子树的左子树高度大于右子树高度的情况。这种情况需要通过先进行右单旋再进行左单旋的方式来进行调整,以保持AVL树的平衡。

下面是左右双旋操作的一般步骤:

  1. 找到需要进行左右双旋的节点,假设为节点A。
  2. 首先对A的左子节点进行左单旋操作,然后再对A进行右单旋操作。

下面是一个示例,展示了左右双旋的过程:

假设我们有以下的AVL树结构,其中节点A需要进行左右双旋:

初始状态:

  • A的平衡因子为2(右子树的高度为0,左子树的高度为2)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • C的平衡因子为-2(左子树的高度为1,右子树的高度为3)。
  • D的平衡因子为0(左右子树的高度均为1)。

左单旋操作后:

  • A的平衡因子为1(右子树的高度为1,左子树的高度为0)。
  • C的平衡因子为0(左右子树的高度均为1)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • D的平衡因子为0(左右子树的高度均为1)。

右单旋操作后:

  • C的平衡因子为0(左右子树的高度均为1)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • A的平衡因子为0(左右子树的高度均为1)。
  • D的平衡因子为0(左右子树的高度均为1)。

通过这个分析,我们可以看到,在左右双旋的过程中,每个节点的平衡因子都被正确地调整为了0,使得整棵树重新达到平衡状态。

代码如下:

//左右双旋
void RotateLR(Node* parent)
{
    // 1、声明临时指针变量
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf; //记录subLR的平衡因子,必定不为nullptr,因为subL的平衡因子为1

    // 2、以subL为旋转点进行左单旋
	RotateL(subL);

    // 3、以parent为旋转点进行右单旋
	RotateR(parent);

    // 4、更新平衡因子
	if (bf == 1) // subLR原先的平衡因子为1
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1) // subLR原先的平衡因子为-1
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 0) // subLR原先的平衡因子为0
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false); // 在旋转前树的平衡因子就有问题
	}
}
  1. 声明临时指针变量:subL 指向父节点的左孩子,subLR 指向 subL 的右孩子,bf 存储 subLR 的平衡因子。由于左单旋操作之前,subL 的平衡因子为1,因此 subLR 的平衡因子必定存在且不为nullptr。
  2. subL 为旋转点进行左单旋操作。
  3. parent 为旋转点进行右单旋操作。
  4. 更新平衡因子:根据 subLR 的原先平衡因子的情况,更新 subLRsubLparent 的平衡因子。

右左双旋

右左单旋是AVL树中的一种旋转操作,用于解决某个节点的右子树的左子树高度大于右子树高度,左子树的右子树高度大于左子树高度的情况。这种情况需要通过先进行左单旋再进行右单旋的方式来进行调整,以保持AVL树的平衡。

下面是右左单旋操作的一般步骤:

  1. 找到需要进行右左单旋的节点,假设为节点A。
  2. 首先对A的右子节点进行右单旋操作,然后再对A进行左单旋操作。

下面是一个示例,展示了右左单旋的过程:

假设我们有以下的AVL树结构,其中节点A需要进行右左单旋:

初始状态:

  • A的平衡因子为-2(右子树的高度为1,左子树的高度为3)。
  • B的平衡因子为0(左右子树的高度均为2)。
  • C的平衡因子为-2(右子树的高度为1,左子树的高度为3)。
  • D的平衡因子为0(左右子树的高度均为1)。

右单旋操作后:

  • A的平衡因子为-1(右子树的高度为1,左子树的高度为2)。
  • C的平衡因子为0(左右子树的高度均为2)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • D的平衡因子为0(左右子树的高度均为1)。

左单旋操作后:

  • C的平衡因子为0(左右子树的高度均为2)。
  • A的平衡因子为0(左右子树的高度均为1)。
  • B的平衡因子为0(左右子树的高度均为1)。
  • D的平衡因子为0(左右子树的高度均为1)。

通过这个分析,我们可以看到,在右左单旋的过程中,每个节点的平衡因子都被正确地调整为了0,使得整棵树重新达到平衡状态。

代码如下:

//右左双旋
void RotateRL(Node* parent)
{
    // 1、声明临时指针变量
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

    // 2、以subR为轴进行右单旋
	RotateR(subR);

    // 3、以parent为轴进行左单旋
	RotateL(parent);

    // 4、更新平衡因子
	if (bf == 1) // subRL原先的平衡因子为1
	{
		subRL->_bf = 0;
		parent->_bf = -1;
		subR->_bf = 0;
	}
	else if (bf == -1) // subRL原先的平衡因子为-1
	{
		subRL->_bf = 0;
		parent->_bf = 0;
		subR->_bf = 1;
	}
	else if (bf == 0) // subRL原先的平衡因子为0
	{
		subRL->_bf = 0;
		parent->_bf = 0;
		subR->_bf = 0;
	}
	else
	{
		assert(false); // 在旋转前树的平衡因子就有问题
	}
}
  1. 声明临时指针变量:subR 指向父节点的右孩子,subRL 指向 subR 的左孩子,bf 存储 subRL 的平衡因子。由于右单旋操作之前,subR 的平衡因子为-1,因此 subRL 的平衡因子必定存在且不为nullptr。
  2. subR 为轴进行右单旋操作。
  3. parent 为轴进行左单旋操作。
  4. 更新平衡因子:根据 subRL 的原先平衡因子的情况,更新 subRLparentsubR 的平衡因子。

 AVL树的验证

AVL树的验证主要包括两个方面:结构的正确性和平衡性。

  1. 结构的正确性

    • 确保树中不存在重复的节点。
    • 确保每个节点的左子树和右子树都是二叉搜索树,即左子树中的所有节点值小于当前节点的值,右子树中的所有节点值大于当前节点的值。
  2. 平衡性

    • 对于每个节点,其左子树的高度与右子树的高度之差的绝对值不超过1。
    • 遍历整棵树,验证每个节点的平衡因子是否满足AVL树的定义。
#include <iostream>
#include <algorithm>

// AVL树节点结构
struct Node {
    int key;        // 节点的关键值
    int height;     // 节点的高度
    Node* left;     // 左子节点指针
    Node* right;    // 右子节点指针
};

// 计算节点的高度
int height(Node* node) {
    if (node == nullptr) return 0;
    return node->height;
}

// 计算节点的平衡因子
int balanceFactor(Node* node) {
    if (node == nullptr) return 0;
    return height(node->left) - height(node->right);
}

// 检查AVL树的平衡性
bool isBalanced(Node* root) {
    if (root == nullptr) return true;
    
    int bf = balanceFactor(root);
    if (std::abs(bf) > 1) return false;

    return isBalanced(root->left) && isBalanced(root->right);
}

// 验证AVL树
bool validateAVL(Node* root) {
    // 检查结构的正确性
    // 这一步是根据具体实现来进行的,通常需要确保树中不存在重复的节点,并且每个节点的左子树中所有节点的值小于当前节点的值,右子树中所有节点的值大于当前节点的值。

    // 检查平衡性
    return isBalanced(root);
}

int main() {
    // 构建AVL树,这里假设已经构建好了AVL树的结构
    
    // 验证AVL树
    if (validateAVL(root)) {
        std::cout << "这是一个有效的AVL树。" << std::endl;
    } else {
        std::cout << "这不是一个有效的AVL树。" << std::endl;
    }

    return 0;
}
  1. #include <iostream>#include <algorithm>:这两行代码是预处理指令,用于包含标准输入输出流和标准库中的算法函数。
  2. struct Node:定义了 AVL 树节点的结构,包括节点的关键值 key、高度 height,以及指向左右子节点的指针 leftright
  3. height(Node* node):计算节点的高度的函数。如果节点为空,则返回高度为0;否则返回节点的高度。
  4. balanceFactor(Node* node):计算节点的平衡因子的函数。如果节点为空,则返回0;否则返回节点的左子树高度减去右子树高度。
  5. isBalanced(Node* root):检查 AVL 树的平衡性的函数。如果根节点为空,则树是平衡的;否则计算根节点的平衡因子,如果绝对值大于1,则树不平衡;否则递归地检查左右子树的平衡性。
  6. validateAVL(Node* root):验证 AVL 树的函数。在这个函数中,首先会检查树的结构的正确性(此处未提供具体实现),然后调用 isBalanced 函数来检查树的平衡性。
  7. main() 函数:在 main() 函数中,构建了 AVL 树的结构(这里未提供具体实现),然后调用 validateAVL 函数来验证 AVL 树的有效性。如果返回值为 true,则打印 "这是一个有效的AVL树。";否则打印 "这不是一个有效的AVL树。"。

AVL树的查找 

AVL树的查找函数与二叉搜索树的查找方式一模一样,逻辑如下:

若树为空树,则查找失败,返回nullptr。
若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
若key值等于当前结点的值,则查找成功,返回对应结点。
代码如下:

// 查找函数
Node* Find(const K& key) {
    Node* cur = _root; // 从根节点开始查找
    while (cur) { // 循环直到当前节点为空(即查找到叶子节点)
        if (key < cur->_kv.first) { // 如果目标键值小于当前节点的键值
            cur = cur->_left; // 则在当前节点的左子树中继续查找
        } else if (key > cur->_kv.first) { // 如果目标键值大于当前节点的键值
            cur = cur->_right; // 则在当前节点的右子树中继续查找
        } else { // 如果目标键值等于当前节点的键值,表示找到了目标节点
            return cur; // 返回指向目标节点的指针
        }
    }
    return nullptr; // 如果循环结束仍未找到目标节点,则返回空指针表示查找失败
}

AVL树的修改

实现修改AVL树当中指定key值结点的value,我们可以实现一个Modify函数,该函数当中的逻辑如下:

  1. 调用查找函数获取指定key值的结点。
  2. 对该结点的value进行修改。
//修改函数
bool Modify(const K& key, const V& value)
{
	Node* ret = Find(key);
	if (ret == nullptr) //未找到指定key值的结点
	{
		return false;
	}
	ret->_kv.second = value; //修改结点的value
	return true;
}

 AVL树的删除

AVL树的删除操作是指从树中删除一个特定的节点。删除操作可能会破坏AVL树的平衡性,因此在删除节点后,需要进行平衡操作以确保AVL树的平衡性。

删除概念:

AVL树的删除操作分为以下几种情况:

  1. 如果要删除的节点是叶子节点(即没有子节点),则直接删除该节点。
  2. 如果要删除的节点只有一个子节点,则将其子节点替换为当前节点。
  3. 如果要删除的节点有两个子节点,则找到该节点的中序后继节点(即右子树中的最小节点),将其值复制到当前节点,然后删除中序后继节点。

在进行这些操作后,需要重新计算每个节点的高度和平衡因子,并执行旋转操作来恢复AVL树的平衡性。

删除规则:

删除节点后,为了保持AVL树的平衡性,需要进行以下步骤:

  1. 删除目标节点。
  2. 从被删除节点的父节点开始向上回溯,重新计算每个祖先节点的高度和平衡因子。
  3. 如果发现某个祖先节点的平衡因子超过了1或-1,则进行相应的旋转操作来恢复平衡。

删除步骤:

  1. 找到要删除的目标节点。
  2. 根据目标节点的情况,分情况处理:
    • 如果目标节点是叶子节点,直接删除。
    • 如果目标节点只有一个子节点,用其子节点替换当前节点。
    • 如果目标节点有两个子节点,找到其中序后继节点,将其值复制到当前节点,然后删除中序后继节点。
  3. 删除节点后,从其父节点开始向上回溯,重新计算每个祖先节点的高度和平衡因子。
  4. 如果需要,进行旋转操作以恢复AVL树的平衡性。

 代码如下:

// 删除节点
void Delete(const K& key) {
    _root = DeleteNode(_root, key); // 递归地删除节点并更新根节点
}

Node* DeleteNode(Node* root, const K& key) {
    if (root == nullptr) {
        return root; // 如果当前节点为空,直接返回
    }

    if (key < root->_kv.first) {
        root->_left = DeleteNode(root->_left, key); // 如果删除值小于当前节点值,递归地删除左子树节点
    } else if (key > root->_kv.first) {
        root->_right = DeleteNode(root->_right, key); // 如果删除值大于当前节点值,递归地删除右子树节点
    } else {
        if (root->_left == nullptr || root->_right == nullptr) {
            Node* temp = (root->_left != nullptr) ? root->_left : root->_right;
            if (temp == nullptr) {
                temp = root;
                root = nullptr;
            } else {
                *root = *temp;
            }
            delete temp;
        } else {
            Node* temp = MinValueNode(root->_right); // 找到右子树的最小值节点
            root->_kv = temp->_kv; // 将右子树的最小值节点的值复制给当前节点
            root->_right = DeleteNode(root->_right, temp->_kv.first); // 递归地删除右子树的最小值节点
        }
    }

    if (root == nullptr) {
        return root; // 如果当前节点为空,直接返回
    }

    // 更新当前节点的高度
    root->height = 1 + std::max(height(root->_left), height(root->_right));

    // 检查当前节点的平衡因子是否超过了1或-1,如果超过则进行旋转操作
    int balance = balanceFactor(root);
    if (balance > 1 && balanceFactor(root->_left) >= 0) {
        return RotateRight(root);
    }
    if (balance > 1 && balanceFactor(root->_left) < 0) {
        root->_left = RotateLeft(root->_left);
        return RotateRight(root);
    }
    if (balance < -1 && balanceFactor(root->_right) <= 0) {
        return RotateLeft(root);
    }
    if (balance < -1 && balanceFactor(root->_right) > 0) {
        root->_right = RotateRight(root->_right);
        return RotateLeft(root);
    }

    return root;
}
  • 33
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值