朱刘算法(Directed Minimum Spanning Tree/Directed MST/Minimum Arborescence/Optimum Branchings)

来源:互联网 发布:好的南方二本院校知乎 编辑:程序博客网 时间:2024/06/06 13:06

概念

最小树形图:有向图所分离出的有向生成树,亦称为最小树形图,其应满足以下条件:
1. 恰好有一个入度为0的点,称为根结点
2. 其他结点的入度均为1
3. 可以从根结点到达其他结点

既然要找最小生成树,当然就是找权重越小的边越好。每一个点(除了根以外)各自找到权重最小的入边之后,有可能就刚好是一棵最小生成树了,但是也有可能形成几只“水母”。

由于每个点都仅有一条入边,如果形成环,环上一定只有出边,不会有入边。每个点都仅有一条入边,除了刚好形成一棵树以外,要不就是形成水母 —— 一只环再加上环上的点各是一棵树的树根,或者说是很多棵树的树根用环串起。

水母图

水母与最小树形图

最小生成树不能有环,所以水母是不合格的,然而水母是权重最小的连接方式。若有一棵恰当的最小生成树,其权重会略高于水母。由水母向最小生成树靠拢,可能有以下两种情况:

  1. 改变水母环上的边,让水母变成一棵树,尽管整体权重稍微变大,但仍可接受。
  2. 改变水母触手上的边,并没有比较好。不但让整体权重变大,而且水母环仍存在,并没有解决掉不合格的问题。

由此可得出结论:

只需要尝试打开水母环上的边就行了。打开环的时候,要同时考虑新加入的边的权重和取消的边的权重。选择差值最小者,可让权值增加最小。进入水母的环全部看一遍后,就能选出差值最小者。

朱刘算法(Chu-Liu/Edmonds Algorithm)

算法思想

根据Kruskal’s Algorithm提到的最小生成树相连性质,可以知道连接多只水母,就和连接多棵最小生成树的道理是一样的,以权重小的边來连接是最好的。唯一不同的是,Kruskal’s Algorithm一旦发现造成环的边,就直接舍弃;Chu-Liu/Edmonds Algorithm则是留下造成环的边(形成水母),并且尝试各种打开环的方式。

该算法设计思想巧妙,也较难理解。 Sasuke_SCUT 的blog讲原理解释得较清楚,特摘录如下:

判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的 算法中不再考虑树形图不存在的情况。
在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。
首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环所称一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩的那个环的权和,就是原图中最小树形图的权。
上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w’(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。
如果实现得很聪明的话,可以达到找最小入边O(E),找环 O(V),收缩O(E),其中在找环O(V)这里需要一点技巧。这样每次收缩的复杂度是O(E),然后最多会收缩几次呢?由于我们一开始已经拿掉了所有的自环,我门可以知道每个环至少包含2个点,收缩成1个点之后,总点数减少了至少1。当整个图收缩到只有1个点的时候,最小树形图就不不用求了。所以我们最 多只会进行V-1次的收缩,所以总得复杂度自然是O(VE)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。

chu_liu and kruskal

算法步骤

  1. 找到除了root以为外其他点的权值最小的入边。用In[i]记录 ;
  2. 如果出现除了root以为存在其他孤立的点,则不存在最小树形图。;
  3. 找到图中所有的环,并对环进行缩点,重新编号。;
  4. 更新其他点到环上的点的距离 ;
  5. 重复3,4直到没有环为止。

chu_liu Algorithm

Codes

// 摘自:http://blog.csdn.net/ditian1027/article/details/21958821#include <iostream>#include <math.h>#include <cstdio>using namespace std;#define N 105#define INFINITE 999999999#define MYTYPE doublestruct _point{    int x;    int y;} point[N];struct _edge{    int from;    int to;    MYTYPE cost;} edge[N*N];MYTYPE inw[N];  //最小入边int vis[N]; //是否被访问 int id[N];  //由当前图到重构图的映射 int pre[N]; //前驱顶点MYTYPE Directed_MST(int root, int NV, int NE){    MYTYPE ret=0;    while (1)   //开始迭代过程     {        //1.确定最小入边集         for(int i=0; i<NV; ++i) inw[i]=INFINITE;        for (int i=0; i<NE; ++i)        {            int from=edge[i].from;            int to=edge[i].to;            if (edge[i].cost<inw[to] && from!=to)   //from!=to忽略自环            {                inw[to]=edge[i].cost;                pre[to]=from;            }        }        //检查是否有不可达点        for (int i=0; i<NV; ++i)        {            if(i==root) continue;   //除根之外            if(inw[i]==INFINITE) return -1; //有不可达顶点,不可能生成最小树形图,退出        }        //2.找环        for (int i=0; i<NV; ++i)        {            vis[i]=-1;            id[i]=-1;        }        int newidx=0;        inw[root]=0;        for (int i=0; i<NV; ++i)    //有两个作用:计算最小入边集的权值和;检查是否有环,如果有,重新对点进行编号        {            ret+=inw[i];            int v=i;            while (vis[v]!=i && id[v]==-1 && v!=root)               //由v回溯。能回到根,即最后v==root,那么肯定不在环里;回不到根,v!=root,v有可能在环里,也有可能不在(回溯到一个环然后出不去了,同样也到不了根)。            //若v在环里,则环上所有点的id[]值会被重新标号,不再是-1;若是后一种情况,它前驱的环上的点的id[]已被修改为非-1,不能通过“id[v]==-1”这个条件的检查。            {                vis[v]=i;                v=pre[v];            }            if (v!=root && id[v]==-1)   //两个条件保证了:1.在环上2.这环没被处理过            {                //下面把环上所有的点的标号设置为同一个                  for (int u=pre[v]; u!=v; u=pre[u])                {                    id[u]=newidx;                }                id[v]=newidx;                ++newidx;            }        }        if(newidx==0)   break;  //无环,ret就是答案,跳出迭代        for (int i=0; i<NV; ++i)        {            if(id[i]==-1) id[i]=newidx++;   //给环外的点继续编号        }        //3.重新构图,准备下一次迭代        for (int i=0; i<NE; ++i)        {            int to=edge[i].to;            edge[i].from=id[edge[i].from];            edge[i].to=id[edge[i].to];            if(edge[i].from != edge[i].to)            {                edge[i].cost -= inw[to];    //算法的关键            }        }        //为下一轮迭代赋初值        NV=newidx;        root=id[root];    }    return ret;}MYTYPE calcdist(int point_a, int point_b){    MYTYPE delta_x=(MYTYPE)(point[point_a].x - point[point_b].x);    MYTYPE delta_y=(MYTYPE)(point[point_a].y - point[point_b].y);    return sqrt(delta_x*delta_x + delta_y*delta_y);}int main(){    int n, m, x, y, from, to;    MYTYPE ans;    while (scanf("%d", &n) != EOF)    {        scanf("%d", &m);        for (int i=0; i<n; ++i) //n个顶点        {            scanf("%d%d", &x, &y);            point[i].x=x;            point[i].y=y;        }        for (int i=0; i<m; ++i) //m个边        {            scanf("%d%d", &from, &to);            edge[i].from=from-1;            edge[i].to=to-1;            edge[i].cost=calcdist(from-1, to-1);        }        ans=Directed_MST(0, n, m);        if (ans==-1)    printf("NO\n");        else printf("%.2f\n", ans);    }    return 0;}

参考:演算法笔记-Tree

阅读全文
0 0