这篇文章将主要将AVL树的插入
一.AVL树相关概念
1.AVL树是一颗空树或具有以下性质的二叉搜索树 :
它的左右子树都是AVL树;
左右子树的高度差不超过1;
这些性质使AVL树是一颗高度平衡的二叉搜索树,可以通过控制其高度差去控制平衡。
2.在AVL树引入一个平衡因子(balance factor) 的概念,每个节点都有一个平衡因子,它的值是该节点右子树的高度减去左子树的高度 (也可以是左子树减右子树,这篇文章统一右子树减左子树),也就是说正常平衡因子的值只有-1/0/1三种情况。AVL树不一定要平衡因子,这只是AVL树的其中一种实现方式,通过它我们可以更方便的观察树是否平衡。
3.AVL树的节点分布与完全二叉树类似,高度可控制到logN,极大提高的增删查改的效率,相比于二叉搜索树有了质的提升。
二.AVL树的实现
1.AVL树的结构
相比于二叉搜索树,AVL树的节点多了parent指针和平衡因子:
cpp
template<class k,class v>
struct TreeNode
{
//平衡因子和parent可以没有,但这要用另一种方法实现了
TreeNode* _left;
TreeNode* _right;
TreeNode* _parent;
pair<k, v> _kv;//储存的数据
int _bf;//blance factor,平衡因子
TreeNode(const pair<k, v>& data = pair<k, v>())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(data)
{ }
};
树本体的成员变量有个根就好了:
cpp
template<class k, class v>
class AVLTree
{
typedef TreeNode<k, v> Node;
public:
private:
Node* _root = nullptr;
};
2.AVL树的插入
1.大概过程
1.按照二叉搜索树的插入逻辑插入节点;
2.插入节点后可能会影响该节点祖先节点的高度,也就是说会影响它们的平衡因子,所以要更新新插入节点->根节点路径上的节点的平衡因子,最坏要更新到根节点,有些情况更新到中途即可停止;
3.更新平衡因子的过程中没有出现问题(所有节点的平衡因子为-1/0/1),则插入结束;
4.更新平衡因子的过程中出现问题(有节点的平衡因子不为-1/0/1),则要对以出现问题的节点为根的子树进行旋转操作,该操作本质上是在调整平衡的过程中降低该子树的高度,不会影响上面的节点,所以插入结束。
2.平衡因子的更新
更新原则:
1.平衡因子 = 左子树高度 - 左子树高度;
2.只有子树高度变化的才会影响当前节点平衡因子;
3.如果节点插入在parent的右子树,这parent的平衡因子++,在左子树则--;
4.以parent为根节点地子树高度是否变化决定了是否需要向上更新平衡因子;
更新停止条件:
1.更新后parent的平衡因子为0 ,也就是说更新过程中parent的平衡因子变化为-1->0或1->0,即原来该子树是一边高一边低,插入新节点后该子树就平衡了,不会影响parent的父节点的平衡因子,更新结束。
2.更新后parent的平衡因子为1/-1 ,也就是说更新过程中parent的平衡因子变化为0->1或着0->-1,即原来该子树是两边高度相同的,插入新节点后该子树的高度必发生改变,因此必影响parent父节点的平衡因子,因此要继续往上更新。
3.更新后parent的平衡因子为2/-2 ,这时候就表示着该节点的平衡因子出问题了,要对以该节点为根节点的子树进行旋转操作;
4.一直更新到根节点,且根节点的平衡因子没有问题,也代表着更新结束。
cpp
bool insert(const pair<k, v>& data)
{
//普通二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(data);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (data.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (data.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_parent = parent;
if (data.first > parent->_kv.first)
parent->_right = cur;
else
parent->_left = cur;
//调整平衡因子
while (parent)
{
if (cur == parent->_right)
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)
{
//旋转
break;
}
else
{
//如果出现意料外的结构,则给个报错
assert(false);
}
}
return true;
}
3.旋转
1.旋转的原则
1.旋转后的树仍要保存搜索树的规则;
2.让旋转的树由不平衡变平衡,其次降低树的高度 ;
旋转分为4中:
左、右单旋,左右、右左双旋。
2.左、右单旋
首先我们要知道,插入元素后要进行旋转的子树 在插入元素前(平衡因子为±1)是长这样的:

对于左边这棵树,只有将元素插入到子树b/c中,才要对以5为根的子树进行旋转;
对于右边这棵树,只有将元素插入到子树a/b中,才要对以10为根的子树进行旋转。
a,b,c的高度>=0。
虽然上面可以让子树发生旋转的选择有两个,当如果只是要进行单旋,只有两种情况:
左边的树:插入在c子树;
右边的树:插入到a子树;
旋转的方法(拿左边的树举例):

虽然看起来逻辑比较简单,当我们需要改动很多节点的各成员指向,比如节点5的parent、right等,这是比较麻烦的。最容易忽略的是,一开始节点5可能是由parent节点的,也可能它就是一整颗树的根节点。
cpp
void RotateL(Node* parent)//左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* Pparent = parent->_parent;
parent->_right = subRL;//将subR的左子树给parent当右子树
subR->_left = parent;//将原来的parent给subL当左子树
if (subRL)
subRL->_parent = parent;//更新subR左子树的父节点,要注意其是否为空
if (parent == _root)
_root = subR;
subR->_parent = Pparent;
if (Pparent && parent == Pparent->_left)
Pparent->_left = subR;
else if(Pparent)
Pparent->_right = subR;
parent->_parent = subR;//将该子树的根节点改为subR;
subR->_bf = 0;
parent->_bf = 0;
}
右单旋也是类似的,不过它是把parent的left的right给parent的left。
cpp
void RotateR(Node* parent)//右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* Pparent = parent->_parent;
parent->_left = subLR;//将subL的右子树给parent当左子树
subL->_right = parent;//将原来的parent给subL当右子树
if (subLR)
subLR->_parent = parent;//更新subL右子树的父节点,要注意其是否为空
if (parent == _root)
_root = subL;
subL->_parent = Pparent;
if (Pparent && Pparent->_left == parent)
Pparent->_left = subL;
else if(Pparent)
Pparent->_right = subL;
parent->_parent = subL;//将该子树的根节点改为subL;
subL->_bf = 0;
parent->_bf = 0;
}
3.左右、右左双旋

对于元素插入位置的选择,上面提到的另外两种情况就要对子树进行双旋:
对于左边的树,元素插入到b树需要对该树进行双旋;
对于右边的树,元素插入到b树需要对该树进行双旋。
那么双旋该怎么做呢?
拿左边的树举例(右左双旋 )说:

简单来说就是对于一开始右边高的子树,先对parent的右子树进行右旋,再对parent进行左旋;对于一开始左边高的树,先对parent的左子树进行左旋,再对parent进行右旋 。
但麻烦的还是接下来的更新平衡因子,对于双旋还会分三种情况。
继续拿左边的树举例,在插入新元素前:

parent和subR和subRL是关键的三个节点,因为只会以它们为根调整树,也就是说在这棵树中,只有它们的平衡因子会改变。
1.对于h == 0 :

旋转结束后三个关键节点的平衡因子都变为了0;
2.对于h >= 1 且 元素插入在b1 :

旋转后:
parent:0
subR:1
subRL:0
2.对于h >= 1 且 元素插入在b2 :

旋转后:
parent:-1
subR:0
subRL:0
因此我们可以写出右左双旋的代码:
cpp
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//只有它们两和parent的平衡因子要改变
//subRL的右子树给subR的左
//subRL的左子树给parent的右
RotateR(parent->_right);//先将父节点的右子树进行右旋,让以parent为根的
//树变成只需单旋的情况
RotateL(parent);//再将该树进行左单旋
//最后要修改平衡因子
if (bf == 0)//此时树中只有两个节点
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 1)//此时新插入的节点再subRL的右子树
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else//理论上来说不会有这种情况,当加上这个判断可以让代码的防御性更强
assert(false);
}
左右双旋的代码也是同理:
cpp
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
assert(false);
}
4.插入代码
cpp
class AVLTree
{
typedef TreeNode<k, v> Node;
public:
bool insert(const pair<k, v>& data)
{
//普通二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(data);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (data.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (data.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_parent = parent;
if (data.first > parent->_kv.first)
parent->_right = cur;
else
parent->_left = cur;
//调整平衡因子
while (parent)
{
if (cur == parent->_right)
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)
{
//旋转
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)//右左双旋
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
{
RotateLR(parent);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateR(Node* parent)//右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* Pparent = parent->_parent;
parent->_left = subLR;//将subL的右子树给parent当左子树
subL->_right = parent;//将原来的parent给subL当右子树
if (subLR)
subLR->_parent = parent;//更新subL右子树的父节点,要注意其是否为空
if (parent == _root)
_root = subL;
subL->_parent = Pparent;
if (Pparent && Pparent->_left == parent)
Pparent->_left = subL;
else if(Pparent)
Pparent->_right = subL;
parent->_parent = subL;//将该子树的根节点改为subL;
subL->_bf = 0;
parent->_bf = 0;
}
void RotateL(Node* parent)//左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* Pparent = parent->_parent;
parent->_right = subRL;//将subR的左子树给parent当右子树
subR->_left = parent;//将原来的parent给subL当左子树
if (subRL)
subRL->_parent = parent;//更新subR左子树的父节点,要注意其是否为空
if (parent == _root)
_root = subR;
subR->_parent = Pparent;
if (Pparent && parent == Pparent->_left)
Pparent->_left = subR;
else if(Pparent)
Pparent->_right = subR;
parent->_parent = subR;//将该子树的根节点改为subR;
subR->_bf = 0;
parent->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//只有它们两和parent的平衡因子要改变
//subRL的右子树给subR的左
//subRL的左子树给parent的右
RotateR(parent->_right);//先将父节点的右子树进行右旋,让以parent为根的
//树变成只需单旋的情况
RotateL(parent);//再将该树进行左单旋
//最后要修改平衡因子
if (bf == 0)//此时树中只有两个节点
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 1)//此时新插入的节点再subRL的右子树
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else//理论上来说不会有这种情况,当加上这个判断可以让代码的防御性更强
assert(false);
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
assert(false);
}
private:
Node* _root = nullptr;
};