节点的平衡因子是它的右子树的高度减去它的左子树的高度。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
我们再来看看它的效率:
以上,我们便了解了AVL树,那么我们就知道,如果要保持平衡因子的的平衡,从而使整个树保持平衡,是不容易的,那么怎样才能保持平衡呢?
在维基百科中是这样说的,它叫这种插入叫做追溯循环的插入。
我们首先不要关注它为什么叫这个名字,我们先看看它有几种情况
以上便是第一种情况:
<span style="font-size:18px;">void _RotateL(Node* &parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
subR->_left = parent;
subR->_parent = parent->_parent;
parent->_parent = subR;
parent = subR;
parent->bf = 0;
}</span>
<span style="font-size:18px;">void _RotateR(Node* &parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
subL->_right = parent;
subL->_parent = parent->_parent;
parent->_parent = subL;
parent =subL;
parent->bf = 0;
}</span>
这种情况就是先左旋转然后右旋转,所以最初,我是这样写的
<span style="font-size:18px;">void _RotateLR(Node*& parent)
{
</span><pre name="code" class="cpp"><span style="font-size:18px;"> _RotateL(parent);</span>
<pre name="code" class="cpp"><span style="font-size:18px;"> _RotateR(parent);</span>
}
但是,在某种特殊的情况下,它的平衡因子是不对的,所以我采用了,下面的办法补救<span style="font-size:18px;">void _RotateLR(Node*& parent)
{
Node* pNode = parent;
Node* subLNode = parent->_left;
Node* subLRNode = subLNode->_right;
int _bf = subLRNode->bf;
_RotateL(parent->_left);
_RotateR(parent);
if (_bf == -1)
{
subLNode->bf = 0;
pNode->bf = 1;
}
else if (_bf == 1)
{
subLNode->bf = -1;
pNode->bf = 0;
}
else
{
subLNode->bf = 0;
pNode->bf = 0;
}
subLRNode->bf = 0;
}</span>
同理,我也就不把最初的代码给出来了,直接给新的
<span style="font-size:18px;">void _RotateRL(Node*& parent)
{
Node* pNode = parent;
Node* subRNode = parent->_right;
Node* subRLNode = subRNode->_left;
int _bf = subRLNode->bf;
_RotateR(parent->_right);
_RotateL(parent);
if (_bf == 1)
{
subRNode->bf = 0;
pNode->bf = -1;
}
else if (_bf == -1)
{
subRNode->bf = 1;
pNode->bf = 0;
}
else
{
subRNode->bf = 0;
pNode->bf = 0;
}
subRLNode->bf = 0;
}</span>
让节点D是我们要删除的节点,并让树中的节点E是一个节点我们需要找到节点D的位置,让节点N是我们拿出实际的节点树。
步骤时要考虑删除一个节点在一个AVL树如下:
1.如果节点D是一片叶子或只有一个孩子,跳到步骤6 N:= D、G D和的父母 dir 孩子的方向在G D。
2.否则,确定E的遍历的节点最左边的(最小) [9] D节点的右子树(D的顺序继承人−没有左子),或者最右边的在它的左子树(D的顺序的前任没有右子)。
3.记得节点G,E的父母。
4.提供节点E与所有节点D的孩子和家长联系,让节点E所有孩子和家长联系以前的新目标指向D在这一步中,节点之间的顺序序列D和E是暂时的干扰,但树结构并没有改变。
5.让N:= E, dir :=左)和F(左)孩子的E。
6.代替N在孩子的位置 dir 由其孩子F,G是null或叶。 在后一种情况下设置其母G。
(现在的最后痕迹D已经从树中删除,所以D可能从内存中删除。)
7.如果节点N根(其母G是null),更新根。
8.Otherweise的高度 dir 子树G已经下降了1,从1到0或从2比1。 所以,让X:= G和N dir 孩子在下面的代码块(X),为了追溯路径备份根的树,从而调整平衡因素(包括可能的旋转)。
因为与单个删除AVL子树的高度不能减少一个以上的,暂时的平衡因素的一个节点将从−2 + 2。
如果平衡因素成为±2子树是不平衡的,需要旋转。 介绍了各种情况下的旋转部分 再平衡 。
通过删除节点N X的子树N的高度已经下降了1,从1到0或从2比1。
不变的追溯循环删除
由N棵子树的高度已经下降了1。 它已经在AVL形状。
<span style="font-size:18px;">for (X = parent(N); X != null; X = G) { // Loop (possibly up to the root)
G = parent(X); // Save parent of X around rotations
// BalanceFactor(X) has not yet been updated!
if (N == left_child(X)) { // the left subtree decreases
if (BalanceFactor(X) > 0) { // X is right-heavy
// ===> the temporary BalanceFactor(X) == +2
// ===> rebalancing is required.
Z = right_child(X); // Sibling of N (higher by 2)
b = BalanceFactor(Z);
if (b < 0) // Right Left Case (see figure 5)
N = rotate_RightLeft(X,Z); // Double rotation: Right(Z) then Left(X)
else // Right Right Case (see figure 4)
N = rotate_Left(X,Z); // Single rotation Left(X)
// After rotation adapt parent link
}
else {
if (BalanceFactor(X) == 0) {
BalanceFactor(X) = +1; // N’s height decrease is absorbed at X.
break; // Leave the loop
}
N = X;
BalanceFactor(N) = 0; // Height(N) decreases by 1
continue;
}
}
else { // (N == right_child(X)): The right subtree decreases
if (BalanceFactor(X) < 0) { // X is left-heavy
// ===> the temporary BalanceFactor(X) == –2
// ===> rebalancing is required.
Z = left_child(X); // Sibling of N (higher by 2)
b = BalanceFactor(Z);
if (b > 0) // Left Right Case
N = rotate_LeftRight(X,Z); // Double rotation: Left(Z) then Right(X)
else // Left Left Case
N = rotate_Right(X,Z); // Single rotation Right(X)
// After rotation adapt parent link
}
else {
if (BalanceFactor(X) == 0) {
BalanceFactor(X) = –1; // N’s height decrease is absorbed at X.
break; // Leave the loop
}
N = X;
BalanceFactor(N) = 0; // Height(N) decreases by 1
continue;
}
}
// After a rotation adapt parent link:
// N is the new root of the rotated subtree
parent(N) = G;
if (G != null) {
if (X == left_child(G))
left_child(G) = N;
else
right_child(G) = N;
if (b == 0)
break; // Height does not change: Leave the loop
}
else {
tree->root = N; // N is the new root of the total tree
continue;
}
// Height(N) decreases by 1 (== old Height(X)-1)
}
// Unless loop is left via break, the height of the total tree decreases by 1.</span>
追溯可以停止如果平衡因素变成了±1意味着子树的高度保持不变。如果平衡因素变成了0,那么子树的高度降低,追溯需要继续。
如果暂时的平衡因素成为±2,这是修复了一个适当的旋转。 这取决于的平衡因子兄弟Z(更高的子树)是否子树的高度减少一个或不会改变(后者,如果Z因子平衡0)。
所需的时间是 O(log n ) 查找,再加上最多 O(log n ) 追溯水平( O(1) 平均)回到根,所以可以完成操作 O(log n ) 时间。
(ps:因为删除逻辑太多,所以以上是维基百科上的,网读者见谅)
AVL树和红黑树都是自平衡二叉搜索树,他们在数学上非常相似。 平衡树的操作不同,但平均发生 O(1) 最大的 O(log n ) 。 真正的两者之间的区别是极限高度。
树的大小 n ≥1
AVL树比“红-黑”树更严格的平衡,导致更快的检索,但缓慢的插入和删除。
完整的代码在点击打开链接