一、AVL树的概念
二叉搜索树在一定程度上可以提高搜索效率,但是当序列是有序时,此时二叉搜索树退化成单链表,搜索效率退化为O(N)。为了解决这个问题科学家引入了AVL树,又称平衡搜索二叉树
AVL简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
- 平衡因子 = 右子树高度 - 左子树高度
二、AVL树结构定义
template<class K,class V>
struct AVLTreeNode {
AVLTreeNode(const pair<K, V>& kv=pair<K,V>())
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_bf(0)
{}
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V>_kv;
int _bf;
};
template<class K, class V>
class AVLTree {
typedef AVLTreeNode<K, V> Node;
private:
Node* _root = nullptr;
public:
//成员函数
//...
}
三、AVL树节点的插入
AVL树的节点插入可以分为三个步骤:
- 新节点的插入
- 平衡因子的更新
- 通过平衡因子确定平衡性是否被打破,若平衡性被打破,进行旋转
3.1节点的插入
新节点的插入步骤和普通二叉搜索树的插入步骤一样,找到插入的位置,直接插入即可!
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
parent = cur;
if (cur->_kv.first < kv.first)
cur = cur->_right;
else if (cur->_kv.first > kv.first)
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;
//先更新平衡因子
//...
//平衡因子更新后,判断是否需要旋转
return true;//插入成功返回true
}
更新平衡因子并判断是否调整
while (parent)
{
if (parent->_left == cur)//更新平衡因子
parent->_bf--;
else
parent->_bf++;
if (parent->_bf == 0)//在插入前为AVL树,实际上就是判断是否改变了以该节点为根节点的树的高度
{
break;
return true;
}
else if (parent->_bf == 1 || parent->_bf == -1)//之前_bf==0改变高度,继续向上调整
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)//违反规则,旋转后高度与插入前相同,结束调整
{
if (parent->_bf == 2)
{
if (cur->_bf == 1)
RotateL(parent);//左旋
else
RotateRL(parent);//左右双旋
}
else
{
if (cur->_bf == -1)
RotateR(parent);//右旋
else
RotateLR(parent);//右左双旋
}
break;
}
else
{
assert(false);//在插入前如果是正确的AVL树则不会进入此处
}
}
四、旋转
AVL树的旋转:
- 左单旋
- 右单旋
- 左右双旋
- 右左双旋
4.1左单旋
左单旋基本步骤
(1)节点的右孩子替代此节点位置
(2)右孩子的左子树变为该节点的右子树
(3)节点本身变为右孩子的左子树
代码如下
void RotateL(Node* parent)//条件:parent->_bf==2&&cur->_bf==1(在图中cur是parent的右孩子)
{
Node* pparent = parent->_parent;
Node* cur = parent->_right;
parent->_right = cur->_left;
cur->_left = parent;
if (pparent == nullptr)//判断parent是否为根节点
_root = cur;
else
{
if (pparent->_left == parent)
pparent->_left = cur;
else
pparent->_right = cur;
}
cur->_parent = parent->_parent;
parent->_parent = cur;
parent->_bf = cur->_bf = 0;//修改平衡因子
}
4.2右单旋
和左单旋类似
右旋的基本步骤:
(1)节点的左孩子代表此节点
(2)节点的左孩子的右子树变为节点的左子树
(3)将此节点作为左孩子节点的右子树。
void RotateR(Node* parent)//条件:parent->_bf==-2&&cur->_bf==-1(cur为parent左孩子)
{
Node* pparent = parent->_parent;
Node* cur = parent->_left;
parent->_left = cur->_right;
cur->_right = parent;
if (pparent == nullptr)
_root = cur;
else
{
if (pparent->_left == parent)
pparent->_left = cur;
else
pparent->_right = cur;
}
cur->_parent = parent->_parent;
parent->_parent = cur;
parent->_bf = cur->_bf = 0;
}
4.3左右双旋
在如上情况下无论怎么单旋都不能解决问题 故引入双旋
由于最终四颗子树的位置都固定,更新的平衡因子只取决于subLR的平衡因子,故分为三种情况处理
void RotateLR(Node* parent)//条件:parent->_bf == -2 && subL->_bf == 1(subL为parent左孩子)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
4.4右左双旋
右左双旋其实就是左右双旋的一个镜像,搞明白了左右双旋,右左双旋直接画一下图,总结一下三种情况的平衡因子就行了.
void RotateRL(Node* parent)//条件:parent->_bf == 2 && cur->_bf == -1(cur为parent的右孩子)
{
Node* cur = parent->_right;
Node* curL = cur->_left;
int bf = curL->_bf;
RotateR(cur);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curL->_bf = 0;
}
else
{
assert(false);
}
}