回顾
最短路径: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;
}