假设有如下一棵二叉树树,为一棵单枝树,而且在以后的插入操作中由于特殊情况使插入节点比树中任意节点都大,它的这种特性继续保持,那么会发生什么情况?
这棵树将蜕化为链表,它的高度将等于树中节点个数N,所有的查找操作、删除操作时间复杂度变为O(N),而且大部分的储存空间被浪费。对于二叉树,我们期望它的高度是O(logN),这样对于它的绝大多数操作的时间复杂度将保持在O (logN)以内。
为了避免上述情况的发生,我们在二叉树的每个节点上加入平衡因子,规定右子树的高度减去左子树的高度为该树平衡因子。
- 完整实现代码
概述
AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis。
AVL树规定如下:
AVL(Adelson-Velskii 和 Landis)树是带有平衡因子的二叉搜索树,树中所有节点的平衡因子(bf)必须满足
-2 < bf < 2
,即左右子树高度差不超过1。
这样对于一棵树在我们每次插入一个节点后,新树的平衡性可能被破坏,而且只有那些从插入点到跟节点路径上的节点的平衡性会被破坏。当沿着插入点向上更新平衡因子时,我们只需调整第一个不满足平衡性的节点,这一调整将使得整个树仍然满足AVL树的特性
左单旋和右单旋
我们称第一个不满足平衡性的节点为parent
,容易得出parent
的不平衡可能出现以下四种情况:
- 当左子树较高时,在左子树的左侧插入节点。
- 当左子树较高时,在左子树的右侧插入节点。
- 当右子树较高时,在右子树的右侧插入节点。
- 当右子树较高时,在右子树的左侧插入节点。
明显看出,插入一个节点后使得该节点的父节点平衡因子变成“1或-1”,才有可能破坏该树的平衡性。这一点对于AVL树调整的理解相当重要。
我们用旋转处理上述情形。
1. 对于左-左,通过一次右旋可以使其重新平衡(右单旋)。
2. 对于右-右,通过一次左旋可以使其重新平衡(左单旋)。
3. 对于左-右,先右旋再左旋可以使其重新平衡(右左双旋)。
4. 对于右-左,先左旋再右旋可以使其重新平衡(左右双旋)。
容易看出情形1和2、3和4分别是对称的。
下面是旋转的大体图解。
可以想象,对于上图左-左,我们抓着6
那个节点上一提,由于重力原因,右边10
和12
那两个节点将落下来,这样该树左边子树高度减少1,右边子树高度增加1,最重要的是,调整后的树的高度与插入节点前的高度相同,所以,当完成第一次调整后将使得整棵树满足AVL树的特性。我们称这样一次调整为右单旋。
可以观察到,此次旋转中,6
和10
是最关键的两个节点,我们所有的旋转操作都是通过改变这两个节点的指向关系来完成。所以在旋转完成后,它俩的平衡因子也应该更新,而且通过旋转后的情况来看,都应该置0
。
对于右-右的情形采与 左左 如出一辙。
双旋
对于左-右和右-左的情形,我们需要通过两次旋转即左右双旋或右左双旋完成。
在左-右情形的图中,我们先以节点4
为根,进行一次左单旋,旋转完成后树的情况将与左-左相同,接着我们在进行一次右单旋,至此,对于左-右这种情形,我么处理已经完成。
在双旋转完成之后,被旋转的几个节点的平衡因子根据插入位置的不同会有不同。如下图所示:
特殊处理平衡因子
我们注意到新节点被插入到36
的左边时,在旋转完成之后,它仍为该树左子树中的一个节点,这将导致40
那个节点的平衡因子为1
,但是我们在每次单旋完成之后时,将该节点平衡因子置0
了。同理,当新节点被插入到36
的右边时,在旋转完成之后,该节点将跑到根节点的右子树中,所以30
那个节点的平衡因子为-1
, 但我们在每次单旋之后却将其置0
。
所以,针对上面两种情况,我们在双旋斩完成之后,对相关节点的平衡因子作一些处理。
比较好的一种做法解释就是,在旋转之前,保存36
那个节点的平衡因子,若为1
,那么说明新节点被插到该节点的右侧,则旋转之后30
那个节点的平衡因子应被更新为-1
,否则说明新节点被插到左侧,则在旋转之后40
那个节点平衡因子应被更新为1
。
对于右-左,和左右对称,与左右的原理相反。
在搞清楚旋转的原理后,我们只需通过节点平衡因子的情况选择合适的旋转方法即可。
在旋转操作中最主要的内容就是更新各个节点之间的指向关系和它们的平衡因子。下面是实现旋转具体的具体代码:
代码实现
下面是有关旋转的代码:
//节点
struct AVLTreeNode
{
AVLTreeNode(const K& key, const V& value)
: _key(key)
, _value(value)
, _bf(0)
, _pParent(NULL)
, _pLeft(NULL)
, _pRight(NULL)
{}
AVLTreeNode<K, V>* _pParent;
AVLTreeNode<K, V>* _pLeft;
AVLTreeNode<K, V>* _pRight;
K _key;
V _value;
int _bf;
};
//左单旋
void _RotateLeft(Node* pParent)
{
Node* pSubR = pParent->_pRight;//右子树的根
Node* pSubRL = pSubR->_pLeft;//右子树根的左孩子
pParent->_pRight = pSubRL;
if (pSubRL)
pSubRL->_pParent = pParent;
Node* pPParent = pParent->_pParent;//根的父节点
//调整节点指向
if (NULL == pPParent)
_pRoot = pSubR;
else if (pParent == pPParent->_pLeft)
pPParent->_pLeft = pSubR;
else
pPParent->_pRight = pSubR;
pSubR->_pLeft = pParent;
pSubR->_pParent = pPParent;
pParent->_pParent = pSubR;
//注意更新旋转后的平衡因子
pParent->_bf = 0;
pSubR->_bf = 0;
}
//右单旋
void _RotateRight(Node* pParent)
{
Node* pSubL = pParent->_pLeft;//左子树的根
Node* pSubLR = pSubL->_pRight;//左子树根的左孩子
pParent->_pLeft = pSubLR;
if (pSubLR)
pSubLR->_pParent = pParent;
Node* pPParent = pParent->_pParent;//根的父节点
if (NULL == pPParent)
_pRoot = pSubL;
else if (pParent == pPParent->_pLeft)
pPParent->_pLeft = pSubL;
else
pPParent->_pRight = pSubL;
pSubL->_pRight = pParent;
pSubL->_pParent = pPParent;
pParent->_pParent = pSubL;
pParent->_bf = 0;
pSubL->_bf = 0;
}
//左右双旋
void _RotateLR(Node* pParent)
{
//双旋转对平衡因子要作特殊处理
Node* SubL = pParent->_pLeft;
Node* SubLR = SubL->_pRight;
int bf = SubLR->_bf;
_RotateLeft(pParent->_pLeft);
_RotateRight(pParent);
//特殊处理平衡因子
if (1 == bf)
SubL->_bf = -1;
else
pParent->_bf = 1;
}
//右左双旋
void _RotateRL(Node* pParent)
{
//双旋转对平衡因子要作特殊处理
Node* SubR = pParent->_pRight;
Node* SubRL = SubR->_pLeft;
int bf = SubRL->_bf;
_RotateRight(pParent->_pRight);
_RotateLeft(pParent);
//特殊处理平衡因子
if (1 == bf)
pParent->_bf = -1;
else
SubR->_bf = 1;
}
【作者:果冻 http://blog.csdn.net/jelly_9】
——谢谢!