AVL树是以发现其的两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis的首字母命名的。其实现方法为:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
这里采用三叉链结构定义AVL树结点,即定义一个结点的父亲,左孩子与右孩子。用pair对象存储key、value值,然后定义平衡因子,用于调整AVL树保证平衡稳定性。
template <class K, class V>
struct AVLTreeNode
{
pair<K,V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;//平衡因子,右子树高度-左子树高度
AVLTreeNode(const pair<K,V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
template <class K, class V>
class AVLTree
{
public:
typedef AVLTreeNode<K, V> Node;
AVLTree()
:_root(nullptr)
{}
private:
Node* _root;
}
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
当结点的平衡因子出现异常时,若左子树高度高于右子树高度,那么该结点需要进行右单旋调整。
进行右单旋的条件为:parent的bf为-2且subL的bf为-1。
在此逻辑上,可以得出右单旋的代码:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//改变链接关系
parent->_left = subLR;
subL->_right = parent;
if (subLR)//注意subLR可能为空
subLR->_parent = parent;
Node* ppNode = parent->_parent;
//parent为根
if (parent == _root)
_root = subL;
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
}
parent->_parent = subL;
subL->_parent = ppNode;
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
与右单旋类似的,若左子树高度高于右子树高度,那么该结点需要进行左单旋调整。
进行左单旋的条件为:parent的bf为2且subL的bf为1
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//改变链接关系
parent->_right = subRL;
if (subRL)//注意subRL可能为空
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
if (parent == _root)
_root = subR;
else
{
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
}
subR->_parent = ppNode;
parent->_parent = subR;
//更新平衡因子
subR->_bf = parent->_bf = 0;
}
当插入结点导致AVL树出现如图所示的情况时,需要进行右左双旋。
进行右左双旋的条件为:parent的bf为-2且subL的bf为1.
右左单旋中,需要分清楚平衡因子的变化:
通过画图分析可以知道:
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;//记录下subRL的bf
//先对subR进行一次右单旋,再对parent进行一次左单旋
RotateR(subR);
RotateL(parent);
//1.bf为1
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)//2.bf为-1
{
subR->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)//3.bf为0,subRL为新增结点
{
subR->_bf = parent->_bf = subRL->_bf = 0;
}
else//其他不合理情况,报错
assert(false);
}
同样的下图中的情况也不能只通过一次单旋来调整,而要进行左右双旋。
进行左右双旋的条件为:parent的bf为2且subR的bf为-1.
左右单旋中,需要分清楚平衡因子的变化:
通过画图分析可以知道:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//找到要插入的位置
Node* cur = _root;
Node* parent = _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为要插入的位置
cur = new Node(kv);
cur->_parent = parent;
if (parent->_kv.first > kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_bf = 0;
//更新平衡因子
while (parent)
{
if (parent->_left == cur)
parent->_bf--;
else
parent->_bf++;
if (parent->_bf == 0)//平衡因子更新后,若parent的bf为0,无需继续更新
break;
else if (parent->_bf == 1 || parent->_bf == -1)//若parent的bf为1/-1,则需要继续向上更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)//若parent的bf为2/-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)
{
//左右双旋
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
//右左双旋
RotateRL(parent);
}
//旋转完成,跳出循环
break;
}
else//若parent的bf为其他情况,说明搜索树的平衡已经破坏,报错
assert(false);
}
return true;
}
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1.验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
void InOrder()
{
_InOrder(_root);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
size_t Height()
{
return _Height(_root);
}
bool IsAVLTree()
{
return _IsAVLTree(_root);
}
private:
bool _IsAVLTree(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
cout << "平衡因子异常:" << root->_kv.first << endl;
return abs(rightHeight - leftHeight) < 2
&& _IsAVLTree(root->_left)
&& _IsAVLTree(root->_right);
}
size_t _Height(Node* root)
{
if (root == nullptr)
return 0;
return _Height(root->_left) > _Height(root->_right) ? 1 + _Height(root->_left) : 1 + _Height(root->_right);
}
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2(N) 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。