目录
一、AVL树的概念
二叉搜索树如果插入有序或接近有序的数值会退化成单支树,查找效率很低。前苏联科学家G. M. Adelson-Velsky和E. M. Landis共同发表论文,提出 AVL树 ,是一种**⾃** 平衡二叉查找树 ,能够在插入或删除后自动调整,保持平衡。
一棵AVL树或是空树,或是有以下特点:
1.它左右 子树的高度差不超过1。
2.它的 左右子树也是都是AVL。
AVL树实现这⾥我们引⼊⼀个 平衡因⼦(balance factor) 的概念,每个结点都有⼀个平衡因⼦,这里我们采用主流教材的定义公式: 任何结点的平衡因子等于右子树的高度减去左子树的高度 ,也就是说任何结点的平衡因⼦等于0/1/-1
二、AVL树的实现
1.节点的定义
为了方便后续的操作,这里将AVL树中的结点定义为三叉链结构 ,并在每个结点当中引入平衡因子(右子树高度-左子树高度)。
cpp
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; // 平衡因子
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
{}
};
++实现AVL树不一定要用平衡因子,也有其它方法,但相比之下使用平衡因子实现更简单。++
2.AVL树的插入
AVL树插入的总过程是先按照二叉搜索树的规则去插入(小于往左走,大于往右走,走到空插入),然后根据插入的位置更新父亲及祖先的平衡因子,更新过程中如果平衡因子不合法 就会进行旋转来调整树的结构。
2.1平衡因子的更新
设cur是新增节点,parent是新结点的父亲节点。
1.按照平衡因子的值为右子树的高度减去左子树的高度,所以:
- 当 cur 是 parent 的左孩子,则 parent 的平衡因子 - 1;
- 当 cur 是 parent 的右孩子,则 parent 的平衡因子 + 1。
2.parent的平衡因子更新后,以parent为根的树的高度可能会改变,这时要根据bf的值来判断是否继续向祖先方向更新平衡因子。
• parent的平衡因子为0 (则更新之前平衡因子为-1/1),以parent为根的树的高度不变,无需向上更新。
• parent的平衡因子为1/-1(则更新之前平衡因子为0),以parent为根的树的高度 + 1,需要向上更新,直到某个节点平衡因子为0为止

• parent的平衡因子为2/-2 (则更新之前平衡因子为1/-1),此时以parent为根的树已经不平衡,需要进行旋转。
停止更新的条件是:更新完根节点或某个节点平衡因子更新后的值为0。
节点插入和更新平衡因子的代码:
cpp
//插入
bool Insert(const pair<K, V>& kv)
{
//根节点为空,直接插入
if (_root == nullptr)
{
_root = new Node(kv);
_size++;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
//寻找合适位置
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;
}
else
{
parent->_right = cur;
}
//让cur的parent指针指向父亲
cur->_parent = parent;
//更新平衡因子
while (parent)
{
//根据插入节点的位置更新父亲的平衡因子
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//判断平衡因子的值
if (parent->_bf == 0)//等于0,停止更新
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)//等于1,继续向上更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)//等于2,需要旋转
{
//开始旋转
//
break;
}
else //不可能有其他情况,走到这里直接断言报错
{
assert(false);
}
}
_size++;
return true;
}
2.2旋转
当平衡因子是2/-2时,进行旋转。
旋转分为4种,**左单旋,右单旋,左右双旋,右左双旋,**下面实现右单旋和左右双旋,剩下两个实现过程和他们相同。
1.右单旋
新增节点在较高子树的左侧时,进行右单旋。(根右边低,带着根往下移)

旋转过程:将节点10标记为parent,5为subL,b为subLR,将parent的左指针指向subLR,subL的右指针指向parent,subL成为新的根。旋转完成后,subL和parent的平衡因子都置为0。
实现代码:
cpp
//右单旋
void RotateR(Node* parent)
{
//先标记subL和subLR
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;//让parent的左指针指向subLR
if (subLR) subLR->_parent = parent;//让subLR的父指针指向parent。注意subLR可能为空,需要判断
//标记parent的父亲
Node* ppNode = parent->_parent;
subL->_right = parent;//让subL的右指针指向parent
parent->_parent = subL;//parent的父指针指向subL
//处理ppNode和subL的关系
if (ppNode == nullptr)//ppNode为空说明parent之前是根节点
{
_root = subL;//subL成为新根
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent) ppNode->_left = subL;
else ppNode->_right = subL;
subL->_parent = ppNode;
}
//调整平衡因子
parent->_bf = 0;
subL->_bf = 0;
}
2.左右双旋
如图,若是插入在较高子树的右边,只进行单旋不能调节好树的状态。

这时就要进行双旋,双旋可以套用单旋,但注意要调节平衡因子。
1)插入新结点

2)以10为根节点进行左单旋

3)以20为根节点进行右单旋

左右双旋后,平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况:
1、当subLR原始平衡因子是-1时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0。

2、当subLR原始平衡因子是1 时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0。
3、当subLR原始平衡因子是0 时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0。
代码实现:
cpp
//左右双旋
void RotateLR(Node* parent)
{
//先标记subL和subLR
Node* subL = parent->_left;
Node* subLR = subL->_right;
//记录subLR的平衡因子
int bf = subLR->_bf;
RotateL(subL);//将subL作为旋转点,进行一次左单旋
RotateR(parent);//将parent作为旋转点,进行一次右单旋
//调整平衡因子
if (bf == 0)//场景1
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)//场景2
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)//场景3
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else //不可能有其他情况,走到这里直接断言报错
{
assert(false);
}
}
2.3.节点的查找
与传统二叉搜索树的查找相同
cpp
//查找结点
Node* Find(const K&key)//查找key
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > key)//key在左子树
{
cur = cur->_left;
}
else if (cur->_kv.first < key)//key在右子树
{
cur = cur->_right;
}
else
{
return cur;//相等,返回当前结点
}
}
//没有找到返回空
return nullptr;
}
2.4.节点个数大小
cpp
public:
int size()
{
return _size(_root);
}
private:
Node* _root=nullptr;
int _size(Node* root)
{
if (root == nullptr)
return 0;
return _size(root->_left) + _size(root->_right) + 1;
}
三、总结
AVL树是绝对平衡的二叉搜索树,每个节点子树高度差不超过1,查询效率高log2N。其中旋转是重点,可以重点看一下。感谢观看,如有错误欢迎指出。
