简介
AVL树本质上是一颗二叉搜索树,但是它和普通的二叉搜索树不同的是它的每一个结点的两个子树的高度差不超过一,所以AVL树也叫做平衡二叉树,如果删除或者插入一个结点使其高度差变化,就要对其进行旋转再使它平衡。这样就解决了二叉搜索树链表化后(类似下图第三种情况)中各类操作中的时间复杂度的提高的状况。
结构定义
和普通的二叉搜索树一样,AVL树的结点中包含左右孩子结点,key值与value值,不同的是AVL树中为了方便旋转还添加了_parent(父节点),最后还有最重要的 _bf(平衡因子:右子树高度-左子树高度),用来判断AVL是否平衡。当平衡因子大于等于二的时候就说明该结点不平衡需要旋转操作。
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
K _key;
V _value;
int _bf;
AVLTreeNode(const K& key, const V& value)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
,_value(value)
,_bf(0)
{}
};
AVL树的旋转
旋转操作是AVL树在插入或者删除操作后要维持其平衡的一种手段。对于一个平衡的结点(-1<=_bf<=1)来说,由于任意的结点最多有两个孩子,当插入或者删除一个结点之后,其高度差有可能会变为2或者-2,此时失去平衡。具体可分为如下几种情况:
第一种和第二种情况为对称的两种情况,以右旋为例,如下图:
以插入一个结点举例,当d结点为插入结点时,插入后发现a的_bf变为-2,AVL树失去平衡,而a的左子树b结点的平衡因子为-1,此时整棵树就应该向右旋转即顺时针旋转。
具体操作即让b作为根结点同时也是a的父结点,a下移称为b的右孩子,e结点按照二叉搜索树的性质,key值比b的key值大又比a的key值小,所以把他放在a的左孩子。
代码实现
void RotateR(Node* parent)
{//右旋
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = NULL;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
第三种情况和第四种情况也为对称,以左右旋转为例,如下图:
还是以插入一个结点为例,当插入结点在d位置时,发现a的_bf为-2,而其左孩子的 _bf为1,这时我们发现单次的旋转无法达到平衡,要经过两次旋转。
具体操作即先以b为根结点进行左旋,使e成为b的父结点,再根据二叉搜索树的性质,使d成为b结点的右孩子。旋转完成后则变成了第一种情况,再进行一次右旋转,最后得到平衡树。
void RotateLR(Node* parent)
{//左右
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
}
else
{
parent->_bf = subL->_bf = 0;
}
subLR->_bf = 0;
}
AVL树的插入操作
AVL树在插入的时候与二叉搜索树相同,区别是在插入之后需要更新平衡因子,如果插入后AVL树失衡,则要进行相应的旋转来保持AVL树的平衡。
bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* parent = NULL;
Node* cur = _root;
while(cur)
{//找到要插入结点的父节点位置
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key>cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//插入新结点
cur = new Node(key, value);
if (key < parent->_key)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
while (parent)
{//调整平衡因子
if (parent->_left == cur)
parent->_bf -= 1;
else
parent->_bf += 1;
if (parent->_bf == 0)
break;
else if (parent->_bf == 1
|| parent->_bf == -1)
{//父结点的平衡因子变化,继续向上更新
cur = parent;
parent = cur->_parent;
}
else if (parent->_bf == 2
||parent->_bf ==-2)
{
if (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;
}
}
}
}
AVL树删除结点
其实AVL树的删除操作与二叉搜索树基本相同,区别是AVL树在删除后,要从其真正删除结点的父结点向上调整平衡。并且要注意在删除操作中时刻调整每个结点的平衡因子。
如图:假定要删除的结点为红色结点,而在二叉搜索树的删除操作中,是把蓝色结点替换掉红色结点,真正删除的是蓝色结点,所以此时就应该从黄色结点的位置向上一层层调整平衡。
bool Remove(const K& key)
{
return _Remove(_root,key);
}
bool _Remove(Node* root,const K& key)
{//与二叉搜索树的删除基本相同
if (root == NULL)
return false;
if (key < root->_key)
{
root->_bf += 1;
return _Remove(root->_left, key);
}
else if (key>root->_key)
{
root->_bf -= 1;
return _Remove(root->_right, key);
}
else
{
Node* del = root;
if (root->_left == NULL)
{
root->_right->_parent = root->_parent;
root = root->_right;
}
else if (root->_right == NULL)
{
root->_left->_parent = root->_parent;
root = root->_left;
}
else
{
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
root->_key = subLeft->_key;
root->_value = subLeft->_value;
del = subLeft;
subLeft = subLeft->_right;
while (del->_parent)
{//调整平衡,从要被真正delete的那个结点开始向上调整
if (del->_parent->_bf == 2
|| del->_parent->_bf == -2)
{
if (del->_parent->_bf == 2)
{
if (del->_bf == 1)
RotateL(del->_parent);
else
RotateRL(del->_parent);
}
else
{
if (del->_parent->_bf == -2)
{
if (del->_bf == 1)
RotateR(del->_parent);
else
RotateLR(del->_parent);
}
}
}
else
return true;
}
}
delete del;
return true;
}
}
判断这棵树是否为AVL树
在这里我们给出了两种方式来判断,第一种方式是通过求树的每个结点左右孩子的高度来计算平衡因子。
bool IsBalance()
{//判断是否平衡
return _IsBalance(_root);
}
size_t _Depth(Node* root)
{//求深度
if (root == NULL)
return 0;
size_t leftDepth = _Depth(root->_left);
size_t rightDepth = _Depth(root->_right);
return leftDepth < rightDepth ? rightDepth+1 : leftDepth+1;
}
bool _IsBalance(Node* root)
{//时间复杂度O(N²)
if (root == NULL)
return true;
size_t leftH = _Depth(root->_left);
size_t rightH = _Depth(root->_right);
if ((rightH - leftH) != root->_bf)
{
cout << "平衡因子异常:" << root->_key << endl;
}
return abs(rightH - leftH) <= 1
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
第二种是直接在寻找每个节点左右叶结点的同时计算其平衡因子。这样大大的提高了效率。使时间复杂度大大减小。
bool IsBalanceOP()
{//判断是否平衡
size_t depth= 0;
return _IsBalanceOP(_root,depth);
}
bool _IsBalanceOP(Node* root, size_t& depth)
{//时间复杂度O(N)
if (root == NULL)
{
depth = 0;
return true;
}
size_t leftDepth, rightDepth;
if (_IsBalanceOP(root->_left, leftDepth)
&& _IsBalanceOP(root->_right, rightDepth))
{
depth = leftDepth < rightDepth ? rightDepth + 1 : leftDepth + 1;
return abs(leftDepth - rightDepth) < 2;
}
}