目录
一、AVL树的概念
1.1 为什么需要 AVL 树?
我们都知道二叉搜索树BST
。在理想情况下,BST
的搜索、插入、删除操作的时间复杂度是O(log n)
。但是,如果插入的数据是有序的(例如 1, 2, 3, 4, 5),BST
就会退化成一条链表,时间复杂度恶化到O(n)
。
AVL
树 就是为了解决这个问题而诞生的。它是一种 自平衡的二叉搜索树。它的核心思想是:在插入和删除节点时,通过一系列的 旋转 操作,始终保持树的左右子树高度大致相等,从而确保树的高度始终保持在 O(log n)
级别,进而保证所有操作的时间复杂度都是稳定的 O(log n)
。
1.2 AVL 树的核心:平衡因子
AVL
树的核心机制是 平衡因子。
-
平衡因子:对于树中的任意一个节点,它的平衡因子定义为 其左子树的高度减去其右子树的高度。
平衡因子 = 右子树高度 - 左子树高度
。 -
平衡条件:
AVL
树要求每个节点的平衡因子只能是-1, 0, 或 1
。如果任何一个节点的平衡因子的绝对值超过了1
,那么这个树就是 不平衡 的,需要通过旋转来恢复平衡。
1.3 AVL 树的节点结构
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent; //便于更新平衡因子
int _bf; //balance factor
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{ }
};
如上图,我们采用了K-V
这一通用结构,使用 std::pair
存储键值对,父指针的作用:便于回溯更新平衡因子,简化旋转操作中的链接调整。
二、AVL树的实现
2.1 AVL树的 insert 操作
insert
操作也遵循二叉搜索树的规则,但和以往不同的是当插入一个结点时,我们需要关注平衡因子的变化,新插入的结点会影响祖先结点的平衡因子。所以我们需要维护更新从新增结点 -> 根节点
的平衡因子,更新平衡因子过程中可能会出现不平衡,这时要对不平衡子树进行旋转,旋转本质在调平衡的同时,也降低了子树的高度,不会再影响上一层,所以插入结束。
2.1.2 平衡因子的更新
更新原则
插入节点会影响parent
结点的平衡因子变化,根据公式 平衡因子 = 右子树高度 - 左子树高度
,新增节点是parent
结点的右子树时,parent
的平衡因子++
,否则--
。parent
所在子树的高度是否变化决定了是否向上更新。
更新停止条件
- 当更新后的
parent
结点的平衡因子是0
,说明是由1
或-1
变成的0
,那么这时候子树由一边高一边低变成了一样高,高度没有发生变化,不会影响parent
的祖先,插入结束。 - 当更新后的
parent
结点的平衡因子是1
或者-1
,说明更新之前是0
。那么这时候子树由一样高变成了一边高一边低,高度发生变化,会影响parent
的祖先,需要继续向上更新。 - 当更新后的
parent
结点的平衡因子是2
或者-2
,说明是由1 -> 2
或者-1 -> -2
,这时候子树的平衡被破坏了需要进行旋转操作,旋转的目标有两个:1、把parent
子树旋转平衡。2、降低parent
子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。
了解到以上这些我们来实现一下 insert
操作。
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (kv.first > cur->_kv.first)
{
cur = cur->_right;
}
else if(kv.first < cur->_kv.first)
{
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_bf = 0;
if (cur->_kv.first > parent->_kv.first) parent->_right = cur;
else parent->_left = cur;
cur->_parent = parent;
//更新维护平衡因子
while (parent)
{
if (parent->_right == cur)
{
parent->_bf++;
}
else parent->_bf--;
if (parent->_bf == 0) //子树高度无变化,停止向上更新
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)//子树高度发生变化
{
//继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转操作
}
else
{
//不是上述情况,说明这棵树之前就不是 AVL 树
assert(false);
}
}
return true;
}
如上就是除去旋转之外,插入函数的核心代码。代码中的插入部分和二叉搜索树一摸一样,就是最后需要维护平衡因子。
2.2 AVL树的 旋转 操作
2.2.1 旋转的原则
旋转的前提是要遵守搜索树的规则,其次要让不平衡的树变平衡,降低树的高度,旋转分为左单旋、右单旋、左右双旋、右左双旋
。
2.2.2 右单旋
旋转核心步骤,因为5 < b子树的值 < 10
,将b
变成10
的左子树,10
变成5
的右子树,5
变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2
,高度没有变化,插入结束。
实现细节:当结点的链接更改时,不要忘记更改结点的_parent
指针,同时右单旋之后,parent
和subL
结点的平衡因子都是0
。
//右单旋
void RotateR(Node* parent)
{
//parent 是平衡因子不符合规则的结点
Node* subL = parent->_left;//相当于插入函数部分的 cur
Node* subLR = subL->_right;
//进行旋转操作
parent->_left = subLR;
if (subLR)//维护 subLR 的父指针
{
subLR->_parent = parent;
}
Node* pparent = parent->_parent; //subL 之后要更改的父指针指向
subL->_right = parent;
parent->_parent = subL;
//判断之前 parent 是什么角色, 便于更改 subL 的父指针
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else pparent->_right = subL;
subL->_parent = pparent;
}
//维护平衡因子
parent->_bf = subL->_bf = 0;
}
右单旋在AVL
树插入部分旋转处理的应用:
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转操作
if (parent->_bf == -2 && cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
break;
}
2.2.3 左单旋
旋转核心步骤,因为10 < b子树的值 < 15
,将b
变成10
的右子树,10
变成15
的左子树,15
变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2
,子树的高度没有发生变化,插入结束。
代码实现的细节部分和右单旋一样。
//左单旋
void RotateL(Node* parent)
{
//parent 是平衡因子不符合规则的结点
Node* subR = parent->_right; //相当于插入函数部分的 cur
Node* subRL = subR->_left;
//进行旋转操作
parent->_right = subRL;
if (subRL)//维护 subRL 的父指针
{
subRL->_parent = parent;
}
Node* pparent = parent->_parent; //subR 之后要更改的父指针指向
subR->_left = parent;
parent->_parent = subR;
//判断之前 parent 是什么角色, 便于更改 subR 的父指针
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else pparent->_right = subR;
subR->_parent = pparent;
}
//维护平衡因子
parent->_bf = subR->_bf = 0;
}
左单旋在AVL
树插入部分旋转处理的应用:
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转操作
if (parent->_bf == -2 && cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
//左单旋
RotateL(parent);
}
break;
}
2.2.4 左右双旋
情况一::h >= 1
时,新增结点插入在e
子树,e
子树高度从h-1
变为h
,并不断更新8->5->10
平衡因子,引发旋转,其中8
的平衡因子为-1
,先以5
为旋转点进行一个左单旋,再以10
为旋转点进行一个右单旋,这棵树就平衡了,旋转后8
和5
平衡因子为0
,10
平衡因子为1
。
情况二:h >= 1
时,新增结点插入在f
子树,f
子树高度从h-1
变为h
并不断更新8->5->10
平衡因子,引发旋转,其中8
的平衡因子为1
,先以5
为旋转点进行一个左单旋,再以10
为旋转点进行一个右单旋,这棵树就平衡了,旋转后8
和10
平衡因子为0
,5
平衡因子为-1
。
情况三:h == 0
时,a/b/c
都是空树,b
自己就是一个新增结点,不断更新5->10
平衡因子,引发旋转,其中8
的平衡因子为0
,先以5
为旋转点进行一个左单旋,再以10
为旋转点进行一个右单旋,这棵树就平衡了,旋转后8
和10
和5
平衡因子均为0
。
搞清楚左右双旋的三种情况之后就可以实现代码了。
// 左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf; // 维护平衡因子的关键点
RotateL(subL);
RotateR(parent);
if (bf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
左右双旋在AVL
树插入部分旋转处理的应用:
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转操作
if (parent->_bf == -2 && cur->_bf == -1)
{
// 右单旋
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
// 左单旋
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
// 左右双旋
RotateLR(parent);
}
break;
}
2.2.5 右左双旋
情况一:h >= 1
时,新增结点插入在e
子树,e
子树高度从h-1
变为h
,并不断更新12->15->10
平衡因子,引发旋转,其中12
的平衡因子为-1
,先以15
为旋转点进行一个右单旋,再以10
为旋转点进行一个左单旋,这棵树就平衡了,旋转后10
和12
平衡因子为0
,15
平衡因子为1
。
情况二:h >= 1
时,新增结点插入在f
子树,f
子树高度从h-1
变为h
,并不断更新12->15->10
平衡因子,引发旋转,其中12
的平衡因子为1
,先以15
为旋转点进行一个右单旋,再以10
为旋转点进行一个左单旋,这棵树就平衡了,旋转后15
和12
平衡因子为0
,10
平衡因子为-1
。
情况三:h == 0
时,a/b/c
都是空树,b
自己就是一个新增结点,不断更新15->10
平衡因子,引发旋转,其中12
的平衡因子为0
,先以15
为旋转点进行一个右单旋,再以10
为旋转点进行一个左单旋,这棵树就平衡了,旋转后10
和12
和15
平衡因子均为0
。
和左右双旋一样,搞清楚右左双旋的三种情况,就可以实现代码了。
// 右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf; // 维护平衡因子的关键点
RotateR(subR);
RotateL(parent);
if (bf == 0)
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = subRL->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋在AVL
树插入部分旋转处理的应用:
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转操作
if (parent->_bf == -2 && cur->_bf == -1)
{
// 右单旋
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
// 左单旋
RotateL(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;
}
2.3 中序遍历
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
测试代码:
void TestAVLTree1()
{
AVLTree<int, int> t;
// 特殊的带有双旋场景的测试用例
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
if (e == 14)
{
int x = 0;
}
t.insert({ e, e });
}
t.InOrder();
}
测试结果:
2.4 其它接口的实现
2.4.1 查找函数
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;
}
2.4.2 Size 和 Height 函数
public:
int Size()
{
return _Size(_root);
}
int Height()
{
return _Height(_root);
}
private:
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
2.4.3 AVL树的判断函数
public:
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
private:
bool _IsBalanceTree(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
int bf = rightH - leftH;
if (abs(bf) > 2 || bf != root->_bf)
{
cout << root->_kv.first << ":" << root->_kv.second << "平衡因子异常" << endl;
return false;
}
return _IsBalanceTree(root->_left)
&& _IsBalanceTree(root->_right);
}
测试代码:
void TestAVLTree2()
{
AVLTree<int, int> t;
// 特殊的带有双旋场景的测试用例
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
if (e == 14)
{
int x = 0;
}
t.insert({ e, e });
cout << "insert:" << e << "->" << t.IsBalanceTree() << endl;
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
cout << t.Size() << " " << t.Height() << endl;
cout << t.Find(2) << " " << t.Find(17) << endl;
}
测试结果:
总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~