Loading docs/ds/pairing-heap.md +3 −24 Original line number Diff line number Diff line Loading @@ -58,14 +58,10 @@ Node* merge(Node* a, Node* b) { 到这里我们会发现,前面的几个操作都十分偷懒,几乎完全没有对数据结构进行维护,所以删除最小值是配对堆最重要的(也是最复杂)的一个操作。 考虑我们拿掉根节点之后会发生什么,根节点原来的所有儿子构成了一片森林,所以我们要把他们合并起来。 一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们两两配成一对,先用 `merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再按上述方法将新产生的堆暴力合并在一起(见下图 2)。 一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们 **从左往右** 两两配成一对,用 `merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再将新产生的堆 **从右往左** 暴力合并在一起(见下图 2)。 先实现一个辅助函数 `merges` ,作用是合并一个节点的所有兄弟。 ##### 递归版本的 merges(推荐) 实现上,推荐使用这种好写的递归式实现。 ```cpp Node* merges(Node* x) { if (x == node || x->xd == node) Loading @@ -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); } Loading Loading
docs/ds/pairing-heap.md +3 −24 Original line number Diff line number Diff line Loading @@ -58,14 +58,10 @@ Node* merge(Node* a, Node* b) { 到这里我们会发现,前面的几个操作都十分偷懒,几乎完全没有对数据结构进行维护,所以删除最小值是配对堆最重要的(也是最复杂)的一个操作。 考虑我们拿掉根节点之后会发生什么,根节点原来的所有儿子构成了一片森林,所以我们要把他们合并起来。 一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们两两配成一对,先用 `merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再按上述方法将新产生的堆暴力合并在一起(见下图 2)。 一个很自然的想法是使用 `merge` 函数把儿子们一个一个并在一起,这样做的话正确性是显然的,但是会导致复杂度退化到 $O(n)$ 。为了保证删除操作的均摊复杂度为 $O(\log n)$ ,我们需要:把儿子们 **从左往右** 两两配成一对,用 `merge` 操作把被配成同一对的两个儿子合并到一起(见下图 1),再将新产生的堆 **从右往左** 暴力合并在一起(见下图 2)。 先实现一个辅助函数 `merges` ,作用是合并一个节点的所有兄弟。 ##### 递归版本的 merges(推荐) 实现上,推荐使用这种好写的递归式实现。 ```cpp Node* merges(Node* x) { if (x == node || x->xd == node) Loading @@ -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); } Loading