可合并优先队列:左偏树和斜堆
有时候需要合并优先队列,对于二叉堆,只能以O(N)的复杂度建堆,因为它除了键值和堆序性质其它什么都没有。
要想把合并的复杂度降为O(LogN),可以用斜堆(左偏树),二项堆等数据结构。斐波拉契堆更牛,很多操作都是O(1)的理论复杂度,但是思维复杂度和编程复杂度……
二项堆了解了一点,但只是半懂不懂地看了点性质之类的,具体的操作还不会。有时间还是想试一下。斐波拉契堆就算了吧……
左偏树的性质是:左子树的零路径长不小于右子树,这就导致树严重向左偏。合并两棵树,比较堆顶元素大小,把一棵树的右子树和另一棵树递归合并即可。如果合并后的树右子树的零路径长小于左子树,那么就交换左右子树。插入的话,可以看成把一棵只有一个元素的左偏树和另一棵合并。删除的话,合并左右子树就行了。因为树符合堆序性质,所以取最小值的操作也一样。
斜堆就是左偏树的自调整形式,说白了就是简化版。我们不需要维护路径长,每次合并后都交换左右子树,这样均摊的操作时间不变,思想类似于Splay与AVL。具体的实现,少打几行代码就行了。
左偏树的入门题是HDU1512,同时用并查集找爸爸。
我是看这里写的http://www.cnblogs.com/lxf90/archive/2011/04/07/2008180.html
下面是一个关于此题和左偏树的PPT的链接http://oi.imzzl.com/2010/07/610.html
代码:(是斜堆,去掉//就是左偏树了)
#include <stdio.h> #define MAXN 100000 struct node { int dis,l,r,strong; }tree[MAXN]; int i,j,k,m,n; int father[MAXN]; int x,y,fx,fy; void swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } int find(int x) { if (x != father[x]) father[x] = find(father[x]); return father[x]; } int merge(int x, int y) { if (x == 0) return y; if (y == 0) return x; if (tree[x].strong < tree[y].strong) swap(&x, &y); tree[x].r = merge(tree[x].r, y); int l = tree[x].l; int r = tree[x].r; father[r] = x; // if (tree[l].dis < tree[r].dis) swap(&tree[x].l, &tree[x].r); // if (tree[x].r == 0) // tree[x].dis = 0; // else // tree[x].dis = tree[r].dis + 1; return x; } int del(int x) { int l,r; l = tree[x].l; r = tree[x].r; father[l] = l; father[r] = r; tree[x].l = tree[x].r = tree[x].dis = 0; return merge(l, r); } void fight(int x , int y) { int left,right,now; tree[x].strong /= 2; tree[y].strong /= 2; left = del(x); right = del(y); left = merge(left, x); right = merge(right ,y); now = merge(left, right); printf("%d\n", tree[now].strong); } int main() { while (scanf("%d", &n) == 1) { for (i = 1; i <= n; i++) { scanf("%d", &tree[i].strong); tree[i].l = tree[i].r = tree[i].dis = 0; father[i] = i; } scanf("%d", &m); for (i = 1; i <= m; i++) { scanf("%d%d", &x, &y); fx = find(x); fy = find(y); if (fx == fy) printf("-1\n"); else fight(fx, fy); } } return 0; }
参考资料:
CLRS
数据结构与算法分析