次小生成树prim与kruskal算法代码详解

Prim求次小生成树

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int N = 1005;

/*
    在最小生成树中,i~j没有直接相连的边时,直接连接(i,j),这样就会形成一个环,再去掉环中 且除去刚连的边(i,j)外 最大的边;
    即去掉在生成树上,i~j 最大的边。用边 (i,j) 替换掉最小生成树里i到j最大的边
    去掉这个最大的边,得到的就是第二大的这个环上所有边权值和。
    依次遍历每两个点,获取替代后最小的,就是次小生成树
*/

int G[N][N], used[N][N], mxd[N][N];
int n, m;

void init()
{
    memset(used, 0, sizeof(used));
    memset(mxd, 0, sizeof(mxd));
    memset(G, INF, sizeof(G));
}

int Prim(int s)
{
    int vis[N], dis[N], p[N];
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i<=n; i++)
    {
        dis[i] = G[s][i];
        p[i] = s;//p[i]  i与最小生成树相连的一个端点
    }
    vis[s] = 1;
    int sum = 0;
    for(int i = 1; i<n; i++)
    {
        int mn = INF, u = s;
        for(int j = 1; j<=n; j++)
        {
            if(!vis[j] && dis[j]<mn)
                mn = dis[u=j];
        }
        vis[u] = 1;
        sum += mn;
        used[u][p[u]] = used[p[u]][u] = 1;//标记 u<->p[u] 这条边在最小生成树上
        for(int j = 1; j<=n; j++)
        {
            if(vis[j] && j!=u)//遍历最小生成树上每个点,获取j 到u 路径上最长的边
                mxd[j][u] = mxd[u][j] = max(mxd[j][p[u]], dis[u]);//dis[u] 是u<->p[u]的距离,mxd[j][p[u]]是j到p[u]的最长边
                                                                  //j -> p[u] <-> u  通过这样一条路径来得到j->u路径上最长的边
            if(!vis[j])//对于没在最小生成树的点
                if(dis[j]>G[u][j])
                {
                    dis[j] = G[u][j];//更新其到最小生成树的距离
                    p[j] = u;//更新点 j 连接最小生成树的点
                }
        }
    }
    return sum;
}

int SecPrim(int x)
{
    int ans = INF;
    for(int i = 1; i<=n; i++)
        for(int j = 1; j<=n; j++)
            if(i!=j && !used[i][j])//去掉最小生成树上 i,j 之间的最大的边值,加上i-j这条边
                ans = min(ans, x-mxd[i][j]+G[i][j]);//依次枚举,找到最小的就是次小生成树
    return ans;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i = 1; i<=m; i++)
        {
            int u, v, w;
            scanf("%d%d%d",&u,&v,&w);
            G[u][v] = G[v][u] = w;
        }
        int temp = Prim(1);
        int ans = SecPrim(temp);
        printf("最小生成树:%d\n次小生成树:%d\n", temp, ans);
    }
    return 0;
}



Kruskal求次小生成树

#include<bits/stdc++.h>
using namespace std;

const int N = 110, INF = 0x3f3f3f3f;
int n,m;
int root[N], mxd[N][N];
vector<int> p[N];

struct node
{
    int u,v,w,vis;//vis判断是否在最小生成树中
}G[N*N];

void init()
{
    for(int i = 0; i<N; i++)
    {
        root[i] = i;
        p[i].clear();
        p[i].push_back(i);
    }
}

int find_(int x)
{
    return x == root[x] ? x : root[x] = find_(root[x]);
}

bool cmp(node a, node b)
{
    return a.w<b.w;
}

void kruskal()
{
    int ans = 0, sec_ans = INF;
    sort(G, G+m, cmp);
    init();
    for(int i = 0; i<m; i++)
    {
        int ra = find_(G[i].u);
        int rb = find_(G[i].v);
        if(ra!=rb)
        {
            root[ra] = rb;
            G[i].vis = 1;
            ans += G[i].w;
            
			//遍历两个不同集合中的每个点,更新他们之间的最长边
            //ra集合里每个点 到rb集合里每个点 的最长边就是当前的w,
            //因为kruskal是从小到大遍历每条边的,前面遍历的边一定比当前边小
            for(int j = 0; j<p[ra].size(); j++)
                for(int k = 0; k<p[rb].size(); k++)
                {
                    int u = p[ra][j], v = p[rb][k];
                    mxd[u][v] = mxd[v][u] = G[i].w;
                }

            //再把两个集合合在一起
            for(int j = 0; j<p[ra].size(); j++)
                p[rb].push_back(p[ra][j]);
        }
    }
    for(int i = 0; i<m; i++)
        if(!G[i].vis)//找不在最小生成树里的边
            sec_ans = min(sec_ans, ans - mxd[G[i].u][G[i].v] + G[i].w);//用当前边替换当前边两端点之间最长边
    printf("最小生成树权值和:%d\n", ans);
    printf("次小生成树权值和:%d\n", sec_ans);
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 0; i<m; i++)
        {
            scanf("%d%d%d",&G[i].u, &G[i].v, &G[i].w);
            G[i].vis = 0;
        }
        kruskal();
    }
    return 0;
}

测试案例

input
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2

output
最小生成树权值和:3
次小生成树权值和:4

最小生成树权值和:6
次小生成树权值和:6

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值