在极端情况下,搜索二叉树会退化成线性表,查找效率变得低下,根本原因是因为搜索二叉树左右子树的高度极大程度上取决于根节点,为了解决这种情况,AVL树和红黑树被相继提出,这两种数据结构都是在搜索二叉树的基础上通过适当的调整来保持左右子树近似相等。
AVL树插入过程:
1、寻找合适的位置,这一步和搜索二叉树一致
2、判断高度差是否超出1
3、如果超出则进行旋转调整降低高度
定义AVL树的节点
cpp
template<class K> //以set为例
struct AVLTreeNode{
K _key;
AVLTreeNode<K>* _left;
AVLTreeNode<K>* _right;
AVLTreeNode<K>* _parent;
int _bf;
/*_bf作为平衡因子,假设平衡因子的计算公式 右树高度-左树高度
AVL树规定左右子树高度差不能超过1,因此正常情况下_bf只可能是
0、1、-1
_parent代表该节点的父节点,之所以存在是因为在后续调整中能够快
速定位到父节点*/
AVLTreeNode(const K& key)
:_key(key)
,_left(nullptr)
,_right(nullptr)
,__parent(nullptr)
,_bf(0)
{}//新增节点一定叶节点,左右子树高度均为0,故_bf=0
};
定义AVL树类
cpp
template<class K>
class AVLTree{
typedef AVLTreeNode<K> Node;
public:
......
private:
Node* _root=nullptr;
};
编写插入功能
1、找到合适位置
cpp
bool Insert(const K& key){
Node* cur=_root,* parent=nullptr;
if(cur==nullptr){
_root=new Node(key);
return true;
}
while(cur){
parent=cur;
if(key<cur->_key)
cur=cur->_left;
else if(key>cur->_key)
cur=cur->_right;
else
return false;
}
Node* cur=new Node(key);
if(key<parent->_left)
cur=parent->_left;
else
cur=parent->_right;
cur->_parent=parent; // 不要忘记更新
...............
.......
return true;
2、判断是否需要调整:
h代表高度为h的抽象树,图中给出的例可能是一颗子树,也可能是一颗完整的树


根据上述分析,插入节点后马上要做的事情就是先向上更新平衡因子
插入的节点在父节点右,父节点bf加1
反之父节点bf减1
再看父节点的平衡因子
如果为0,则说明左右子树高度相等,即没有打破规则
通俗地理解为在较矮的子树插入
如果为1 or -1,说明左右子树高度出现了一端高的情况,那么也一定会影响到父节点的父节点的平衡因子
需要继续向上调整
如果为2 or -2,那就需要调整了,设计到的旋转操作见下文值得注意的是,调整过后我们就可以break了,因为调整后目标树高度
会回到原来高度,就不会对上一层影响了
那么就先写更新平衡因子的代码片段吧
cpp
while (parent) {
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0) {
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) {
//旋转调整
}
else {
assert(false);
}
}
3、旋转操作
针对情况3的调整,称作右单旋
它的镜像操作称作左单旋
代码实现
cpp
void RotateR(Node* parent){ //右单旋
Node* subR = parent->_left;
Node* pparent = parent->_parent;
Node* subRR = subR->_right;
/*parent代表要进行旋转的节点,例如图中的30
ppparent代表要旋转节点的父节点
subR代表目标节点的左节点
subRR代表目标节点左节点的右节点
*/
parent->_left = subRR;
if (subRR) {
subRR->_parent = parent;
}
subR->_right = parent;
if (pparent) {
if (pparent->_left == parent) {
pparent->_left = subR;
}
else {
pparent->_right = subR;
}
}
else {
_root = subR; //针对目标节点是根节点的情况
}
subR->_parent = pparent;
parent->_parent = subR;
subR->_bf = parent->_bf = 0;//不要忘记更新
}
//左单旋RotateL同理
针对情况2,调整操作称作左右双旋
它的镜像操作是右左双旋
右左双旋是类似的
代码实现
cpp
void RotateLR(Node* parent) { //左右双旋
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
//更新bf
if (bf == 1) { //c位置插入
subL->_bf = -1;
subLR->_bf = parent->_bf = 0;
}
else if (bf == -1) { //b位置插入
parent->_bf = 1;
subLR->_bf = subL->_bf = 0;
}
else if (bf == 0) {
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else {
assert(false);
}
}
//双旋后的平衡因子需要画图分情况讨论,这里不做讲解