AVL树(Adelson-Velsky and Landis Tree)
文章目录
1.前言
- 本文中,我们使用
KV
实现的AVL树。- 源码链接放在文章结尾。
AVL树(Adelson-Velsky and Landis Tree)是一种自平衡的二叉搜索树(BST),它基于二叉搜索树并通过平衡而得到。
AVL树的增删查改都是基于二叉搜索树的,如果不了解二叉搜索树,点我进入二叉搜索树的世界
- 在前面的学习中我们提到,二叉搜索树可以提高搜索数据的效率,但在数据有序的情况下会退化为单支树,此时在树中查找元素就得遍历一整个分支,时间复杂度也会退化至O(N)。
- 如果有一种算法,可以使二叉搜索树时刻保持左右子树的平衡,就可以避免这种最坏情况。
- 两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了并以他们的名字命名了AVL树,解决了上述问题。
2.AVL树的基本概念
-
二叉搜索树的性质:对于任何一个节点,它的左子树的所有键值都小于该节点的键值,右子树的所有键值都大于该节点的键值。
-
平衡因子:AVL树中的每个节点都会有一个平衡因子(balance factor)。
本文定义平衡因子是该节点的右子树高度减去左子树高度的值。具体来说:
- 如果平衡因子为
1
,说明右子树比左子树高。 - 如果平衡因子为
-1
,说明左子树比右子树高。 - 如果平衡因子为
0
,说明左右子树的高度相同。
AVL树的要求是:对于每个节点,平衡因子的绝对值不超过1。如果某个节点的平衡因子绝对值大于1,就需要进行旋转操作来恢复平衡。
- 如果平衡因子为
3.节点的定义
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left; // 指向左子树的指针
AVLTreeNode<K, V>* _right; // 指向右子树的指针
AVLTreeNode<K, V>* _parent; // 指向父节点的指针,用于实现三叉链表(Three-pronged chain)
int _bf; // 平衡因子 (Balance Factor),用于判断树是否平衡
pair<K, V> _kv; // 存储键值对
// 构造函数:初始化一个节点
AVLTreeNode(const pair<K, V>& kv)
: _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv)
{
}
};
4.插入
思路:
- 插入节点:
- 首先执行标准的 二叉搜索树(BST)插入操作。通过循环,遍历树来找到合适的位置,然后插入新的节点
cur
。 - 对于每个节点,比较其键
kv.first
来决定插入到左子树还是右子树。 - 如果树中已经存在相同的键值对(即
kv.first == cur->_kv.first
),直接返回false
,表示不插入重复的元素。
- 首先执行标准的 二叉搜索树(BST)插入操作。通过循环,遍历树来找到合适的位置,然后插入新的节点
- 更新平衡因子:
- 如果插入的节点是父节点的左子节点,父节点的平衡因子减
1
,如果插入的节点是父节点的右子节点,父节点的平衡因子增1
。 - 如果更新后父节点的平衡因子的值为
0
,则说明树的高度没变,更新停止。 - 如果更新后父节点的平衡因子的值为
1
或-1
,则说明树的高度发生变化,因此继续向上回溯。 - 如果更新后父节点的平衡因子的值为
2
或-2
,则说明树失衡,旋转处理后树的高度恢复到插入前,更新停止。
- 如果插入的节点是父节点的左子节点,父节点的平衡因子减
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
// 1. 按照二叉搜索树规则进行插入
if (!_root)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (kv.first < cur->_kv.first) cur = cur->_left;
else if (kv.first > cur->_kv.first) cur = cur->_right;
else return false; // 说明已有相同的键,不能插入
}
cur = new Node(kv);
if (kv.first < parent->_kv.first) parent