最小树形图

最小树形图,是指有向图的最小生成树。简单的来说,求一个图G0的最小树形图,先求出最短弧集合E0(从所以以vi为终点的弧中取一条最短的),若E0不存在(对于一给点vi没有入边),则图的最小树形图不存在。否则E0存在且不含有向环,则E0就是T0(最小树形图)中所有的边。如果存在且含有向环,则收缩成有向环为一点u,并形成图G1,继续求G1的最小树形图知道Gi,若Gi无最小树形图,则图G0也不存在最小树形图,存在最小树形图Ti就逐层展开得到T0.


最小树形图就是最小生成树在带权有向图上面的变种,也就是在有向图上找出一个边权和最小的有向树。这个问题需要给定一个带权的有向图以及有向树的根节点v。所谓有向树是指除了叶节点以外,所有节点都有指向其孩子节点的有向边。专业一点的说,“有向树(Directed Tree)是一个用于定义数据流或流程的逻辑结构。数据流的源点是根。数据流是单向分支离开根部到达目标,这个目标就是有向树的叶子。”。

求解最小树形图的第一个算法是1965年朱永津和刘振宏提出的复杂度为O(VE)的算法,该算法被称为朱-刘算法。在实现过程中,我们也可以表达为O(V3)的复杂度。

首先我们需要判断解是否存在。为了解决这个问题,我们只需要从指定的根节点v除法遍历一次就可以了。如果所有节点都可以被访问,那么解显然存在,否则不存在。另外,图中所有的自环也必须清除,因为这些自环显然不可能是最小树形图中的边。如果不知道什么是自环,请自行查阅图论相关定义。消除自环的目的是保证算法的时间复杂度。

下面是算法的流程:

  1. 对于图中除了根节点v以外的节点,选择一条权值最小的入边。所有被选择的边构成了一个子图,如果这个子图不含有有向环,那么这就是问题的解,算法结束;否则进行下一步。
  2. 我们需要收缩有向环,并且用一个新节点来代替。对于环中的点x,假定其选择边的权值为w0,y为环外的点,z为环收缩后新的节点。如果存在边<x,y>,那么<z,y>的边权值与<x,y>相同。如果存在边<y,x>且权值为w,那么新边<y,z>的权值就是w-w0
  3. 重复执行1、2两个步骤。

要保证算法O(VE)的复杂度,要做到找最小入边O(E),找环O(V),收缩O(E)。再加上由于收缩环最多只进行V-1次(自环已经被消除),所以时间复杂度为O(VE)。在步骤2中,x的出边权值不变是因为收缩环并没有影响边所指向的节点,而入边的权值之所以要变小,是因为当新边被选择之后,x原来选择的入边就会被取消选择,所以实际花费的代价就是w-w0


求所有边权和最小 ;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 111
#define inf 1<<30

struct Edge{
    int u,v;
    double w;
}edge[MAXN*MAXN];

struct Node {
    double x,y;
} node[MAXN];

int n,m;
double In[MAXN];
int pre[MAXN],visited[MAXN],ID[MAXN];
//root表示根结点,n是顶点树,m是边数
//最小树形图邻接表版本,三步走,找最小入弧,找有向环,缩环为点
double Directed_MST(int root,int n,int m)
{
    int u,v,i,ansi;
    double cnt=0;
    while(true) {
        //找最小入边
        for(i=0; i<n; i++)In[i]=inf;
        for(i=0; i<m; i++) {
            u=edge[i].u;
            v=edge[i].v;
            if(edge[i].w<In[v]&&u!=v) {
                pre[v]=u;//u->v;
                if(u==root)//记录是root从哪一条边到有效点的(这个点就是实际的起点)
                    ansi=i;
                In[v]=edge[i].w;
            }
        }
        for(i=0; i<n; i++) {
            if(i==root)continue;
            if(In[i]==inf)return -1;//说明存在点没有入边
        }
        //找环
        int cntcode=0;
        memset(visited,-1,sizeof(visited));
        memset(ID,-1,sizeof(ID));
        In[root]=0;
        //标记每一个环
        for(i=0; i<n; i++) {
            cnt+=In[i];
            v=i;
            while(visited[v]!=i&&ID[v]==-1&&v!=root) {
                visited[v]=i;
                v=pre[v];
            }
            //说明此时找到一个环
            if(v!=root&&ID[v]==-1) {
                //表示这是找到的第几个环,给找到的环的每个点标号
                for(u=pre[v]; u!=v; u=pre[u]) {
                    ID[u]=cntcode;
                }
                ID[v]=cntcode++;
            }
        }
        if(cntcode==0)break;//说明不存在环
        for(i=0; i<n; i++) {
            if(ID[i]==-1)
                ID[i]=cntcode++;
        }
        //缩点,重新标记
        for(i=0; i<m; i++) {
            int v=edge[i].v;
            edge[i].u=ID[edge[i].u];
            edge[i].v=ID[edge[i].v];
            //说明原先不在同一个环
            if(edge[i].u!=edge[i].v) {
                edge[i].w-=In[v];
            }
        }
        n=cntcode;
        root=ID[root];
    }
    return cnt;
}

double Get_dist(int i,int j)
{
    double d1=(node[i].x-node[j].x)*(node[i].x-node[j].x);
    double d2=(node[i].y-node[j].y)*(node[i].y-node[j].y);
    return sqrt(d1+d2);
}

int main()
{
    double ans;
    while(~scanf("%d%d",&n,&m)){
        for(int i=0;i<n;i++)scanf("%lf%lf",&node[i].x,&node[i].y);
        for(int i=0;i<m;i++){
            scanf("%d%d",&edge[i].u,&edge[i].v);
            edge[i].u--;
            edge[i].v--;
            if(edge[i].u!=edge[i].v)edge[i].w=Get_dist(edge[i].u,edge[i].v);
            else edge[i].w=inf;//消除自环
        }
        ans=Directed_MST(0,n,m);
        if(ans==-1){
            puts("poor snoopy");
        }else
            printf("%.2f\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值