Pairing_heap(配对堆)

这个东西在pb_ds中用过。但不知道是什么。然后刚好这几天有空就看了一下。发现其实
很简单。

他事实上就只是维护了一颗树而已。

我们假设现在有 N 个节点,每个节点都带有权值。我们要维护这些节点的一个堆。

假设我们要维护一个小根堆。

对于每个节点 i ,我们都有一个表 Son 维护他所有的儿子,并且还有一个值 Fai 表示他的父亲。

对于这颗树而言,我们保证每个节点的儿子的权值都要大于等于这个节点的权值

那么最后这颗树的根,设为 Root ,就是权值最小的点。

接下来考虑堆的几个基本操作。

1. Merge(u,v)

表示我们要把u,v这两个堆合并起来,接着返回这个新堆的根。
那么很显然的,我们只需要比较一下 Rootu 的权值和 Rootv 的权值,设权值大的是 y ,另外一个是 x ,接着直接把 Fay 设为 x ,把 y 加入到 Sonx 中即可。
最后返回 x 即可。

2. Insert(u,v)

表示我们要往堆 u 中加入一个权值为 v 的节点。那么我们可以先新建一个堆,里面只有一个权值为 v 的点。然后把他们Merge在一起就好了。

3. ChangeVal(u, Δ )

表示我们要把节点 u 的权值减去 Δ,Δ0 .
这是一个在堆中非常常用的操作。
首先,若 u 就是他所处堆的根的话,这个堆没有任何变化。我们直接把权值修改了即可。

否则,我们先把 u 从这个堆中分离出来。

值得注意的是,我们不修改 Fau Son 集合。我们只把 Fau 设为空。表示他现在是他所处堆的根。至于对 Fau 的影响,我们接下来会考虑。

u 分离出来后。我们直接修改他的权值。然后 Merge(u) 即可。

4. Pop(u)

表示我们要把 u 这个堆的根给删掉。

这是整个 Paring_heap 中最复杂(其实也很简单),也是最神奇的地方。

我们知道按照我们上面的操作,我们的根的儿子个数最坏情况下是 O(N) 的。

设根为 Root

我们首先肯定要提取出 Root 的所有儿子。但注意的是,此时 Root Son 表中可能有些并不是他的儿子。但事实上我们只需要判一下 Fav 是否等于 Root 即可。这是不影响时间复杂度的。而且这也简化了ChangeVal的编程复杂度。

假设现在的真正的儿子表就是 Son .

一种简单粗暴的方法就是枚举所有的儿子,然后把他们直接Merge在一起。

由于这个实在是太粗暴了。所以这个的时间复杂度是最差的。。

另外一种方法是,
回忆我们用最普通的堆来 O(N) 建出 N 个元素的堆的做法。

我们维护一个FIFO(first in first out)队列。
一开始先把所有的元素塞进这个队列里。

若当前队列中只有一个元素,那么我们的根就是这个元素。

否则我们取出队首的两个元素,设为 x,y ,接着再把 Merge(x,y) 塞入队列当中。

这种做法的单次时间复杂度是 O() 的。

但是我们发现这样子建出来的根的儿子个数肯定变少了。

很可惜我并不会分析这个时间复杂度。

时间复杂度

以下复杂度都是期望复杂度

  1. 一种说法是,除了Pop() O(logN) ,其他都是 O(1)
  2. 我看来最早提出这个算法的paper,他的复杂度是除了 Pop ChangeVal O(logN) ,其他都是 O(1)

实用性

我用pairing_heap打了个dijkstra,和priority_queue比较了一下。发现虽然算法一的理论复杂度是 O(NlogN+M) ,但是 Pop 的常数较大。同开了 O2 的priority_queue感觉差不多,还慢了一点,可能要松弛次数比 N 大得多才能体现出优势吧。

代码


struct STACK
{
    int A[MAXN],tot,cnt;

    int Top()
    {
        if (!tot) return ++ cnt;
        return A[tot --];
    }

    void Push(int x)
    {
        A[++ tot] = x;
    }

};

struct Pairing_heap
{
    STACK Edge,Point;
    int Que[MAXN << 1];
    LL Val[MAXN];
    int Final[MAXN],Next[MAXN],Fa[MAXN],Refer[MAXN],Apear[MAXN],To[MAXN],Root;
    int tmp;

    int Merge(int a,int b)
    {
        if (Val[a] > Val[b]) {tmp = a,a = b,b = tmp;}
        int v = Edge.Top();
        To[v] = b,Next[v] = Final[a],Final[a] = v,Fa[b] = a;
        return a;
    }

    void Insert(int x,int y)
    {
        int u = Point.Top();
        Val[u] = y;
        Refer[x] = u,Apear[u] = x;
        if (Root) Root = Merge(u,Root); else Root = u;
    }

    void Extract(int x)
    {
        if (x == Root || !x) return;
        Fa[x] = 0;
    }

    void ChangeVal(int x,int y)
    {
        x = Refer[x];
        Extract(x);
        Val[x] = y;
        if (x != Root) Root = Merge(x,Root);
    }

    void Value(int x,int y)
    {
        if (!Refer[x]) Insert(x,y); else ChangeVal(x,y);
    }

    int Top()
    {
        return Apear[Root];
    }

    void Pop()
    {
        int fi = 0,en = 0;
        for(int i = Final[Root];i;i = Next[i])
        {
            Edge.Push(i);
            if (Fa[To[i]] == Root) Fa[To[i]] = 0,Que[++ en] = To[i];
        }
        Point.Push(Root);
        Refer[Apear[Root]] = Final[Root] = Fa[Root] = 0;
        Apear[Root] = 0;
        Root = 0;
        while (fi < en)
        {
            ++ fi;
            if (fi == en) {Root = Que[fi];return;}
            int u = Que[fi],v = Que[++ fi];
            Que[++ en] = Merge(u,v);
        }
    }
};

你可能感兴趣的:(数据结构)