次小生成树 Prim

Graph 图论

次小生成树 Prim

我们最常见的是最小生成树,我们最长使用的是Prim和Kruskal算法,一个是对点进行处理,一个是对边进行处理,对于次小生成树来说我们同样可以使用这两个方法进行处理,这里我们先以Prim的写法为例。
首先我们要清楚的是什么是最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。所以在这之前我们需要先了解几个结论:
如果我们要换掉其中的一条边的话新得到的生成树的权值之和一定小于等于原最小生成树的权值之和;
证明:换掉一条边之后得到的生成树的权值之和小于原最小生成树了,那么换掉之后的应该才是最小生成树,与定义不符,但是因为最小生成树不一定唯一,所以用可能等于。
如果我们在最小生成树的基础上任意再加入一条边,会形成一个环;
证明:最小生成树的边有(V - 1)条,如果再加入一条边就有(V)条边了,(V)个顶点(V)条边一定至少会形成一个环,因为一条边连接两个点,如果有 N 条边,那么就用到了 “2N” 个点,因为我们有 N 个点,假设我们(N-1)个点只使用了一次,即所有的点都连在了一个点上,即就有(N-1)条边连在了中心那个点上,而我们又多出来了一条边,所以肯定会形成一个环;
有了上述的两个结论我们再来想次小生成树,我们先得到一个最小生成树,然后我们再往里面加入一条我们在构建这个最小生成树的时候没有用到的边,这样就形成了一个环,然后我们再把形成的这个环中最大的那条边(不是我们新加入的那条)删除掉,这样我们就得到了一个新的生成树,这个新的生成树的权值之和一定大于等于原最小生成树之和,我们只需要把剩余的边遍历一遍找出那个最小的新生成的最小生成树即可。
我们会发现这个地方有一点难实现的地方,就是如果我们要新加入两个边,难道我们要先用BFS找到这个环,然后再找到这个环的最大值吗?且不说是否容易实现,单单是时间复杂度我们就难以承受,所以我们肯定不会这样实现在竞赛考试中(有兴趣的同学可以写写试试这种“傻”方法,相信写完之后对递归的理解肯定有一个很大的提升),这一步我们完全可以用一个maxx数组来实现:
maxx[i][j]:在我们得到的最小生成树中从 i 到 j 最大的那条边的长度,那么我们再加入边的时候就可以直接得到我们需要替换掉的那条最大的边的长度了:
//sum:最小生成树的权值之和
//used[i][j]:i到j的边是否是最小生成树中的边
//cost[i][j]:边i到j的权值,如果i到j不存在边就是INF

	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(!used[i][j] && cost[i][j]!=INF){
				if(sum-maxx[i][j]+cost[i][j]<ans){//删除最大的那条边,加入一新的边
					ans = sum-maxx[i][j]+cost[i][j];
				}
			}
		}
	}

大致实现过程:
1、遍历dist数组,找到dist最小的那个点(跟Prim一样),把这个点标记为已经访问过,然后把这条边加入到最小生成树中(让这条边的used变成true),在这里我们需要使用到的pre数组,来存储这条边是由哪个点到达的,即pre[i]到 i 就是我们所加入的这条边;
2、对maxx数组进行更新,之前都是其他点到pre[i]的最大距离,现在要看加上这条边之后有没有最这些边有更新(只看被访问过的点),如果这个点没被访问过并且cost[p][j]<dist[j](边能够被更新),那么就更新dist数组并且更新pre数组(类似于存储路径的操作)。
3、遍历每一条边,如果这条边没有被访问过,假设这条边连接的是a和b,那么我们就把这条边加上,删去maxx[a][b],依次找更新过的最小值,最后的就是次小生成树的权值。

#include<iostream>
#include<cstring> 
using namespace std;
const int MAXN = 1001;
const int INF = 0x7f7f7f7f;
bool vis[MAXN];
int dist[MAXN];
int pre[MAXN];
int maxx[MAXN][MAXN];
bool used[MAXN][MAXN];
int cost[MAXN][MAXN];
int n,m;
int Prim(){
	int ans = 0;
	memset(vis,false,sizeof(vis));
	memset(maxx,0,sizeof(maxx));
	memset(cost,INF,sizeof(cost));
	memset(used,false,sizeof(used));
	memset(dist,INF,sizeof(dist));
	vis[1] = true;
	pre[1] = -1;
	dist[1] = 0;
	for(int i=2;i<=n;i++){
		dist[i] = cost[1][i];
		pre[i] = 1;
	}
	for(int i=1;i<n;i++){
		int minc = INF;
		int p = -1;
		for(int j=1;j<=n;j++){//找当前最小的dist 
			if(!vis[j] && dist[j]<minc){
				minc = dist[j];
				p = j;
			}
		}
		if(p == -1){
			return -1;
		}
		ans += minc;//最小生成树 
		vis[p] = true;
		used[p][pre[p]] = used[pre[p]][p] = true;
		for(int j=1;j<=n;j++){
			if(vis[j]){
				maxx[j][p] = maxx[p][j] = max(maxx[j][pre[p]],dist[p]);
			}
			if(!vis[j] && cost[p][j]<dist[j]){
				dist[j] = cost[p][j];
				pre[j] = p;
			}
		}
	}
	return ans;
}
int main(){
	cin >> n >> m;
	memset(cost,INF,sizeof(cost));
	for(int i=0;i<m;i++){
		int a,b,c;
		cin >> a >> b >> c;
		cost[a][b] = cost[b][a] = c;
	}
	int sum = Prim();
	int ans = INF;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(!used[i][j] && cost[i][j]!=INF){
				if(maxx[i][j]!=cost[i][j] && sum-maxx[i][j]+cost[i][j]<ans){
					ans = sum-maxx[i][j]+cost[i][j];
				}
			}
		}
	}
	cout << ans << endl;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值