最小生成树- Prim & Kruskal(并查集) (2021-8-10)

回顾

最短路径:Dijkstra, Floyd, Bellman-ford
强连通分量:kosaraju
图的遍历:bfs,dfs
最小生成树:prim, kruskal

最小生成树

重要前提:
从全局考虑,相当于修路,路径要短且要每个点都连通(直接或间接可达)

prim:
出发
(1)给定一个起点,先纳入点域
(2)把和点域相邻的最短边连接的点纳入点域
(3)重复(2)
(4)直到所有点纳入点域
(有点像dijkstra,松弛操作不同,集合到点最短距离)

kruskal:
出发
(邻接表 边集 kruskal )

1.准备工作:建立边集数组
2.对边集数组排序,排序规则:权值从小到大
3.取未被访问过且最小的边(边集数组中第一个未被访问过的边)
4.判断这条边的俩端是否在同一个集合中并查集
是: 舍弃
否:joint

5.最后所有点都放入一个集合中,结束

prim:(邻接矩阵存图)
二维数组 maxn>1e4 会爆栈

模板(感觉好像dijkstra)

//prim邻接矩阵 mp 
//(1)准备工作:设置一个数组 dis 动态变化 
//保存当前点域到其他所有点的距离(直达),
//建图邻接矩阵(二维数组) 
//(2)取dis数组中未被访问过且权值最小的点加入点域,
//并且遍历当前最小的点A点到其他所有顶点(未被访问)
//的距离,是否满足更新dis条件:
//mp[A][x] < dis[x] 
//(3)重复2,直到所有点都被访问过 
 
 
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=1e4+5;
int mp[maxn][maxn];
int dis[maxn];//一个集合到一个点的最短距离 
const int INF = 0x3f3f3f3f;
bool vis[maxn];//该点是否访问过 
void prim(int s,int n){
// 	memset(dis,INF,sizeof(dis));//卡顿 
// 	memset(vis,false,sizeof(vis));
 	dis[s]=0; 
 	int cnt=0;
	while(1){
		int min_dis=INF,min_index=-1;
	
		for(int i=1;i<=n;i++){
			if(!vis[i]&&min_dis>dis[i]){
				min_dis=dis[i];
				min_index=i;//未被访问过,且权值
			//最小的点的编号  范围[1,n] 
			}
		}
		if(min_index==-1||min_dis==INF){
			break;
		}
		vis[min_index]=true;
		for(int i=1;i<=n;i++){
			if(!vis[i]&&dis[i]>mp[min_index][i]){//不同之处,一个集合到一个点的最短距离
				dis[i]=mp[min_index][i];
			}
		}
 		cout<<(++cnt)<<"->";
 		for(int i=1;i<=n;i++){
 			cout<<dis[i]<<" ";
		 }
 		cout<<endl;
	}
	
 	return;
}
int main(){
	int n,m;
	while(cin>>n>>m){
		for(int i=0;i<=n;i++){//最初设为每个点之间不可达 
			dis[i]=INF;
			vis[i]=false;
			for(int j=0;j<=n;j++){
				mp[i][j]=INF;
			}
		}
		int u,v,val;
		while(m--){
			cin>>u>>v>>val;
			mp[u][v]=val;//双向 
			mp[v][u]=val;
		}//建图
		prim(1,n); //随机起点 
	}
	
	return 0;
}

//6 10
//1 2 6
//1 4 5
//1 3 1
//2 5 3
//2 3 5
//3 4 5
//3 6 4
//3 5 6
//4 6 2
//5 6 6

并查集:
1.查(find):查找俩个元素是否在一个集合
2.并(join):合并俩个元素所在的集合

查:(压缩版)
1.是自己直接返回
2.否则,往上找,同时将每级祖先置为自己的父亲

int find(int x){
	return p[x]==x ? x: p[x]=find(p[x]);//压缩版路径查找 找父亲找爷爷作为根节点... 
}

并:(p数组为祖先)

int up=find(u);
		int vp=find(v);
		if(up!=vp){
			p[up]=vp;
		}

模板:
注意:
(1)一定要记得初始化,每个点为一个集合
(2)求几个集合就是求这些点一共有几个祖先

#include<iostream>
#include<set>
using namespace std;
const int maxn=1e5;
int p[maxn];
void init(int n){
	for(int i=1;i<=n;i++){//初始化为一个个独立的集合 
		p[i]=i;
	}
}
int find(int x){
	return p[x]==x ? x: p[x]=find(p[x]);//压缩版路径查找 找父亲找爷爷作为根节点... 
}
int main(){
	int n,m;
	cin>>n>>m;
	int u,v;
	init(n); 
	while(m--){
		cin>>u>>v;
		int up=find(u);
		int vp=find(v);
		if(up!=vp){
			p[up]=vp;
		}	 
	}
	set<int> ans;
	for(int i=1;i<=n;i++){
		ans.insert(find(i));//有几个集合 (找的是这个图有几个祖先) 
	}
	cout<<ans.size()-1<<endl;//至少加几条边使之成为一个集合 
	return 0;
}

kruskal:(结构体数组存图)
注意:并查集的初始化,查找和归纳

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
const int maxm=5e5+5; 
struct Edge{
	int from;
	int to;
	int val;
};
Edge edges[maxm];//边集 结构体数组 
bool cmp(Edge left,Edge right){
	return left.val<right.val;
}
int p[maxn];//祖先 
void init(int n){ //并查集 
	for(int i=1;i<=n;i++){
		p[i]=i;
	} 
}
int find(int x){
	return x==p[x]? x: p[x]=find(p[x]);
}
void kruskal(int n,int m){
	sort(edges,edges+m,cmp);//边集从小到大排序 
	init(n);
	int sum=0;
	for(int i=0;i<m;i++){//取出未被访问的最小边集,若起点终点不在一个集合 
		int up=find(edges[i].from);//则加入此边并且连通集合,否则舍弃 
		int vp=find(edges[i].to);
		if(up!=vp){
			p[up]=vp;//joint
			sum+=edges[i].val;
		}
	}//所有点都放入一个集合,结束 
	cout<<sum<<endl;//总权值
}
int main(){
	int n,m;
	cin>>n>>m;
	int u,v,val;
	for(int i=0;i<m;i++){
		cin>>u>>v>>val;
		edges[i].from=u;
		edges[i].to=v;
		edges[i].val=val;
	}
	kruskal(n,m);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值