📕 概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序,二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和 E.M.Landis 在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
📕 具体实现
节点定义
如下,AVL 树相比二叉搜索树,还要多一个指向父亲节点的指针,以及影响因子。
影响因子代表的是该节点左右子树的高度差。假设当前有一个节点 A,A左右子树均为空。如果在A 的左边插入一个节点,那么 A 的影响因子就 -1 ,代表 A 的左子树比右子树高度高1。如果此时A的右边再插入一个节点,那么A 的影响因子就 +1,变成了0,代表A 的左右子树高度相同。
并且,A节点的影响因子变动,也会影响 A 的父亲节点的影响因子。
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; // balance factor 影响因子
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
框架
如下,是AVL树的框架。
#pragma once
#include<iostream>
#include<cassert>
using namespace std;
namespace simulate
{
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; // balance factor 影响因子
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
// 成员方法 …………
private:
Node* _root=nullptr;
};
}
插入
AVL 树的插入,本质上和搜索二叉树的插入类似。找到插入位置之后,插入节点。
但是 AVL 树插入节点之后,还需要检查是否平衡,如果不平衡(左右子树高度差超过1),那么就不满足 AVL 树的性质,需要进行旋转来降低高度,达到平衡的状态。
所以如下,插入节点的代码,前半部分和 搜索二叉树别无二致,后面检查平衡因子才是重头戏。
如下,插入 cur 节点之后,其父亲节点的平衡因子也受到以下,由于 cur 修改在 parent 的右边,所以 parent 的平衡因子 +1 。但是,这就结束了吗?
通过上图,很明显看出这已经不是 AVL 树了,8节点的左右子树高度差为2,需要旋转。
但是,如果更新平衡因子的方法,只按照上面说的,更新一轮 cur 与 parent,极有可能是检查不到错误的,所以必须往上继续更新。如下,将 cur 指向 其父亲节点,parent 也指向其父亲节点,继续检查平衡因子。
当检查到某个节点的平衡因子为 2 或者 -2 的时候,就说明其左右子树高度差为2了,需要旋转来降低高度。
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent=cur;
cur = cur->_right;
}
else
return false;
}
// 插入
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
cur->_parent = parent;
// 调整平衡因子
while (parent)
{
if (cur == parent->_right)
parent->_bf++;
else if (cur == parent->_left)
parent->_bf--;
if (parent->_bf == 1 || parent->_bf == -1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
break;
else if (parent->_bf == 2 || 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)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else
assert(false);
break;
}
else
{
assert(false);
}
}
return true;
}
那么,如何进行平衡因子的更新,也就成了插入操作里的重点。
对于插入的 cur 节点,它一定是在 parent 节点的左子树或者右子树,那么 parent 的平衡因子就可以由此进行 -1 或者 +1 操作。更新之后的 parent 节点的平衡因子可能有三种情况:0、-1 或者 1、2 或者 -2。
- 更新之后的 parent 节点平衡因子是 1 或者 -1。如下情况1,就是这一类,说明 parent 节点原本的平衡因子是 0,所以更新之后才会是 1 或者 -1。那么这时就说明,parent 为根节点的子树,其高度增加了1,这肯定会影响 parent 节点的父亲节点(此图中是值为 8 的节点)。所以必须要向上更新平衡因子,只需要 把 cur 指向其父亲节点,parent 指向其父亲节点,然后进行同样的判定即可。
- 更新之后的 parent 节点,平衡因子是 0 。如下情况2 ,说明其原本是不平衡的,更新之后为0,说明变平衡了,但是 以 parent 节点为根节点的子树,其高度没有增加,那么就没有向上更新平衡因子的必要。
- 更新之后的 parent 节点,平衡因子是 2 或者 -2。说明 parent 左右子树高度差为2,需要旋转。
★ 旋转 ★
要进行旋转,必然是更新之后某个节点的平衡因子为 2 或者 -2,那么更新之前,该节点的平衡因子必然是 1 或者 -1,也就是说,更新前,该节点的 左子树 比 右子树高 1,或者右子树比左子树高1,可以分为这两种情况。
情况一
如下,左子树比右子树高1,如果插入的节点是在 a 区域,那么 30 这个节点的平衡因子就变成了 -1,60 的平衡因子是 -2。这种情况可以进行右单旋转,如图,将 30 放到最高,60 放低。但是注意,60 上面可能还有父亲节点,只是这里没画出来,旋转也要处理 60 的父亲节点。
所以,下图中,这种情况下涉及到的节点是 father(值为60),subL(值为30),subLR(b部分的根节点),pparent(最左边的状态时,值为60的父亲节点)。
第一种情况,旋转前,60 和 30 两个节点构成 / 的形状(便于记忆,右边高,进行右单旋)。
情况二
如下,右子树比左子树高1,如果插入的节点是在 c 这个部分,那么 60 节点的平衡因子就变成了 1,30 节点的平衡因子就变成了 2 ,此时可以进行左单旋转,如图所示。
下图中,涉及到的节点是和上面基本一样的,father(值为 30),subR(值为 60),subRL(b 部分的根节点),pparent(最左边的状态时,30的父亲节点)。
第二种情况,旋转前,30 和 60 两个节点构成 \ 的形状(便于记忆,左边高,进行左单旋)。
情况三
但是,左子树高时,如果插入在左子树的另一边呢?如下,插入前,依然是左子树比右子树高1,此时不插入 a 部分,而是插入左子树的另一部分(即 b 部分或者 c 部分),此时值为60的节点,平衡因子是 -1,值为 30 的节点,平衡因子是 1,值为 90 的节点,平衡因子是 -2。这又是另一种情况了,因为这种情况要进行两次旋转。
第一次,先把值为 30 的节点看作根节点,即 90 节点和 d 部分忽略。很明显,以 30 为根节点的部分,是上面需要左单旋的情况。所以直接进行左单旋。
第二次,以 90 为根节点,进行右单旋即可。
插入在 b 或者 c 部分,影响的只是旋转之后,平衡因子的更新,其他没有什么影响。此外,如果插入在 c 部分,那么插入之后,值为 60 的节点平衡因子就是 1,值为 30、90 节点的平衡因子和插入在 b 部分之后是一样的。而我们就可以根据这一点,来判断插入在 b 区域还是 c 区域(设置一个变量记录 60 这个节点的平衡因子),进而根据情况来修改旋转后的平衡因子。
如下图,旋转之后,平衡因子受到影响的只有 30、60、90 这三个节点,插入在 b 还是 c,影响的是 30、90 这两个节点。
第三种情况, 旋转前,90、30、60 节点构成 < 的形状,要进行两次旋转,先从下面开始,下面是 \ ,所以先左单旋转,上面是 / ,所以再右单旋。
情况四
如下,是最后一种情况。
插入前,右子树高,但是不插入在 d 部分,插入在 b 或者 c 部分。
第一步,以 90 节点为根节点,进行右单旋。
第二步,以 30 节点为根节点,进行左单旋。
其余思想均和第三种情况一样,就不过多赘述。
第四种情况,旋转前,30、90、60 这三个节点构成 > 的形状,从下往上,先是 / ,所以右单旋,再是 \ ,所以再左单旋。
代码如下。当然了,这几个旋转方法都是成员函数内部使用的,所以用 private 修饰,对外不可见。
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* pparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
parent->_bf = 0;
subR->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
if (pparent == nullptr)
{
subL->_parent = nullptr;
_root = subL;
}
else
{
if (parent == pparent->_left)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
parent->_bf = 0;
subL->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_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);
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
assert(false);
}
判断平衡
如下,判断平衡的关键在于,左右子树的高度差是否和平衡因子的绝对值相同,所以,需要知道左右子树的高度,要用到 Height() 来辅助判断。
public:
bool IsBalance()
{
return _isBalance(_root);
}
int Height()
{
return _Height(_root);
}
private:
bool _isBalance(Node* root)
{
if (root == nullptr)
return true;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
if (root->_bf != rightH - leftH)
{
cout << root->_kv.first << "号平衡因子错误" << endl;
root->_bf = rightH - leftH;
}
return abs(rightH - leftH) < 2
&& _isBalance(root->_left)
&& _isBalance(root->_right);
}
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;
}
📕 源代码
#pragma once
#include<iostream>
#include<cassert>
using namespace std;
namespace simulate
{
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; // balance factor 影响因子
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent=cur;
cur = cur->_right;
}
else
return false;
}
// 插入
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
cur->_parent = parent;
// 调整平衡因子
while (parent)
{
if (cur == parent->_right)
parent->_bf++;
else if (cur == parent->_left)
parent->_bf--;
if (parent->_bf == 1 || parent->_bf == -1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
break;
else if (parent->_bf == 2 || 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)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else
assert(false);
break;
}
else
{
assert(false);
}
}
return true;
}
void Inorder()
{
_Inorder(_root);
}
bool IsBalance()
{
return _isBalance(_root);
}
int Height()
{
return _Height(_root);
}
private:
bool _isBalance(Node* root)
{
if (root == nullptr)
return true;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
if (root->_bf != rightH - leftH)
{
cout << root->_kv.first << "号平衡因子错误" << endl;
root->_bf = rightH - leftH;
}
return abs(rightH - leftH) < 2
&& _isBalance(root->_left)
&& _isBalance(root->_right);
}
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;
}
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first << " ";
_Inorder(root->_right);
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* pparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
parent->_bf = 0;
subR->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
if (pparent == nullptr)
{
subL->_parent = nullptr;
_root = subL;
}
else
{
if (parent == pparent->_left)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
parent->_bf = 0;
subL->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_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);
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
assert(false);
}
private:
Node* _root=nullptr;
};
}