zoukankan      html  css  js  c++  java
  • 次小生成树

    最小生成树大家都会,但是次小生成树呢?生成树算法在计算机运用广泛啊!!!!
    先来讲一下定义:
    (G=(V,E,w))是连通的无向图,(T) 是图(G) 的一棵最小生成树。如果有另一棵树(T1),满足不存在(找不到)树(T’),(W_{T’}<W_{T1}),则称T1是图G的次小生成树。(就是长度第二小的树,但是非严格次小生成树的长度可以等于最小生成树的长度,严格起来就一定是长度之比最小生成树大的生成树)

    求解次小生成树的算法

    • 结论1
      次小生成树可由最小生成树换一条边得到.

    • 证明:
      具体操作是:

    • step 1. 在Ti中任取一条不在T中的边uv.

    • step 2. 把边uv去掉,就剩下两个连通分量A和B,
      在T中,必有唯一的边u'v' 连结A和B.

    • step 3. 显然u'v'的权比uv小 (否则,uv就应该在T中).
      把u'v'替换uv即得树T(i+1).

    • 特别地:取T0为任一棵次小生成树,T(n-1) 也就是次小生成树且
      跟T差一条边. 结论1得证.

    算法:
    只要充分利用结论1, 即得V^2的算法. 具体如下:

    • step 1. 先用prim求出最小生成树T.
      在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
      路中权值最大的那条边的权值. (注意这里).
      这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
      集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
      step 1 用时 O(V^2).
    • step 2. 枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边.
      故总时间为O(V^2).

    prim

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #define INF 0x3f3f3f3f
    using namespace std;
    int mp[110][110];
    int vis[110];
    int dis[110];
    int path[110][110];//记录i,到j点的最大距离
    int pre[110];//记录每个点的父节点
    int use[110][110];//标记这条边是否用过(是否在最小生成树中)
    int ans,n,m;
    int prim(int cost[ ][110],int n)
    {
        memset(dis,INF,sizeof(dis));
        memset(vis,0,sizeof(vis));
        memset(path,0,sizeof(path));
        memset(use,0,sizeof(use));
        vis[1]=1;//从节点1开始
        for(int i=1;i<=n;i++)
        {
            dis[i]=cost[1][i];
            pre[i]=1;//开始的父节点都为1
        }
        int ans=0;
        for(int i=2;i<=n;i++)
        {
            int pos=-1;
            for(int j=1;j<=n;j++)
            {
                if(!vis[j]&&(pos==-1||dis[pos]>dis[j]))//寻找最小边
                {
                    pos=j;
                }
            }
            if(pos==-1)
            {
                break;
            }
            ans+=dis[pos];//加入
            vis[pos]=1;
            use[pre[pos]][pos]=use[pos][pre[pos]]=1;//标记这条边已经在最小生成树里面
            for(int j=1;j<=n;j++)
            {
                if(vis[j]==1&&j!=pos)
                {
                    path[pos][j]=path[j][pos]=max(dis[pos],path[j][pre[pos]]);//添加pos到各个已经在最小生成树的点集中的点的最大距离,
                    //之所以是dis[pos]与path[j][pre[pos]]比较,是以为dis[pos]表示的是pos与其父节点的距离,再者要想将pos加入点集,
                    //说明它的父节点早已经在最小生成树的点集中了,于是只要比较各个点到pos的父节点的最大值与pos到它父节点的值就可以了。因为path[][]记录的就是i点到j点的最大值
                }
                if(vis[j]==0&&cost[pos][j]<dis[j])//发现通过pos节点到达节点j的距离更小
                {
                    dis[j]=cost[pos][j];
                    pre[j]=pos;//于是节点j更新父节点为pos
                }
            }
        }
        return ans;
    }
    int Subprim(int n)
    {
        int res=INF;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i!=j&&use[i][j]==0)//挑选没有在最小生成树的边
                {
                    res=min(res,ans-path[i][j]+mp[i][j]);//删除两节点的最大边,加上没有加入的边,
                    //加入的边或许没有连接,所以初始化mp为INF
                }
            }
        }
        if(res==INF)
        {
            return -1;
        }
        return res;
    }
    int main( )
    {
        int t,x,y,w;
        cin>>t;
        while(t--)
        {
            cin>>n>>m;
            memset(mp,INF,sizeof(mp));
            for(int i=1;i<=n;i++)
            {
                mp[i][i]=0;
            }
            for(int i=1;i<=m;i++)
            {
                cin>>x>>y>>w;
                mp[x][y]=mp[y][x]=w;
            }
            ans=prim(mp,n);
            int ans1=Subprim(n);
            if(ans1==ans)
            {
                cout<<"Not Unique!"<<endl;
            }
            else
            {
                cout<<ans<<endl;
            }
        }
        return 0;
    }
    
    Kruskal
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #define INF 0x3f3f3f3f
    using namespace std;
    const int M=1e5;
    int root[M];
    struct node
    {
        int a,b,w;
    } edge[M];
    int n,m;
    int a[M];
    int res;
    void init(int n)
    {
        for(int i=1; i<=n; i++)
        {
            root[i]=i;
        }
    }
    int find(int x)
    {
        if(x==root[x])
        {
            return x;
        }
        else
        {
            return root[x]=find(root[x]);
        }
    }
    int ouin(int a,int b)
    {
        int x=find(a);
        int y=find(b);
        if(x==y)
        {
            return 0;
        }
        else
        {
            root[x]=y;
            return 1;
        }
    
    }
    bool cmp(node a,node b)
    {
        return a.w<b.w;
    }
    int Kruskal_MinTree( )
    {
        int ans=0;
        int cnt=0;
        for(int i=0; i<m; i++)
        {
            if(ouin(edge[i].a,edge[i].b))
            {
                ans+=edge[i].w;
                a[res++]=i;
                cnt++;
            }
            if(cnt==n-1)
            {
                break;
            }
        }
        if(cnt==n-1)
        {
            return ans;
        }
        return -1;
    }
    int Subsmall( )
    {
        int sum=INF;
        for(int i=0; i<res; i++)
        {
            init(n);
            int ans=0;
            int cnt=0;
            for(int j=0; j<m; j++)
            {
                if(j!=a[i])
                {
                    if(ouin(edge[j].a,edge[j].b))
                    {
                        ans+=edge[j].w;
                        cnt++;
                    }
                }
                if(cnt==n-1)
                {
                    break;
                }
            }
            if(cnt==n-1)
            {
                sum=min(sum,ans);
            }
        }
        if(sum==INF)
        {
            return -1;
        }
        return sum;
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            res=0;
            cin>>n>>m;
            init(n);
            for(int i=0; i<m; i++)
            {
                cin>>edge[i].a>>edge[i].b>>edge[i].w;
            }
            int ans=Kruskal_MinTree( );
            int ans2=Subsmall( );
            if(ans==ans2)
            {
                cout<<"Not Unique!"<<endl;
            }
            else
            {
                cout<<ans<<endl;
            }
            
        }
        return 0;
    }
    

    根据定义,利用克鲁斯卡尔找出两点路径中最大值

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<algorithm>
    #include<cstring>
    #define INF 0x3f3f3f3f
    using namespace std;
    const int M=110;
    struct node
    {
        int a,b,w;
    }edge[M*M];
    int root[M];
    int lenth[M][M];
    vector<int>G[M];//存放点
    int use[M*M];
    int find(int x)
    {
        if(x==root[x])
        {
            return x;
        }
        else
        {
            return root[x]=find(root[x]);
        }
    }
    int ouin(int a,int b)
    {
        if(a==b)
        {
            return 0;
        }
        else
        {
            return 1;
        }
        
    }
    bool cmp(node a,node b)
    {
        return a.w<b.w;
    }
    void Kruskal(int n,int m)
    {
        memset(use,0,sizeof(use));
        memset(lenth,0,sizeof(lenth));
        for(int i=1;i<=n;i++)
        {
            G[i].clear( );
            G[i].push_back(i);//各个点集中只有本身
            root[i]=i;
        }
        sort(edge,edge+m,cmp);
        int ans=0,cnt=0;
        for(int i=0;i<m;i++)
        {
            int u=find(edge[i].a),v=find(edge[i].b);
            if(ouin(u,v))
            {
                ans+=edge[i].w;
                cnt++;
                use[i]=1;
                int lenu=G[u].size( );//点集1
                int lenv=G[v].size( );//点集2
                for(int j=0;j<lenu;j++)
                {
                    for(int k=0;k<lenv;k++)
                    {
                        lenth[G[u][j]][G[v][k]]=lenth[G[v][k]][G[u][j]]=edge[i].w;//更新点集1中的各个点与点集2中各个的点的距离,
                                                                                //因为edge[i].w从小到大排序,所以后面的一定比前面的大
                    }
                }
                root[u]=v;//合并点集父节点,u的父节点是v;
                for(int j=0;j<lenu;j++)//合并两个点集
                {
                    G[v].push_back(G[u][j]);//把子节点的点放到父节点的点集中
                }
            }
            if(cnt==n-1)
            {
                break;
            }
        }
        int res=INF;
        for(int i=0;i<m;i++)
        {
            if(!use[i])
            {
                res=min(res,ans+edge[i].w-lenth[edge[i].a][edge[i].b]);
            }
        }
        if(res>ans)
        {
            cout<<ans<<endl;
        }
        else
        {
            cout<<"Not Unique!"<<endl;
        }
    }
    int main( )
    {
        int t,n,m;
        cin>>t;
        while(t--)
        {
            cin>>n>>m;
            for(int i=0;i<m;i++)
            {
                cin>>edge[i].a>>edge[i].b>>edge[i].w;
            }
            Kruskal(n,m);
        }
        return 0;
    }
    
  • 相关阅读:
    删除List集合中的元素你碰到过这样的陷阱吗?
    从spring框架中的事件驱动模型出发,优化实际应用开发代码
    SpringBoot启动原理及相关流程
    基于SpringBoot实现定时任务的设置(常用:定时清理数据库)
    C#开发中常用的加密解密方法
    http://go.microsoft.com/fwlink/?linkid问题
    移动端开发必须知道的小技巧
    工作中遇到的细节问题总结(二)
    redis分布式锁和消息队列
    join和wait
  • 原文地址:https://www.cnblogs.com/lcbwwy/p/13125221.html
Copyright © 2011-2022 走看看