Step 4: Rebalance

本文详细介绍了在AVL树中进行节点删除后如何通过旋转操作来重新平衡树的具体步骤,并提供了两种不同情况下的代码实现。此外,还讨论了可能需要在每一级上进行平衡调整的树形状示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Step 4: Rebalance

Now we have to write code to rebalance when it becomes necessary. We'll use rotations to do this, as before. Again, we'll distinguish the cases on the basis of x's balance factor, where x is y's right child:

 

173. <Step 4: Rebalance after AVL deletion 173> =
struct avl_node *x = y->avl_link[1];
if (x->avl_balance == -1)
  { 
    <Left-side rebalancing case 1 in AVL deletion 174>
  } else
  {
    <Left-side rebalancing case 2 in AVL deletion 175>
  }

This code is included in 172.

Case 1: x has - balance factor

If x has a - balance factor, we handle rebalancing in a manner analogous to case 2 for insertion. In fact, we reuse the code. We rotate right at x, then left at y. w is the left child of x. The two rotations look like this:

[Click here for plain-text rendering]

174. <Left-side rebalancing case 1 in AVL deletion 174> =
struct avl_node *w;
<Rotate right at x then left at y in AVL tree 159>
pa[k - 1]->avl_link[da[k - 1]] = w;
image

This code is included in 173.

Case 2: x has + or 0 balance factor

When x's balance factor is +, the needed treatment is analogous to Case 1 for insertion. We simply rotate left at y and update the pointer to the subtree, then update balance factors. The deletion and rebalancing then look like this:

[Click here for plain-text rendering]

When x's balance factor is 0, we perform the same rotation, but the height of the overall subtree does not change, so we're done and can exit the loop with break. Here's what the deletion and rebalancing look like for this subcase:

[Click here for plain-text rendering]

175. <Left-side rebalancing case 2 in AVL deletion 175> =
y->avl_link[1] = x->avl_link[0];
x->avl_link[0] = y;
pa[k - 1]->avl_link[da[k - 1]] = x;
if (x->avl_balance == 0) 
  { x->avl_balance = -1; y->avl_balance = +1; break; } else
  x->avl_balance = y->avl_balance = 0;

This code is included in 173.

image

Exercises:

1. In <Step 4: Rebalance after AVL deletion 173>, we refer to fields in x, the right child of y, without checking that y has a non-null right child. Why can we assume that node x is non-null? [answer]

A: Tree y started out with a + balance factor, meaning that its right subtree is taller than its left. So, even if y's left subtree had height 0, its right subtree has at least height 1, meaning that y must have at least one right child.

2. Describe the shape of a tree that might require rebalancing at every level above a particular node. Give an example. [answer]
A:

Rebalancing is required at each level if, at every level of the tree, the deletion causes a +2 or -2 balance factor at a node p while there is a +1 or -1 balance factor at p's child opposite the deletion.

For example, consider the AVL tree below:

[Click here for plain-text rendering]

Deletion of node 32 in this tree leads to a -2 balance factor on the left side of node 31, causing a right rotation at node 31. This shortens the right subtree of node 28, causing it to have a -2 balance factor, leading to a right rotation there. This shortens the right subtree of node 20, causing it to have a -2 balance factor, forcing a right rotation there, too. Here is the final tree:

[Click here for plain-text rendering]

Incidentally, our original tree was an example of a “Fibonacci tree”, a kind of binary tree whose form is defined recursively, as follows. A Fibonacci tree of order 0 is an empty tree and a Fibonacci tree of order 1 is a single node. A Fibonacci tree of order n >= 2 is a node whose left subtree is a Fibonacci tree of order n - 1 and whose right subtree is a Fibonacci tree of order n - 2. Our example is a Fibonacci tree of order 7. Any big-enough Fibonacci tree will exhibit this pathological behavior upon AVL deletion of its maximum node.

<think>好的,用户提供的代码在输出时出现了多余的空行问题。首先,我需要仔细查看用户提供的代码片段,找出问题所在。 用户提到,输出结果中最后一行多了一个空格。观察代码中的输出部分: ```cpp for (int v : in) std::cout << v << " "; std::cout << std::endl << std::endl; for (int v : pre) std::cout << v << " "; std::cout << std::endl << std::endl; ``` 这里,每次输出`in`和`pre`数组后,都添加了两个换行符`std::endl`,这会导致每个数组输出后有两个空行。但用户希望最后一行的后面没有多余的空行。 接下来,我需要考虑如何调整换行符的数量。可能的解决方案是,在最后一个数组输出后,只添加一个换行符。但是,用户可能有多个测试用例,每个用例需要输出两组数据,每组后各有一个空行分隔。因此,需要确保每组输出之间有一个空行,而最后一行之后没有多余的。 例如,在每组输出中,`in`数组输出后换行两次,`pre`数组同样处理,这样会导致每组之间有两个空行。但用户示例中的Case 1、2、3之间似乎没有多个空行,因此可能需要调整。 进一步分析用户给出的示例输出,发现每个Case的输出块之间可能只有一个空行。因此,正确的做法可能是:每组`in`和`pre`输出后各跟一个换行,而整个Case结束后再添加一个换行。但需要根据用户的具体需求调整。 可能的优化方法是将`std::endl << std::endl`改为仅一个`std::endl`,或者在输出最后一个元素时不添加空格。但问题可能出在每次循环都输出`v << " "`,导致最后一个元素后面也有空格,随后换行两次,出现多余的空行。 另一个思路是使用条件判断,仅在元素不是最后一个时添加空格。例如: ```cpp if (!in.empty()) { for (size_t i = 0; i < in.size(); ++i) { std::cout << in[i]; if (i != in.size() - 1) std::cout << " "; } } std::cout << std::endl; // 类似处理pre数组 ``` 这样可以避免末尾的空格,但用户的问题主要是换行符过多。因此,将两个`std::endl`减少为一个,可能解决多余的空行问题。例如,将`std::cout << std::endl << std::endl;`改为`std::cout << std::endl;`,这样每个数组输出后只换行一次,而Case之间可能通过其他方式分隔。 根据用户提供的案例,例如Case 1的输出: ``` 4 8 9 11 20 24 25 27 31 35 40 20 8 4 11 9 31 25 24 27 35 40 ``` 每个数组后有一个空行,但用户希望最后一行没有多余的空行。可能问题出在代码在所有Case结束后多输出一个空行。因此,需要确保最后一个Case的输出后没有额外的换行。 可能的解决方法是,在输出完所有Case后,检查是否需要换行。或者,调整每个Case内部的换行次数。例如,将每组输出的两个换行改为一个,并在每个Case结束后再添加一个换行。 综上,正确的优化应该是将每个数组后的两个换行符减少为一个,这样每组输出之间有一个空行,而最后一行不会多出空行。例如,修改代码为: ```cpp for (int v : in) std::cout << v << " "; std::cout << std::endl; for (int v : pre) std::cout << v << " "; std::cout << std::endl << std::endl; ``` 这样,每组中的`in`和`pre`各占一行,然后两个数组之间有一个空行。但根据用户示例,每组输出后似乎有两个数组行,然后一个空行分隔下一个Case。可能需要根据具体情况调整。 最终,正确的做法是确保每个Case的`in`和`pre`各输出一行,然后输出一个空行分隔下一个Case。而最后一个Case后不需要空行。但根据用户提供的代码,可能无法知道是否有多个Case,因此最简单的调整是将每个数组后的两个换行改为一个,并在最后去掉多余的换行。 因此,修改后的代码应为: ```cpp for (int v : in) std::cout << v << " "; std::cout << std::endl; for (int v : pre) std::cout << v << " "; std::cout << std::endl; ``` 这样,每个数组输出后换行一次,两组数组之间没有额外空行,而每个Case的输出块之间可能需要添加一个空行。但根据用户提供的案例,可能每个Case的`in`和`pre`之间应有一个空行,而Case之间也有一个空行。需要进一步明确用户需求。 根据用户示例中的Case 1输出: ``` 4 8 9 11 20 24 25 27 31 35 40 20 8 4 11 9 31 25 24 27 35 40 ``` 这里,`in`数组输出后有一个空行,接着`pre`数组输出后有一个空行。而用户希望最后一行的后面没有多余的空行。可能问题在于每个Case输出后多了一个空行,因此需要仅在非最后一个Case时添加空行。 但由于代码可能无法知道是否是最后一个Case,另一种方法是调整每个Case内部的输出,使每个Case的`in`和`pre`之间有一个空行,而不同Case之间也通过一个空行分隔。例如,修改代码为: ```cpp // 输出in数组 for (int v : in) std::cout << v << " "; std::cout << std::endl; // 输出pre数组 for (int v : pre) std::cout << v << " "; std::cout << std::endl; // 添加一个空行分隔下一个Case std::cout << std::endl; ``` 但这样会导致最后一个Case后多出一个空行。因此,更好的方法是在每个Case后添加一个空行,除了最后一个Case。这需要在代码中控制,例如使用一个标记变量。 假设用户代码的结构是循环处理每个Case,可以在循环内部处理: ```cpp bool first = true; for each case { if (!first) { std::cout << std::endl; } first = false; // 输出in和pre数组,每个数组后换行一次 for (int v : in) std::cout << v << " "; std::cout << std::endl; for (int v : pre) std::cout << v << " "; std::cout << std::endl; } ``` 这样,每个Case之间有一个空行,而最后一个Case后没有空行。但用户提供的原始代码可能没有这样的结构,因此需要根据上下文调整。 由于用户提供的代码片段可能是在每个Case处理完后输出`in`和`pre`数组,并在之后添加两个换行,导致多余的空行。因此,最简单的解决方法是减少换行次数,将每个数组后的两个换行改为一个。 修改后的代码应为: ```cpp for (int v : in) std::cout << v << " "; std::cout << std::endl; // 仅一个换行 for (int v : pre) std::cout << v << " "; std::cout << std::endl; // 仅一个换行 ``` 这样,每个数组输出后换行一次,整个Case的输出结束后,`in`和`pre`各占一行,没有多余的空行。但根据用户提供的示例,每个Case的输出块之间可能有一个空行,例如Case 1和Case 2之间有空行。因此,可能需要在每个Case的输出后添加一个换行,但最后一个Case后不添加。这需要更复杂的逻辑,但用户提供的代码可能无法判断是否是最后一个Case。 因此,综合考虑,用户的问题可能出在每次输出数组后都添加了两个换行符,导致每个数组后多了一个空行。因此,优化后的代码应减少换行次数,确保每个数组后只换行一次。</think>### 代码优化方案 问题出现在每次输出数组后连续使用两个`std::endl`,导致多出额外空行。以下是修复后的代码: ```cpp // 修改前(产生多余空行): for (int v : in) std::cout << v << " "; std::cout << std::endl << std::endl; // 连续两个换行 for (int v : pre) std::cout << v << " "; std::cout << std::endl << std::endl; // 连续两个换行 // 修改后(消除多余空行): for (int v : in) std::cout << v << " "; std::cout << std::endl; // 仅一个换行 for (int v : pre) std::cout << v << " "; std::cout << std::endl; // 仅一个换行 ``` ### 优化说明 1. **减少换行次数**:将每个数组输出后的两个`std::endl`改为一个,避免生成多余空行 2. **保留必要分隔**:通过单个换行符保证不同数组的分隔,例如: ``` 4 8 9 11 20 24 25 27 31 35 40 20 8 4 11 9 31 25 24 27 35 40 ``` 而非原代码生成的: ``` 4 8 9 11 20 24 25 27 31 35 40 20 8 4 11 9 31 25 24 27 35 40 ``` ### 改进后输出效果 **Case 1** 输出示例: ``` 4 8 9 11 20 24 25 27 31 35 40 20 8 4 11 9 31 25 24 27 35 40 ``` **Case 3** 输出示例: ``` 5 6 8 6 5 8 ``` ### 相关技术点 这种输出控制方式在树结构遍历输出中很常见。例如在AVL树的中序遍历输出时,需要精确控制格式以保证结果可读性[^2]。类似地,在二叉搜索树的旋转操作中,输出格式的准确性有助于验证旋转后结构的正确性[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值