简述
AVL树简单来讲可以说是一种二叉树的变种,它由搜索二叉树变化而来,在AVL树中,任何节点的两个子树的高度最大差别为一,所以它也称为高度平衡树,相比于二叉树,它对于每一个节点赋予了一个新的信息,就是平衡度。平衡度,简单来讲就是一个把一个树的右子树的高度减去左子树的高度。而AVL树则是给其添加了一个限制条件,即平衡度只能为1,0,-1。
AVL树是一种比较经典的平衡二叉搜索树,它规定每个节点的左子树和右子树的高度差最多为1,默认空树的高度为-1,这样能保证树的深度为O(logN),而且除了遍历,赋值,基本上所有树的操作都可以以时间O(logN)执行(当然插入和删除后因为要重新平衡树,可能时间会长于O(logN))。
对于新的AVL树来讲,它是基于二叉搜索树来实现的,所以在很多方面同二叉搜索树很像,但是我们在插入的同时也需要做一下相关的调整,以确保符合AVL树的特性。为什么说需要调整,我们先来看一下这个例子,现在我们有这样一棵树。
红色的数字代表这个节点的平衡度,此时我们可以看到这棵树符合我们的AVL树的规则,接下来我么继续插入一个新的节点80,按照二叉排序树的规则,我们要把他插入到75这个节点的右侧。 此时我们会得到这样一棵树
更新一下平衡度
此时我们再使用条件去判断一下,很明显此时不满足我们的AVL树,这时我们需要对其进行调整。
AVL树的调整一共有四种:
1.左旋
2.右旋
3.先左后右
4.先右后左
接下来我们对这四种调整进行逐一分析:
四种旋转
- 左旋
左旋:顾名思义就是向左旋转,需要左旋的情况是当我们插入的节点位于最右子节点的子节点时需要进行的变动。也就是说如图插入的节点插入到80的左子树或右子树时,我们需要进行左旋。为什么说是左旋呢?因为我们从图中可以看到,我们把节点70抬高使60的节点成为了70的左子树,此时就好像我们将原树进行向左旋转90度一般,其余的各节点以此改变位置,最终达到平衡状态。
代码如下
void rotatepRight(Node * parent)//左旋 parent当前为根节点
{
Node * SubR = parent->_right;
Node * SubRL = SubR->_left;
parent->_right = SubRL;
if(SubRL)
SubRL->_parent = parent;
SubR->_left = parent;
Node * pparent = parent->_parent;
pparent->_parent = SubR;
if(pparent == NULL)
_proot = SubR;
else
{
if (pparent->_left = parent)
pparent->_parent = SubR;
else
pparent->_right = SubR;
}
parent->bf = SubR->bf = 0;
}
注:我们一般把parent设置为平衡度不符合AVL的节点。
2.右旋
同左旋相似,只不过这次的插入节点在左侧也就是说如图插入的节点插入到60的左子树或右子树时,我们需要进行左旋。左旋与右旋同理,我们可以想象把图中的70节点太高,使其左右子树改变从而达到平衡状态。
代码如下
void rotatepLeft(Node * parent)//右旋
{
Node*SubL = parent->_left;
Node*SubLR = SubL->_right;
parent->_right = SubLR;
if(SubLR)//判断75这个节点是否存在
SubLR->_parent = parent;
SubL->_right = parent;
Node*pparent = parent->_parent;
pparent->_parent = SubL;
if(pparent == NULL)
_proot = SubL;
else
{
if (pparent->_right = parent)
pparent->_parent = SubR;
else
pparent->_left = SubR;
}
parent->bf = SubL->bf = 0;
}
这里,我们同样传入的节点是我们的平衡度不满足AVL树的节点。
3.先左后右
相对于上面的两次旋转,我们可能会发现一个问题,那就是我们需要单旋的情况都是插入节点都是右子树最右子树的子节点,或插入左子树最左的子节点。但是我们在实际的操作中还发现有这样的插入
此时我们会发现,不论我们左旋或者右旋,都无法使其平衡,这时我们就需要我们的先左后右的插入方法。
先左后右,也可以说是先左旋后右旋,图中我们对红色方框的部分首先进行左旋,左旋完成后我们会得到倒数第二张图示的树,此时我们恰好发现,我们可以在此使用右旋使其满足AVL树的条件。
代码如下
void _RotateLR_R(Node*& parent)//左右旋转(递归)
{
RotatepLeft(parent->_right);
RotatepRight(parent);
}
void _RotateLR(Node*& parent) //左右旋转(非递归)
{
Node *subL = parent->_left;
Node *subLR = subL->_right;
subL->_right = subLR->_left;
if (subLR->_left != NULL)
{
subLR->_left->_parent = subL;
}
subLR->_left = subL;
subL->_parent = subLR;
//调整平衡因子,右树减去左树
if (subLR->_bf == 0 || subLR->_bf == 1)
{
subL->_bf = 0;
}
else
{
subL->_bf = 1;
}
parent->_left = subLR->_right;
if (subLR->_right != NULL)
{
subLR->_right->_parent = parent;
}
subLR->_right = parent;
subLR->_parent = parent->_parent;
parent->_parent = subLR;
//调整平衡因子,右树减去左树
if (subLR->_bf == 0 || subLR->_bf == 1)
{
parent->_bf = 0;
}
else
{
parent->_bf = 1;
}
parent = subLR;
subLR->_bf = 0;
}
这里我们提供两种思路,一种是采用递归调用,我们直接用我们写过的左旋和右旋对插入数进行调整,但是需要注意的是传入的参数,先左旋传入的就不再是节点不平衡的树了而是我们需要先调整部分的根节点,再右旋时我们需要调整才是我们节点不平衡的树。非递归的调用则不需要考虑该问题,不过相对来讲写起来些许复杂。
4.先右后左。
这里因为同先左后右相似,我们就不再对其进行过多赘述,直接上代码。
void _RotateRL_R(Node*& parent)//右左旋转(递归)
{
_RotateRight(parent->_left);
_RotateLeft(parent);
}
void _RotateRL(Node*& parent)//右左旋转(非递归)
{
Node *subR = parent->_right;
Node *subRL = subR->_left;
subR->_left = subRL->_right;
if (subRL->_right != NULL)
{
subRL->_right->_parent = subR;
}
subRL->_right = subR;
subR->_parent = subRL;
if (subRL->_bf == 0 || subRL->_bf == 1)
{
subR->_bf = 0;
}
else
{
subR->_bf = 1;
}
parent->_right = subRL->_left;
if (subRL->_left != NULL)
{
subRL->_left->_parent = parent;
}
subRL->_left = parent;
subRL->_parent = parent->_parent;
parent->_parent = subRL;
if (subRL->_bf == 0 || subRL->_bf == -1)
{
parent->_bf = 0;
}
else
{
parent->_bf = -1;
}
parent = subRL;
subRL->_bf = 0;
}
void _InOrder(Node *root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void _destroy(Node *root)
{
if (root)
{
_destroy(root->_left);
_destroy(root->_right);
delete root;
root = NULL;
}
}
总结
AVL树给我们提供了一种更加便捷的存储,相对于单独的搜索二叉树来讲各有各的优点和特色。以上内容属于总结内容,如有问题希望指出。