次小生成树

Posted lcbwwy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了次小生成树相关的知识,希望对你有一定的参考价值。

技术图片

最小生成树大家都会,但是次小生成树呢?生成树算法在计算机运用广泛啊!!!!
先来讲一下定义:
(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;
}














以上是关于次小生成树的主要内容,如果未能解决你的问题,请参考以下文章

次小生成树

次小生成树

poj1679次小生成树入门题

次小生成树

严格次小生成树(Bzoj1977:[Beijing2010组队]次小生成树)

次小生成树