AVL树的实现与解析(四种旋转代码实现)

目录

一、AVL树的基本概念

二、AVL树的节点结构

(一)节点结构解析

三、AVL树的查找操作

(一)思路解析

四、AVL树的插入操作

(一)插入操作解析

五、AVL树的旋转操作

(一)左单旋转(RotateL)

(二)右单旋转(RotateR)

(三)右左双旋转(RotateRL)

(四)左右双旋转(RotateLR)

(五)旋转操作解析

六、AVL树的遍历操作

(一)中序遍历解析

七、AVL树的平衡性检查

(二)平衡性检查解析

八、总结


一、AVL树的基本概念

AVL树是一种特殊的二叉查找树,其每个节点的左右子树高度差(平衡因子)的绝对值不超过1。平衡因子的定义为:

BF(x)=Height(x.right)−Height(x.left)

其中,Height(x) 表示以 x 为根的子树的高度。AVL树通过旋转操作来调整树的平衡,确保在插入或删除节点后,树仍然保持平衡。

这种特性使得AVL树在插入和查找操作中都能保持较高的效率。本文将详细介绍AVL树的实现,包括插入操作、旋转调整以及平衡因子的维护。

二、AVL树的节点结构

在实现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)
    { }
};

(一)节点结构解析

  • _left_right 分别指向节点的左子节点和右子节点。

  • _parent 指向当前节点的父节点,用于在调整树结构时快速定位。

  • _kv 是一个键值对,存储节点的键和值。

  • _bf 是平衡因子,用于记录当前节点的左右子树高度差。

三、AVL树的查找操作

(一)思路解析

用cur代替根节点循环查找,如果key值比节点的值大,那就在右子树中查找;如果key值比节点的值小,那就在左子树中查找;循环往复,如果能找到返回当前指针,否则返回空。

Node* Find(const K& key)
{
	Node* cur=_root;
	while(cur)
	{
		if(key > cur->_kv.first)
		{
			cur=cur->_right;
		}
		else if(key < cur->_kv.first)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

四、AVL树的插入操作

插入操作是AVL树的核心功能之一。插入一个新节点后,需要调整树的平衡。以下是插入操作的代码实现:

template<class K, class V>
class 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 (kv.first > parent->_kv.first)
        {
            parent->_right = cur; // 插入到父节点的右子树
            cur->_parent = parent;
        }
        else
        {
            parent->_left = cur; // 插入到父节点的左子树
            cur->_parent = parent;
        }

        // 更新平衡因子并调整树的平衡
        while (parent)
        {
            if (cur == parent->_left)
            {
                --parent->_bf; // 左子树高度增加
            }
            else
            {
                ++parent->_bf; // 右子树高度增加
            }

            if (parent->_bf == 0)
            {
                break; // 平衡因子为0,树仍然平衡
            }
            else if (parent->_bf == 1 || parent->_bf == -1)
            {
                cur = parent; // 继续向上更新平衡因子
                parent = parent->_parent;
            }
            else if (parent->_bf == 2 || parent->_bf == -2)
            {
                // 平衡因子为2或-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);
                }
                break; // 旋转调整后,树恢复平衡
            }
            else
            {
                assert(false); // 平衡因子异常
            }
        }
        return true;
    }

private:
    Node* _root = nullptr; // AVL树的根节点
};

(一)插入操作解析

  1. 插入新节点

    • 如果树为空,直接将新节点设置为根节点。

    • 如果树不为空,从根节点开始,根据键值大小找到插入位置。

    • 插入新节点后,更新其父节点的左右子节点指针。

  2. 更新平衡因子

    • 从插入节点的父节点开始,向上更新平衡因子。

    • 如果父节点的平衡因子变为0,说明树仍然平衡,无需进一步调整。
      在插入节点前,父亲bf==-1或者bf==1,一边高,一边低,新插入的节点就在低的那边.

    • 如果父节点的平衡因子变为1或-1,说明树的高度发生变化,需要继续向上更新。
      在插入节点前,父亲bf==0,两边一样高,插入导致高度变化

    • 如果父节点的平衡因子变为2或-2,说明树失去平衡,需要进行旋转调整。
      在插入节点前,父亲bf==-1或者bf==1,一边高,一边低,新插入的节点在高的那边

  3. 旋转调整

    • 根据父节点和子节点的平衡因子,选择合适的旋转操作:

      • 左单旋转:父节点的平衡因子为2,子节点的平衡因子为1。

      • 右单旋转:父节点的平衡因子为-2,子节点的平衡因子为-1。

      • 右左双旋转:父节点的平衡因子为2,子节点的平衡因子为-1。

      • 左右双旋转:父节点的平衡因子为-2,子节点的平衡因子为1。

五、AVL树的旋转操作

旋转操作是AVL树调整平衡的关键步骤。以下是四种旋转操作的代码实现:

(一)左单旋转(RotateL)

思路解析:让父亲节点去做子节点的左,子节点的左变成父亲节点的右,再让子节点变成父亲节点,最后更新平衡因子。

void RotateL(Node* parent)
{
    Node* subR = parent->_right; // 右子节点
    Node* subRL = subR->_left;   // 右子节点的左子节点

    // 更新旋转完成后节点的孩子指向
    parent->_right = subRL;
    subR->_left = parent;

    // 更新旋转完成后节点的父亲指向
    Node* grandparent = parent->_parent;
    parent->_parent = subR;
    if (subRL)
    {
        subRL->_parent = parent;
    }

    // 更新根节点或子树的父节点指向
    if (_root == parent)
    {
        _root = subR; // 如果在根节点左单旋转,更新根节点
        subR->_parent = nullptr;
    }
    else
    {
        if (grandparent->_left == parent)
        {
            grandparent->_left = subR; // 父节点在左子树中
        }
        else
        {
            grandparent->_right = subR; // 父节点在右子树中
        }
        subR->_parent = grandparent;
    }

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

(二)右单旋转(RotateR)

思路解析:让父亲节点去做子节点的右,子节点的右变成父亲节点的左,再让子节点变成父亲节点,最后更新平衡因子。

void RotateR(Node* parent)
{
    Node* subL = parent->_left;  // 左子节点
    Node* subLR = subL->_right;  // 左子节点的右子节点

    // 更新旋转完成后节点的孩子指向
    parent->_left = subLR;
    subL->_right = parent;

    // 更新旋转完成后节点的父亲指向
    Node* grandparent = parent->_parent;
    parent->_parent = subL;
    if (subLR)
    {
        subLR->_parent = parent;
    }

    // 更新根节点或子树的父节点指向
    if (_root == parent)
    {
        _root = subL; // 如果在根节点右单旋转,更新根节点
        subL->_parent = nullptr;
    }
    else
    {
        if (grandparent->_left == parent)
        {
            grandparent->_left = subL; // 父节点在左子树中
        }
        else
        {
            grandparent->_right = subL; // 父节点在右子树中
        }
        subL->_parent = grandparent;
    }

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

(三)右左双旋转(RotateRL)

思路解析:先对右子节点进行右单旋转,再对父节点进行左单旋转,最后根据旋转后的结果更新平衡因子(有三种情况需要进行讨论)

void RotateRL(Node* parent)
{
    Node* subR = parent->_right;  // 右子节点
    Node* subRL = subR->_left;    // 右子节点的左子节点
    int bf = subRL->_bf;          // 保存右左子节点的平衡因子

    // 先对右子节点进行右单旋转
    RotateR(parent->_right);
    // 再对父节点进行左单旋转
    RotateL(parent);

    // 更新平衡因子
    if (bf == 1) // 新增在右子树
    {
        parent->_bf = -1;
        subR->_bf = 0;
        subRL->_bf = 0;
    }
    else if (bf == 0) // 新增在右左子节点
    {
        parent->_bf = subR->_bf = subRL->_bf = 0;
    }
    else if (bf == -1) // 新增在左子树
    {
        parent->_bf = 0;
        subR->_bf = 1;
        subRL->_bf = 0;
    }
    else
    {
        assert(false); // 平衡因子异常
    }
}

(四)左右双旋转(RotateLR)

思路解析:先对左子节点进行左单旋转,再对父节点进行右单旋转,最后根据旋转后的结果更新平衡因子(有三种情况需要进行讨论)

void RotateLR(Node* parent)
{
    Node* subL = parent->_left;   // 左子节点
    Node* subLR = subL->_right;   // 左子节点的右子节点
    int bf = subLR->_bf;          // 保存左右子节点的平衡因子

    // 先对左子节点进行左单旋转
    RotateL(parent->_left);
    // 再对父节点进行右单旋转
    RotateR(parent);

    // 更新平衡因子
    if (bf == 1) // 新增在右子树
    {
        parent->_bf = 0;
        subL->_bf = -1;
        subLR->_bf = 0;
    }
    else if (bf == 0) // 新增在左右子节点
    {
        parent->_bf = subL->_bf = subLR->_bf = 0;
    }
    else if (bf == -1) // 新增在左子树
    {
        parent->_bf = 1;
        subL->_bf = 0;
        subLR->_bf = 0;
    }
    else
    {
        assert(false); // 平衡因子异常
    }
}

(五)旋转操作解析

  • 左单旋转(RotateL)

    • 适用于父节点的右子树过高,且右子节点的平衡因子为1的情况。

    • 将父节点的右子节点提升为新的父节点,调整指针关系,更新平衡因子都为0。

  • 右单旋转(RotateR)

    • 适用于父节点的左子树过高,且左子节点的平衡因子为-1的情况。

    • 将父节点的左子节点提升为新的父节点,调整指针关系,更新平衡因子都为0。

  • 右左双旋转(RotateRL)

    • 适用于父节点的右子树过高,且右子节点的平衡因子为-1的情况。

    • 先对右子节点进行右单旋转,再对父节点进行左单旋转,调整指针关系,更新平衡因子。

    • 平衡因子分为三种情况:

      • 1、新增在右子树

      • 2、新增在左子树

      • 3、新增在右左子节点

  • 左右双旋转(RotateLR)

    • 适用于父节点的左子树过高,且左子节点的平衡因子为1的情况。

    • 先对左子节点进行左单旋转,再对父节点进行右单旋转,调整指针关系,更新平衡因子。

    • 平衡因子分为三种情况:

      • 1、新增在右子树

      • 2、新增在左子树

      • 3、新增在左右子节点 

六、AVL树的遍历操作

为了验证AVL树的结构和平衡性,我们可以通过中序遍历输出树的节点。以下是中序遍历的代码实现:

void InOrder()
{
    _InOrder(_root);
}

void _InOrder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }
    _InOrder(root->_left);         // 遍历左子树
    cout << root->_kv.first << " "; // 输出当前节点的键
    _InOrder(root->_right);        // 遍历右子树
}

(一)中序遍历解析

中序遍历的顺序为:左子树 -> 当前节点 -> 右子树。通过中序遍历,可以输出AVL树的节点,验证树的结构是否正确。

七、AVL树的平衡性检查

为了确保AVL树在插入操作后仍然保持平衡,我们可以通过递归检查每个节点的平衡因子。以下是平衡性检查的代码实现:

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

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

    int leftHeight = _Height(root->_left);  // 左子树高度
    int rightHeight = _Height(root->_right); // 右子树高度

    if (rightHeight - leftHeight != root->_bf)
    {
        cout << root->_kv.first << "平衡因子异常" << endl;
        return false;
    }
    return abs(rightHeight - leftHeight) < 2
        && _IsBalance(root->_left)
        && _IsBalance(root->_right);
}

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

    int left = _Height(root->_left);  // 左子树高度
    int right = _Height(root->_right); // 右子树高度

    return left > right ? (1 + left) : (1 + right);
}

(二)平衡性检查解析

  • _IsBalance 函数递归检查每个节点的平衡因子是否符合AVL树的定义。

  • _Height 函数计算以某个节点为根的子树的高度。

八、总结

AVL树是一种自平衡二叉查找树,通过维护每个节点的平衡因子(左右子树高度差绝对值≤1)来保证高效操作。本文详细介绍了AVL树的实现:1)定义包含平衡因子的节点结构;2)插入操作时通过四种旋转(左单旋、右单旋、右左双旋、左右双旋)调整平衡;3)提供查找、遍历和平衡性检查方法。通过维护平衡因子和旋转操作,AVL树确保插入、查找等操作的时间复杂度保持在O(logn)。文中给出了完整的C++实现代码,包括节点结构调整、平衡因子更新和旋转操作的具体实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值