一、AVL树的概念
AVL树是最先发明的自平衡⼆叉查找树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树:它的
左右子树都是AV树,且左右子树的高度差的绝对值不超过1。AVL树是⼀颗高度平衡搜索⼆叉树,
通过控制高度差去控制平衡。
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis是两个前苏联的科学家,他们在1962
年的论文《An algorithm for the organization of information》中发表了它。
AVL树实现这里我们引入⼀个平衡因子(balance factor)的概念,每个结点都有⼀个平衡因子,任何
结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,
AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,
就像⼀个风向标⼀样。
思考⼀下为什么AVL树是高度平衡搜索⼆叉树,要求高度差不超过1,而不是高度差是0呢?0不是更
好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。⽐
如⼀棵树是2个结点,4个结点等情况下,高度差最好就是1,无法作为高度差是0
AVL树整体结点数量和分布和完全⼆叉树类似,高度可以控制在 ,那么增删查改的效率也可
以控制在 ,相比⼆叉搜索树有了本质的提升。
二、AVL树的实现
1、AVL树的结构
#pragma once
#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因子可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:
private:Node * _root = nullptr;
};
2、插入
-
插入一个值按⼆叉搜索树规则进行插入。
-
新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新
从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可
以停止了,具体情况我们下面再详细分析。
-
更新平衡因子过程中没有出现问题,则插入结束
-
更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,本质降低了子树
的高度,不会再影响上⼀层,所以插入结束。
3、平衡因子更新
更新原则:
平衡因子 = 右子树高度-左子树高度
只有子树高度变化才会影响当前结点平衡因子。
插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在
parent的左子树,parent平衡因子–
parent所在子树的高度是否变化决定了是否会继续往上更新
更新停止条件:
更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0 或者 1->0,说明更新前
parent子树⼀边高⼀边低,新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会
影响parent的父亲结点的平衡因子,更新结束。
更新后parent的平衡因子等于1 或 -1,更新前更新中parent的平衡因子变化为0->1 或者 0->-1,说
明更新前parent子树两边⼀样高,新增的插入结点后,parent所在的子树⼀边高⼀边低,parent所
在的子树符合平衡要求,但是高度增加了1,会影响arent的父亲结点的平衡因子,所以要继续向上
更新。
更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因子变化为1->2 或者 -1->-2,说
明更新前parent子树⼀边高⼀边低,新增的插入结点在高的那边,parent所在的子树高的那边更高
了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把
parent子树旋转平衡。2、降低parent子树的高度,恢复到插入结点以前的高度。所以旋转后也不
需要继续往上更新,插入结束。
更新到10结点,平衡因子为2,10所在的子树已经不平衡,需要旋转处理
更新到中间结点,3为根的子树高度不变,不会影响上⼀层,更新结束
最坏更新到根停⽌
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 (parent->_kv->first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_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){//选转break;}}}
4、旋转
a、右单旋
本图1展⽰的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要
求。10可能是整棵树的根,也可能是⼀个整棵树中局部的子树的根。这里a/b/c是高度为h的子树,
是⼀种概括抽象表示,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体图2/图3/图4/
图5进行了详细描述。
在a子树中插入⼀个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平
衡因子从-1变成-2,10为根的树左右高度差超过1,违反平衡规则。10为根的树左边太高了,需要
往右边旋转,控制两棵树的平衡。
旋转核心步骤,因为5 < b子树的值 < 10,将b变成10的左子树,10变成5的右子树,5变成这棵树新
的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2,符合旋转原
则。如果插入之前10整棵树的⼀个局部子树,旋转后不会再影响上⼀层,插入结束了。
代码:
void RotateR(Node* parent)
{Node* Pparent = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;// 需要注意除了要修改孩子指针指向,还是修改父亲if (subLR)//防止对空指针解引用 subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (Pparent == nullptr)//parent是根时{_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}//更新平衡因子parent->_bf = subL->_bf = 0;
}
b、左单旋
本图6展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要
求。10可能是整棵树的根,也可能是⼀个整棵树中局部的子树的根。这里a/b/c是高度为h的子树,
是⼀种概括抽象表示,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体跟上⾯左旋类
似。
在a子树中插入⼀个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平
衡因子从1变成2,10为根的树左右高度差超过1,违反平衡规则。10为根的树右边太高了,需要往
左边旋转,控制两棵树的平衡。
旋转核心步骤,因为10 < b子树的值 < 15,将b变成10的右子树,10变成15的左子树,15变成这棵
树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2,符合旋转
原则。如果插入之前10整棵树的⼀个局部子树,旋转后不会再影响上⼀层,插入结束了。
代码:
void RotateL(Node* parent)
{Node* Pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subL->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (Pparent == nullptr)//parent是根时{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;
}
c、左右双旋
通过图7和图8可以看到,左边高时,如果插入位置不是在a子树,而是插入在b子树,b子树高度从h变
成h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边
高,但是插入在b子树中,10为跟的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边
高,需要用两次旋转才能解决,以5为旋转点进行⼀个左单旋,以10为旋转点进行⼀个右单旋,这棵树
这棵树就平衡了
图7和图8分别为左右双旋中h=0和h=1具体场景分析,下面我们将a/b/c子树抽象为高度h的AVL
子树进行分析,另外我们需要把b子树的细节进⼀步展开为8和左子树高度为h-1的e和f子树,因为
我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。b子树中新增结点的位置
不同,平衡因子更新的细节也不同,通过观察8的平衡因子不同,这里我们要分三个场景讨论。
场景1:h >= 1时,新增结点插⼊在e子树,e子树高度从h-1变为h并不断更新8->5->10平衡因子,
引发旋转,其中8的平衡因子为-1,旋转后8和5平衡因子为0,10平衡因子为1。
场景2:h >= 1时,新增结点插⼊在f子树,f子树高度从h-1变为h并不断更新8->5->10平衡因子,引
发旋转,其中8的平衡因子为1,旋转后8和10平衡因子为0,5平衡因子为-1。
场景3:h == 0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新5->10平衡因子,引发旋
转,其中8的平衡因子为0,旋转后8和10和5平衡因子均为0。
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//根据subLR的平衡因子判断RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1)parent->_bf = 1;{subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
d、右左双旋
跟左右双旋类似,下⾯我们将a/b/c子树抽象为⾼度h的AVL子树进⾏分析,另外我们需要把b子树的
细节进⼀步展开为12和左子树⾼度为h-1的e和f子树,因为我们要对b的⽗亲15为旋转点进⾏右单
旋,右单旋需要动b树中的右子树。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通
过观察12的平衡因子不同,这⾥我们要分三个场景讨论。
场景1:h >= 1时,新增结点插⼊在e子树,e子树⾼度从h-1变为h并不断更新12->15->10平衡因
子,引发旋转,其中12的平衡因子为-1,旋转后10和12平衡因子为0,15平衡因子为1。
场景2:h >= 1时,新增结点插⼊在f子树,f子树⾼度从h-1变为h并不断更新12->15->10平衡因子,
引发旋转,其中12的平衡因子为1,旋转后15和12平衡因子为0,10平衡因子为-1。•
场景3:h == 0时,a/b/c都是空树,b自己就是⼀个新增结点,不断更新15->10平衡因子,引发旋
转,其中12的平衡因子为0,旋转后10和12和15平衡因子均为0。
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
三、整体代码
#pragma once
#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因⼦可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};
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 (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 链接父亲cur->_parent = parent;if (parent->_kv->first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_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){//选转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){RotateLR(parent);}else{RotateRL(parent);}break;}}}void RotateR(Node* parent){Node* Pparent = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;// 需要注意除了要修改孩⼦指针指向,还是修改⽗亲if (subLR)//防止对空指针解引用 subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (Pparent == nullptr)//parent是根时{_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}//更新平衡因子parent->_bf = subL->_bf = 0;}void RotateL(Node* parent){Node* Pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subL->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (Pparent == nullptr)//parent是根时{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//根据subLR的平衡因子判断RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1)parent->_bf = 1;{subL->_bf = -1;subLR->_bf = 0;parent->_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 == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}
private:Node * _root = nullptr;
};