一、AVL树的概念
虽然二叉搜索树可以缩短查找的效率,但是如果数据有序或者接近有序,二叉搜索树就会退化成单支树,就相当于在顺序表中查找,这样效率就很低下。
为了解决上面的问题,就引入了一种规划:在向二叉搜索树中插入新节点后,保证节点的左右子树高度差不超过1,经过这样调整就降低树的高度。减少平均搜索时间。
一颗AVL树具有以下性质:
1.它的左右子树都是AVL树
2.左右子树的高度差的绝对值不超过一。
二、AVL树节点的定义。
template<class K, class V>
struct AVLTreeNode
{
struct AVLTreeNode* _left; //左节点
struct AVLTreeNode* _right; //右节点
struct AVLTreeNode* _parent; //双亲节点
pair<K, V> _kv;
int _bf; //平衡因子
AVLTreeNode(const pair<K,V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_bf(0)
{}
};
三、AVL树的插入
AVL树就是在搜索二叉树的基础上引入了平衡因子。
AVL树的插入分为i两部:
1.按照二叉搜索树的规则插入节点。
2.调整平衡因子
在插入新节点后,该节点的父亲的平衡因子需要调整,插入前父节点的平衡因子有三种情况,-1,0,1.
这三种情况可以以归类为两种情况:
1.如果插入到父亲的左侧,只需要给父亲的平衡因子-1即可。
2.如果插入到父亲的右侧,只需要给父亲的平衡因子+1即可。
改完父亲的平衡因子后父亲的平衡因子又可能会出现三种情况:0,1,-2
1.如果父亲的平衡因子是0,那说明插入之前的平衡因子是 ±1 ,插入后被调整为0。
2.如果父亲的平衡因子是 ±1 说明插入之前父亲的平衡因子一定是0,此时就需要继续向上更新。
3.如果父亲的平衡因子是±2,说明父亲的平衡因子是违反AVL树的性质,需要对其进行旋转。
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//更新平衡因子与旋转
while (parent)
{
if (parent->_left == cur)
{
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)
{
//。。。。
}
else
{
assert(false);
}
}
return true;
}
四、AVL树的旋转
根据插入位置的不同,旋转可以分为四种。(以下用抽象图表示)
1.新节点插入较高左子树的左侧:右单旋
插入完成后导致,5节点的平衡因子为-1,需要继续向上更新平衡因子,进而导致6节点需要调整平衡
所以我们需要把左子树往上提,这样6转下来,因为6比5大,只能将其放在5的右子树,而如果5有 右子树,右子树根的值一定大于5,小于6,只能将其放在6的左子树,旋转完成后,更新节点 的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
1. 5节点的右孩子可能存在也可能不存在。
2. 6可能是根节点也可能是子树,如果是子树,也可能是左子树或者右子树。如果是根节点,旋转完成后需要跟新节点。
调整完成后的情况:
void RotateL(node* parent)
{
node* subr = parent->_right;
node* subrl = subr->_left;
parent->_right = subrl;
subr->_left = parent;
if (subrl)
{
subrl->_parent = parent;
}
node* pparent = parent->_parent;
parent->_parent = subr;
if (_root == parent)
{
_root = subr;
subr->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subr;
}
else
{
pparent->_right = subr;
}
subr->_parent = pparent;
}
subr->_bf = parent->_bf = 0;
}
2.新节点插入新节点插入较高右子树的右侧:左单旋
void RotateR(node* parent)
{
node* subl = parent->_left;
node* sublr = subl->_right;
parent->_left = sublr;
if (sublr)
{
sublr->_parent = parent;
}
node* pparent = parent->_parent;
subl->_right = parent;
parent->_parent = subl;
if (_root == parent)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subl;
}
else
{
pparent->_right = subl;
}
subl->_parent = pparent;
}
subl->_bf = parent->_bf = 0;
}
3.新节点插入较高左子树的右侧:先左单旋再右单旋
void RotateLR(node* parent)
{
node* subl = parent->_left;
node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(subl);
RotateR(parent);
if (bf == 0)
{
parent->_bf = subl->_bf = sublr->_bf = 0;
}
else if (bf == 1)
{
subl->_bf = -1;
parent->_bf = 0;
sublr->_bf = 0;
}
else if (bf == -1)
{
subl->_bf = 0;
parent->_bf = 1;
sublr->_bf = 0;
}
else
{
assert(false);
}
}
4. 新节点插入较高右子树的左侧:先右单旋再左单旋
void RotateRL(node* parent)
{
node* subr = parent->_right;
node* subrl = subr->_left;
int bf = subrl->_bf;
RotateR(subr);
RotateL(parent);
if (0 == bf)
{
parent->_bf = subr->_bf = subrl->_bf = 0;
}
else if( -1 == bf)
{
//在subrl的左子树插入
parent->_bf = subrl->_bf = 0;
subr->_bf = 1;
}
else if (1 == bf)
{
//在subrl的右子树插入
subr->_bf = subrl->_bf = 0;
parent->_bf = -1;
}
else
{
assert(false);
}
}
总结:
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑
1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subr
当subr的平衡因子为1时,执行左单旋
当subr的平衡因子为-1时,执行右左双旋
2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subl
当subl的平衡因子为-1是,执行右单旋
当subl的平衡因子为1时,执行左右双旋
旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
五、AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分为两步:
1.每个节点子树的高度差的绝对值不超过1。
2.节点的平衡因子是否计算正确。
int _Hight(node* root)
{
if (root == nullptr)
{
return 0;
}
int lefthight = _Hight(root->_left);
int righthight = _Hight(root->_right);
return lefthight > righthight ? lefthight + 1 : righthight + 1;
}
bool _IsBalance(const node* root)
{
if (root == nullptr)
{
return true;
}
int lefthight = _Hight(root->_left);
int righthight = _Hight(root->_right);
if (righthight - lefthight != root->_bf)
{
cout << root->_kv.first << "当前平衡因子异常" << endl;
return false;
}
return abs(righthight - lefthight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
六、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操 作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。