次小生成树

次小生成树的定义
设 G=(V,E,w)是连通的无向图,T 是图G 的一个最小生成树。如果有另一棵树T1,满
足不存在树T’,ω(T’)<ω(T1) ,则称T1是图G的次小生成树。


求解次小生成树的算法
约定:由T 进行一次可行交换得到的新的生成树所组成的集合,称为树T的邻集,记为N(T)。
定理 3:设T是图G的最小生成树,如果T1满足ω(T1)=min{ω(T’)| T’∈N(T)},则T1是G
的次小生成树。
证明:如果 T1 不是G 的次小生成树,那么必定存在另一个生成树T’,T’=T 使得
ω(T)≤ω(T’)<ω(T1),由T1的定义式知T不属于N(T),则
E(T’)/E(T)={a1,a2
1,……,at},E(T)/E(T’)={b1,b2,……,bt},其中t≥2。根据引理1 知,存在一
个排列bi1,bi2,……,bit,使得T+aj-bij仍然是G 的生成树,且均属于N(T),所以ω(aj)≥ω(bij),
所以ω(T’)≥ω(T+aj-bij)≥ω(T1),故矛盾。所以T1是图G 的次小生成树。
通过上述定理,我们就有了解决次小生成树问题的基本思路。
首先先求该图的最小生成树T。时间复杂度O(Vlog2V+E)
然后,求T的邻集中权值和最小的生成树,即图G 的次小生成树。
如果只是简单的枚举,复杂度很高。首先枚举两条边的复杂度是O(VE),再判断该交换是否
可行的复杂度是O(V),则总的时间复杂度是O(V2E)。这样的算法显得很盲目。经过简单的
分析不难发现,每加入一条不在树上的边,总能形成一个环,只有删去环上的一条边,才能
保证交换后仍然是生成树,而删去边的权值越大,新得到的生成树的权值和越小。我们可以
以此将复杂度降为O(VE)。这已经前进了一大步,但仍不够好。
回顾上一个模型——最小度限制生成树,我们也曾面临过类似的问题,并且最终采用动态规
划的方法避免了重复计算,使得复杂度大大降低。对于本题,我们可以采用类似的思想。首
先做一步预处理,求出树上每两个结点之间的路径上的权值最大的边,然后,枚举图中不在
树上的边,有了刚才的预处理,我们就可以用O(1)的时间得到形成的环上的权值最大的边。
如何预处理呢?因为这是一棵树,所以并不需要什么高深的算法,只要简单的BFS 即可。
预处理所要的时间复杂度为O(V2)。
这样,这一步时间复杂度降为O(V2)。
综上所述,次小生成树的时间复杂度为O(V2)。

 

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

证明:
    可以证明下面一个强一些的结论:
    T是某一棵最小生成树,T0是任一棵异于T的树,通过变换
T0 --> T1 --> T2 --> ... --> Tn (T)  变成最小生成树.
所谓的变换是,每次把Ti中的某条边换成T中的一条边, 而
且树T(i+1)的权小于等于Ti的权.
    具体操作是:
    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).

感觉比较好的模板

#include<stdio.h>  
#include<string.h>  
#include<algorithm>  
#define M 107  
#define inf 0x3f3f3f  
using namespace std;  
int g[M][M],path[M][M];//path求的是i到j最大的边权  
int dist[M],pre[M],vis[M];  
bool used[M][M];//是否在最小生成树中  
int n,m,mst;  
  
void init()  
{  
    for(int i=0;i<=n;i++)  
        for(int j=i+1;j<=n;j++)  
            g[i][j]=g[j][i]=inf;  
  
}  
  
int prime()  
{  
    int mst=0;  
    memset(path,0,sizeof(path));  
    memset(vis,0,sizeof(vis));  
    memset(used,0,sizeof(used));  
    vis[1]=1;  
    for(int i=1;i<=n;i++)  
    {  
        dist[i]=g[1][i];  
        pre[i]=1;  
    }  
    for(int i=1;i<n;i++)  
    {  
        int u=-1;  
        for(int j=1;j<=n;j++)  
        {  
            if(!vis[j])  
                if(u==-1||dist[j]<dist[u])  
                    u=j;  
        }  
        used[u][pre[u]]=used[pre[u]][u]=true;//加入mst  
        mst+=g[pre[u]][u];  
        vis[u]=1;  
        for(int j=1;j<=n;j++)  
        {  
            if(vis[j]&&j!=u)//从u到j这条路径上最大边的权值  
                path[j][u]=path[u][j]=max(path[j][pre[u]],dist[u]);  
            if(!vis[j])  
                if(dist[j]>g[u][j])//更新相邻节点的距离  
                {  
                    dist[j]=g[u][j];  
                    pre[j]=u;//记录他的前驱  
                }  
        }  
    }  
    return mst;  
}  
  
int second_tree()//求次小生成树  
{  
    int res=inf;  
    for(int i=1;i<=n;i++)  
        for(int j=1;j<=n;j++)  
            if(i!=j&&!used[i][j])  
                res=min(res,mst-path[i][j]+g[i][j]);//删除树上权值最大的路径并且加上这条路径其它边  
    return res;  
}  
  
int main()  
{  
    int t;  
    scanf("%d",&t);  
    while(t--)  
    {  
        scanf("%d%d",&n,&m);  
        init();  
          
        mst=prime();//最小生成树  
        int second_mst=second_tree();//次小生成树  
    }  
} 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值