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

11 篇文章 1 订阅
3 篇文章 0 订阅

概念

最小树形图:有向图所分离出的有向生成树,亦称为最小树形图,其应满足以下条件:
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 double

struct _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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值