📅  最后修改于: 2023-12-03 15:09:41.510000             🧑  作者: Mango
左派树和左派堆是两种常见的堆的数据结构,常常用于优先队列的实现。
左派堆是一种可以迅速合并的堆,它可以在 $O(\log n)$ 的时间复杂度下合并两个堆,也可以在 $O(\log n)$ 的时间内插入元素和删除堆顶。
左派树实现了左式堆,也是一种可以迅速合并的优先队列。
左派堆是一种可以迅速合并的堆,它基于一个特殊的性质:左儿子的距离一定大于等于右儿子的距离(即左儿子一定比较“左”)。这个性质使得左派堆在合并的时候可以利用一些 clever 的技巧来达到 $O(\log n)$ 的合并时间复杂度。
左派堆通常由一个树状结构组成,其中每个节点都有以下三个成员:
val
:节点的值。dist
:节点的距离值。left
和 right
:左右儿子。为了方便描述,假设现在有两个左派堆 $H_1$ 和 $H_2$,我们需要将它们合并成一个新的左派堆 $H$。
当 $H_1$ 或 $H_2$ 为空堆时,直接返回另一个非空堆即可。
否则,根据左派堆的性质,我们需要让 $H$ 的根节点为 $H_1$ 和 $H_2$ 中距离值更小的那个节点。具体做法是将 $H_1$ 和 $H_2$ 的根节点分别作为新堆 $H$ 的左右儿子,然后再递归地合并 $H$ 的两个儿子得到新的 $H$。
struct Node {
int val, dist;
Node *left, *right;
Node(int x) : val(x), dist(0), left(nullptr), right(nullptr) {}
};
Node* merge(Node* h1, Node* h2) {
// 空堆情况
if (h1 == nullptr) return h2;
if (h2 == nullptr) return h1;
// 让 h1 为距离较小的堆
if (h1->val > h2->val) swap(h1, h2);
// 将 h1->right 和 h2 合并到新堆 h1->right 上
h1->right = merge(h1->right, h2);
// 令 h1 的左儿子更加“左”
if (h1->left == nullptr || h1->left->dist < h1->right->dist) swap(h1->left, h1->right);
// 更新距离值
h1->dist = h1->right == nullptr ? 0 : h1->right->dist + 1;
return h1;
}
插入操作相对简单,只需要将插入的元素看作一个单节点左派堆,然后将其与当前堆合并即可。
Node* insert(Node* h, int x) {
auto node = new Node(x);
return merge(h, node);
}
删除操作比较复杂,但基本思路和合并是一样的。我们需要将根节点删去,然后将其左右儿子合并成一个新的堆。
Node* remove(Node* h) {
auto left = h->left;
auto right = h->right;
delete h;
return merge(left, right);
}
左派树是一种实现了左式堆的数据结构。它可以完成左派堆的所有操作(包括合并、插入和删除),并且在保证合并的时间复杂度 $O(\log n)$ 的同时,可以更灵活地应对优先队列的各种需求。
左派树同样由一个树状结构组成,其中每个节点也有以下三个成员:
val
:节点的值。left
和 right
:左右子树。dist
:节点的 siftdown 距离。左派树和左派堆的堆操作大致相同,只是堆节点的结构发生了一些变化。
插入操作和左派堆非常相似,只需要插入一个新的节点,然后尝试将其左右子树用 siftdown 方法调整成“左倾树”即可。
node* insert(Node* root, int x) {
auto node = new Node(x);
return merge(root, node);
}
删除操作和左派堆类似,只需要将根节点删除,然后将其左右儿子合并成一个新的堆。
Node* remove(Node* root) {
return merge(root->left, root->right);
}
合并操作是左派树最核心的操作之一,它主要利用了左派树的一些特殊性质。
具体做法是将左右两棵子树递归地合并,然后根据节点的 siftdown 距离和节点的大小进行一些 clever 的旋转操作,使得节点更加倾向于左子树的位置。
node* merge(Node* h1, Node* h2) {
// 同左派堆
if (h1 == nullptr) return h2;
if (h2 == nullptr) return h1;
// 让 h1 始终是较小堆
if (h1->val > h2->val) swap(h1, h2);
// 递归地合并 h1 和 h2 的子树
auto right = merge(h1->right, h2);
h1->right = right;
// 旋转节点
if (h1->left == nullptr || h1->left->dist < h1->right->dist) swap(h1->left, h1->right);
h1->dist = h1->right == nullptr ? 0 : h1->right->dist + 1;
return h1;
}
左派树和左派堆是两种十分实用的数据结构,尤其适用于优先队列的实现。除此之外,它们也可以直接用于其他需要堆的场合。
左派堆和左派树对于刚开始接触它们的新手来说,可能并不好理解。但是只要掌握了其基本思想,就可以愉快地享用它们带来的好处啦!