生成树——Kruscal & Prim
关于树
MST——最小生成树
- 无向无环的联通图
- 任意两个结点之间有且仅有一条简单路径的无向图
- 任何边均为桥的连通图
- 没有圈,且在任意不同两点见添加一条边之后所得图含唯一的一个圈的图
Kruscal
适合简单图
原理
不断加边
- 认定每个点都是一个独立的集合
- 按照边的权值检查每一条边
- 如果该边连接两个不同的集合,将边加入MST,合并两个集合
- 集合的判定和合并使用并查集
图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGEma39r-1628932722088)(https://i.loli.net/2021/08/02/Vkvg8dHDKIZ4Xln.png)]
代码模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2*1e6;
int n,m;//点,边
int ans=0;//权值和
int sum=0;//边数
int fat[maxn];//集合
struct node//结构体存边
{
int u,v,w;
}edge[maxn];
bool cmp(const node &a,const node &b){//按权值sort
return a.w<b.w;
}
/*递归
int find_set(int x){//查找是否在集合
if(fat[x]!=x)
return find_set(fat[x]);
else
return x;
}
*/
int find_set(int x){//查找是否在集合
if(fat[x]!=x)
fat[x] = find_set(fat[x]);
return fat[x];
}
int kruscal(){
sort(edge+1,edge+m+1,cmp);//按权值排序
for(int i=1;i<=n;i++)
fat[i] = i;//先认定每个人都是一个集合
for(int i=1;i<=m;i++){
//合并
int x=find_set(edge[i].u);
int y=find_set(edge[i].v);
//如果两点在同一个集合中,说明已经连通,则不能选择
if(x==y)
continue;
//不连通的话,合并集合
fat[x]=y;
sum++;//增加边数
ans += edge[i].w;//增加边权
if(sum == n-1)//n个点n-1条边
{
cout << ans << endl;
return 0;
}
}
cout<<"orz"<<endl;//图不连通
return 0;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>edge[i].u>>edge[i].v>>edge[i].w;
kruscal();
return 0;
}
Prim
适合稠密图
原理
不断加点
- 选择任意点为根
- 找不在树上的离树最近的点加入生成树
图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQY7LsfK-1628932722089)(https://i.loli.net/2021/08/02/IpfgBm1aH9XEYDO.png)]
代码模板
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2*1e6,inf=1e9+7;
int n,m;//点,边
int cnt=0;
int sum=0;//边数
int ans=0;//权值和
int head[maxn];
int dis[maxn];//距离
bool vis[maxn];//标记
struct node
{
int v,w,next;
}edge[maxn];
//链式前向星存边
void add_edge(int u,int v,int w){
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
/*
随机选择一个点 将它的dist变为0(我选择的是1号节点),并将它标记(因为最小生成树每个点只能用一次)
然后我们遍历与这个点相连的点 将它们的dist更新(更新为它们的边权值)
现在 我们得到了多个dist不为0x7f7ff的点了
于是 我们选择其中dist最小的点 标记它 然后再遍历与它相连的点,更新它们的dist
不断的重复 重复 直到所有点都被标记为止
当然 也存在orz的情况,这时 被标记的点的数量是小于n的,加个判断就好啦
*/
void prim(){
for(int i=1;i<=n;i++)
dis[i] = inf;//没到的距离均为inf
dis[1] = 0;//起始点距离为0
for(int j=1;j<=n;j++){
int now=-1,minn=inf;
for(int i=1;i<=n;i++){
if(!vis[i] && minn>dis[i])
{
minn = dis[i];
now = i;
}
}
if(now==-1){//是否联通标记
ans=-1;
return;
}
vis[now]=1;
ans += dis[now];
for(int i=head[now];i!=-1;i=edge[i].next){
int v = edge[i].v;
if(!vis[v] && dis[v]>edge[i].w)
dis[v]=edge[i].w;
}
}
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
add_edge(v,u,w);
}
prim();
//cout<<sum<<endl;
if(ans==-1)
cout<<"orz"<<endl;
else
cout<<ans<<endl;
return 0;
}