最小生成树

问题引入

考虑如下问题:有n个城市,要在这n个城市之间修建道路,显然只要修建n-1条路就能让任意两个城市连通,但每两个城市之间修建道路所花的费用不同,该如何选择使花费最少?

这个问题就是在具有n个点的连通图中选择n-1条边使图依然连通,并且花费最少,也就是构造图的最小生成树

常用算法: 普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法

Prim算法

算法描述

普里姆算法(Prim)先将图中任意一点加入空集U中,然后在 U与V-U的点之间的所有边中找到代价最小的边,选中,并把端点 加入U中,依次进行,直到选出n-1条边。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int N=55;
const int inf=1e9;
struct Graph{
	int vum;
	int arcs[N][N];//邻接矩阵 
};
void prime(Graph &G){
	int n=G.vum,ans=0;//顶点个数,最小代价和 
	vector<bool> vis(n,false);//是否被访问过 
	vector<int> dis(n,inf);//当前剩下的结点到最小生成树的距离 
	vis[0]=true;//顶点0加到最小生成树中去 
	for(int i=1;i<n;i++){//距离初始化 
		dis[i]=G.arcs[0][i];//邻接矩阵中各个顶点到0之间的距离 	
    }
	for(int i=1;i<n;i++){
		int m=inf,j=0;//到最小生成树的的最小距离 最小距离顶点的编号 
		for(int k=0;k<n;k++){ 
			if(!vis[k]&&dis[k]<m){//如果k没被访问过,并且k到最小生成树的距离小于最小距离 
				m=dis[k];//更新最小距离 
				j=k;//更新顶点编号; 
			}
		}
		if(j==0){
			cout<<"不存在最小生成树,图不连通"<<endl;
		}
		ans+=m;
		vis[j]=true;
		for(int k=0;k<n;k++){
			if(G.arcs[j][k]<inf && !vis[k]){//如果其他结点到j的距离存在并且没有访问 
				dis[k]=min(dis[k],G.arcs[j][k]);//最新的距离就是原来距离和到j距离的的最小值 
			}
		}
	}
	cout<<ans<<endl;
}
int main(){
	Graph G;
	cin>>G.vum;
	for(int i=0;i<G.vum;i++){//输入邻接矩阵 
		for(int j=0;j<G.vum;j++){
			cin>>G.arcs[i][j];
			if(G.arcs[i][j]==0){
				G.arcs[i][j]=inf;
			}
		}
	}
	prime(G);
	return 0;
}

//输入:
//4
//0 2 4 0
//2 0 3 5
//4 3 0 1
//0 5 1 0
//输出:
//6

Kruskal算法

算法描述

 克鲁斯卡尔(Kruskal)算法采用贪心思想,按边权值从小到大开始寻找,如果该边的两个端点属于不同的连通分量,则加入该边,否则舍弃该边。

并查集的应用

Kruskal算法主要利用并查集的思想:即在升序的边的集合中依次挑选最小的边,并检查该边的两个端点是否属于同一个集合,如果不属于则可以将两个端点合并到一个集合中去,并选取这条边;如果属于则不能合并,即不能选取这条边 。

例子:

代码实现

#include<bits/stdc++.h>
using namespace std;
const int N=105;//边的上限
struct edge{
	int x,y;//顶点
	int w;//权重 
}; 

int fa[N],n;//并查集数组 实际顶点数
int getf(int x){//查询一个顶点在并查集中属于哪一个类别 
	return x==fa[x] ? x:fa[x]=getf(fa[x]);//如果相等,则它是最终类别;如果不是,则看它和谁一个类别,逐层查下去,直到查到最终类别。 
}
void Kruskal(vector<edge> &e){
	int ans=0;
	for(int i=0;i<n;i++){//并查集初始化 
		fa[i]=i;
	}
	//核心代码 
	for(int i=0;i<e.size();i++){//e已经从小到大排序好了 
		int fx=getf(e[i].x),fy=getf(e[i].y);//看每一条边的两个顶点是否属于同一类别
		if(fx!=fy){
			ans+=e[i].w;
			fa[fx]=fy;//将该顶点所属类别fx归并到fy中去,即x的最终类别就是fy了 
		} 
	}
	cout<<ans;
} 
bool cmp(edge a,edge b){//因为结构体是新定义的,所以要写一个比较函数 
	return a.w<b.w;
}
int main(){
	while(cin>>n && n){
		int m=n*(n-1)/2;//边的最大数目:完全图 
		vector<edge> e(m);//开最大的向量,防止越界 
		for(int i=0;i<m;i++){
			cin>>e[i].x>>e[i].y>>e[i].w;
			if(e[i].x==0 && e[i].y==0 && e[i].w==0){//输入结束的条件 
				break;
			}
		} 
		sort(e.begin(),e.end(),cmp);
		Kruskal(e);
	}
	return 0;
} 
//输入 
//6
//1 2 6
//1 3 1
//1 4 5
//2 3 5
//2 5 3
//3 6 4
//3 4 6
//3 5 6
//4 6 2
//5 6 6
//0 0 0
//输出 
//15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值