Files
Data-Structure/BinaryTree/priorityQueue/maxHBLT/README.MD
2025-08-10 18:16:51 +08:00

278 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 左高树 Height-biased Leftist Tree 合并思路
合并两个左高树Height-biased Leftist Tree, HBLT是其核心操作利用递归和左右子树的`s(x)`通常指null path lengthNPL来保持“左高性质”和堆序。下面给你完整详细步骤和示例代码。
---
## 左高树合并的基本思路
假设有两棵左高树的根节点指针 `h1``h2`,要合并它们,得到合并后的左高树根节点。
### 主要步骤
1. **处理空树情况**
* 如果 `h1` 是空,直接返回 `h2`
* 如果 `h2` 是空,直接返回 `h1`
2. **保证根节点的键值满足最大堆性质**
* 如果是最大左高树Max-Heap`h1` 的键值大于等于 `h2`
*`h1->key < h2->key`,交换 `h1``h2`
3. **递归合并**
*`h2` 合并到 `h1` 的右子树。
* `h1->right = merge(h1->right, h2)`
4. **保持左高性质(左子树的 null path length ≥ 右子树)**
* 如果 `s(h1->left) < s(h1->right)`,交换左右子树指针。
5. **更新当前节点的 s 值**
* `s(h1) = s(h1->right) + 1`
6. 返回新的根 `h1`
---
## 伪代码
```cpp
template<typename T>
struct binaryTreeNode {
std::pair<int, T> data; // 键值和负载
binaryTreeNode *left;
binaryTreeNode *right;
int s; // null path length (NPL)
};
template<typename T>
binaryTreeNode<std::pair<int, T>>* merge(binaryTreeNode<std::pair<int, T>>* h1,
binaryTreeNode<std::pair<int, T>>* h2) {
if (!h1) return h2;
if (!h2) return h1;
// 保证最大堆性质,根值较大
if (h1->data.first < h2->data.first)
std::swap(h1, h2);
// 递归合并 h2 到 h1 的右子树
h1->right = merge(h1->right, h2);
// 保持左高性质
int leftNPL = h1->left ? h1->left->s : 0;
int rightNPL = h1->right ? h1->right->s : 0;
if (leftNPL < rightNPL)
std::swap(h1->left, h1->right);
// 更新当前节点的 s 值
h1->s = (h1->right ? h1->right->s : 0) + 1;
return h1;
}
```
---
## 说明
* `s`null path length定义为从该节点到其最近的空外部节点外部节点视为 `nullptr`)的路径长度。
* 由于左高树特性,左子树的 `s` 不小于右子树的 `s`,合并后通过交换左右子树保证这个性质。
* 这个合并过程保证了合并操作时间复杂度为 O(log n)。
---
# 左高树合并 — 逐步图解(示例:根 50 与 根 45
下面是你给出的两个左高树Max-Leftist的逐步合并图解。我把每一步的递归调用、比较、左右交换和 s 值null path length定义为s(nullptr)=0s(leaf)=1节点 s = right.s + 1都写清楚了。
---
## 初始两棵树与 s 值计算
**Tree A根 50**
```
50(s=2)
/ \
40(s=2) 30(s=1)
/ \
20(s=1) 10(s=1)
```
计算过程:
* s(20)=1, s(10)=1 → s(40)=right.s + 1 = 1 + 1 = 2
* s(30)=1叶子
* s(50)=right.s + 1 = 1 + 1 = 2
**Tree B根 45**
```
45(s=2)
/ \
35(s=2) 25(s=1)
/ \
15(s=1) 5(s=1)
```
计算过程:
* s(15)=1, s(5)=1 → s(35)=1 + 1 = 2
* s(25)=1叶子
* s(45)=right.s + 1 = 1 + 1 = 2
---
## 合并入口:`merge(50, 45)`
* 比较根50 >= 45 → 保留 50 为新根。
* 按算法把 **第二棵树45合并到 50 的右子树**
递归调用: `merge(50,45)` → 需要计算 `50->right = merge(30, 45)`
---
## 递归 1`merge(30, 45)`
* 比较根30 < 45 交换让数值大的作为子树根所以在实现上会把参数交换使 `h1` 指向 45`h2` 指向 30
* 结果 45 为当前子树根继续将 `h2(30)` 合并到 `45->right`
递归调用`merge(45,30)` `45->right = merge(25, 30)`因为 45 的右子树原为 25)。
当前在这一层状态
```
(暂时) 45
/ \
35 25
(将与 30 合并)
```
---
## 递归 2`merge(25, 30)`
* 比较根25 < 30 交换 30 为根25 为待合并树
* 执行 `30->right = merge(30->right, 25)`但原来 30 没有孩子所以 `30->right` `nullptr`
* 所以调用变成 `merge(nullptr, 25)` 直接返回 25
所以在这一步临时构造出
```
30
\
25
```
接下来要维护左高性质保证 s(left) >= s(right)
* 计算 s 值:原来 30 的左子为空left\_s = 0右子为 25right\_s = 1
* 因为 left\_s < right\_s所以**交换左右子树**得到
```
30
/
25
```
* 更新 s(30) = (right ? right.s : 0) + 1 = (nullptr ? 0 : ) + 1 = 0 + 1 = 1
所以 `merge(25,30)` 返回的子树为
```
30(s=1)
/
25(s=1)
```
---
## 回到上层(恢复到 45 的层)
* 把返回的子树作为 `45->right`现在 45 的左右子树为
```
45
/ \
35(s=2) 30(s=1)
/
25(s=1)
```
* 检查并维持左高性质
* left\_s = s(35) = 2
* right\_s = s(30) = 1
* left\_s >= right\_s**不必交换**。
* 更新 s(45) = right.s + 1 = 1 + 1 = 2保持不变
`merge(30,45)` 到此结束,返回的子树根为 45其 s=2
---
## 回到最顶层(恢复到 50 的层)
* 将上一步得到的子树赋为 `50->right`,现在 50 的结构为:
```
50
/ \
40(s=2) 45(s=2)
/ \ / \
20 10 35 30
/\ /
15 5 25
```
为清楚起见35 下保留 15、530 下保留 25
* 检查并维持左高性质:
* left\_s = s(40) = 2
* right\_s = s(45) = 2
* left\_s >= right\_s等于也符合所以**不交换**。
* 更新 s(50) = right.s + 1 = 2 + 1 = 3。
---
## 最终合并结果(图与 s 值)
```
50(s=3)
/ \
40(s=2) 45(s=2)
/ \ / \
20(1)10(1)35(2) 30(1)
/\ /
15(1)5(1)25(1)
```
说明:
* 合并过程中确实发生了你关心的“原来右子树的位置30被另一棵树的节点45/35取代”的情况这很正常合并算法**优先保证堆序(根值最大)**,于是较大的根会在递归中“上移”到合适位置。
* 同时,算法通过「递归合并到右子树」+「必要时交换左右子树」来保持左高性质。最终时间复杂度按树的右路径长度上界(均摊 O(log n))来保证。
---
## 伪代码回顾(便于对应每一步)
```cpp
Node* merge(Node* h1, Node* h2) {
if (!h1) return h2;
if (!h2) return h1;
if (h1->key < h2->key) swap(h1, h2); // 确保 h1 的根更大Max-heap
h1->right = merge(h1->right, h2);
// 保持左高:如果左子 s < 右子 s则交换
if (npl(h1->left) < npl(h1->right)) swap(h1->left, h1->right);
h1->s = npl(h1->right) + 1;
return h1;
}
```
---