图论 —— 次小生成树

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011815404/article/details/89465257

【概述】

对于给定的无向图 G=(V,E),设 T 是图 G 的一个最小生成树,那么,对于除 T 外的第二小的生成树 T' 即为图的次小生成树。

简单来说,最小生成树是生成树的最小解,次小生成树是生成树的次小解,它有可能和最小生成树的值一样,但肯定不能比最小生成树的值要小。

一般来说,求最小生成树的算法是 Prim 或 Kurskal,那么对于次小生成树,同样可以使用这两种算法来解。

对于求次小生成树来说,两种算法的思路都是相同的。首先求出最小生成树,再枚举每条不在最小生成树上的边,并把这条边放到最小生成树上面,此时一定会形成环,那么在这条环路中取出一条除新加入的边外的最长路,最终得到的权值就是次小生成树的权值。

【Prim 求解次小生成树】

使用 Prim  求解次小生成树需要使用一个二维数组 maxDis[i][j] 来表示最小生成树中 i 到 j 的最远距离,其是使用动态规划的思想来计算的,例如:当前节点为 x,其父节点为 per[x],根节点为 root,那么 maxDis[root][x] = max(maxDis[root][per[x]] , maxDis[per[x]][x]);

此外,还需要一个二维数组 connect[i][j] 表示最小生成树中这条边有没有被用到,剩下的就是模拟算法中所说的删边以及添边的操作。

int n,m,G[N][N];
bool vis[N],connect[N][N];
int dis[N],maxDis[N][N],per[N];
int prim(){
    memset(maxDis,0,sizeof(maxDis));
    memset(vis,false,sizeof(vis));

    for(int i=1;i <=n; i++){
        dis[i]=G[1][i];
        per[i]=1;//父节点都是根节点
    }

    vis[1]=true;
    dis[1]=0;

    int res=0;
    for(int i=1; i<n; i++){
        int index=-1,temp=INF;
        for(int j=1; j<=n; j++){
            if(!vis[j] && dis[j]<temp){
                index=j;
                temp=dis[j];
            }
        }
        if(index==-1)
            return res;

        vis[index]=1;
        connect[index][per[index]]=false;//边已在最小生成树中
        connect[per[index]][index]=false;//边已在最小生成树中

        res+=temp;
        maxDis[per[index]][index]=temp;//更新点之间的最大值
        maxDis[index][per[index]]=temp;//更新点之间的最大值
        for(int j=1; j<=n; j++){
            if(j!=index && vis[j]){//更新已遍历过的节点
                maxDis[index][j]=max(maxDis[j][per[index]],dis[index]);
                maxDis[j][index]=max(maxDis[j][per[index]],dis[index]);
            }
            if(!vis[j] && G[index][j]<dis[j]){
                dis[j]=G[index][j];
                per[j]=index;
            }
        }
    }
    return res;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(G,INF,sizeof(G));
    memset(connect,false,sizeof(connect));

    for(int i=0; i<m; i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        G[u][v]=w;
        G[v][u]=w;
        connect[u][v]=true;
        connect[v][u]=true;
    }
    int res=prim();//求次小生成树
    bool flag=false;
    for(int i=1; !flag && i<=n; i++){//枚举点
        for(int j=1 ; j<=n; j++){//枚举边
            //某边未被使用或i~j的最大值大于图中最大值,次小生成树存在
            if(connect[i][j]==false || G[i][j]==INF)
                continue;
            //边长度相同时表示最小生成树相同,次小生成树不存在
            if(G[i][j]==maxDis[i][j]){
                flag=true;
                break;
            }
        }
    }
    if(flag)
        printf("Not Unique!\n");
    else
        printf("%d\n",res);

    return 0;
}

【Kurskal 求解次小生成树】

Kruskla 算法中枚举的边权值会依次增大,那么就会给计算提供一定的便利,但因为 Kruskal 的实现方式和 Prim 有所不同,因此 Kruskal 需要存储当前最小生成树中的节点,然后再去更新 maxDis 数组

using namespace std;
int n,m;
struct Node{
    int u,v,w;
    bool vis;
    bool operator <(const Node &rhs)const{
        return w<rhs.w;
    }
} node[N];
vector<int>G[1000];
int father[1000],maxDis[1000][1000];
int Find(int x){
    return x==father[x]?x:father[x]=Find(father[x]);
}
void Kruskal(){
    sort(node,node+m);
    for(int i=0; i<=n; i++){//初始化
        G[i].clear();
        G[i].push_back(i);
        father[i]=i;
    }

    int mst=0,k=0;//k为当前生成树中的点
    for(int i=0; i<m; i++){//枚举边
        if(k==n-1)//等于n-1个点
            break;

        int x=Find(node[i].u),y=Find(node[i].v);
        if(x!=y){
            k++;
            mst+=node[i].w;
            node[i].vis=true;//边已用过,标记


            int lenX=G[x].size();
            int lenY=G[y].size();
            for(int j=0; j<lenX; j++){//更新两点之间距离的最大值
                for(int k=0; k<lenY; k++){
                    maxDis[G[x][j]][G[y][k]]=node[i].w;//因为后面的边会越来越大,所以这里可以直接等于当前边的长度
                    maxDis[G[y][k]][G[x][j]]=node[i].w;
                }
            }
            father[x]=y;
            for(int j=0; j<lenX; j++)
                G[y].push_back(G[x][j]);
        }
    }

    int cimst=INF;//次小生成树权值
    for(int i=0; i<m; i++)
        if(!node[i].vis)
            cimst=min(cimst,mst+node[i].w-maxDis[node[i].u][node[i].v]);
    if(cimst>mst)
        printf("%d\n",mst);
    else
        printf("Not Unique!\n");
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0; i<m; i++){
        scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
        node[i].vis=false;
    }
    Kruskal();
    return 0;
}

【例题】

  • The Unique MST(OpenJ_Bailian-1679)(次小生成树)点击这里
  • Qin Shi Huang's National Road System(HDU-4081)(次小生成树思想)点击这里

猜你喜欢

转载自blog.csdn.net/u011815404/article/details/89465257
今日推荐