Commit 2ce7f01e authored by WAAutoMaton's avatar WAAutoMaton
Browse files
parent 5fbb8d4c
Loading
Loading
Loading
Loading
+3 −24
Original line number Diff line number Diff line
@@ -58,14 +58,10 @@ Node* merge(Node* a, Node* b) {

到这里我们会发现,前面的几个操作都十分偷懒,几乎完全没有对数据结构进行维护,所以删除最小值是配对堆最重要的(也是最复杂)的一个操作。  
考虑我们拿掉根节点之后会发生什么,根节点原来的所有儿子构成了一片森林,所以我们要把他们合并起来。  
一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们两两配成一对,`merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再按上述方法将新产生的堆暴力合并在一起(见下图 2)。![](./images/pairingheap4.jpg)![](./images/pairingheap5.jpg)
一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们**从左往右**两两配成一对,用 `merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再将新产生的堆**从右往左**暴力合并在一起(见下图 2)。![](./images/pairingheap4.jpg)![](./images/pairingheap5.jpg)

先实现一个辅助函数 `merges` ,作用是合并一个节点的所有兄弟。

##### 递归版本的 merges(推荐)

实现上,推荐使用这种好写的递归式实现。

```cpp
Node* merges(Node* x) {
  if (x == node || x->xd == node)
@@ -82,26 +78,9 @@ Node* merges(Node* x) {
2.   `merges(b)` 递归合并 b 和他的兄弟们。
3.  将上面 2 个操作产生的 2 个新树合并。

##### 迭代版本的 merges

迭代版本不仅不好写,而且实现不优越的话还不一定比递归版快(下面这个就是不优越的实现,跑的不比上面的递归版本快),所以更推荐写递归版。

```cpp
Node* merges(Node* x) {
  Node* t = x;
  x = x->xd;
  t->xd = node;
  while (x->xd != node) {
    Node *a = x->xd, *b = a->xd;
    x->xd = a->xd = node;
    t = merge(t, merge(x, a));
    x = b;
  }
  return merge(t, x);
}
```
需要注意到的是,上文提到了配对方向和合并方向是有要求的(从左往右配对,从右往左合并),该递归函数的实现已保证了这个顺序,如果读者需要自行实现迭代版本的话请务必注意保证该顺序,否则复杂度将失去保证。

然后 `delete-min` 操作就显然了。(因为这个封装实在没啥用,实际在实现时中一般不显式写出这个函数)
有了 `merges` 函数,`delete-min` 操作就显然了。(因为这个封装实在没啥用,实际在实现时中一般不显式写出这个函数)

```cpp
Node* delete_min(Node* x) { return merges(x->ch); }