最小生成树的两种方法:Prim和Kruskal。

Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。Prim是以更新过的节点的连边找最小值,Kruskal是直接将边排序。

Prim

Prim的思想是将任意节点作为根,再找出与之相邻的所有边(用一遍循环即可),再将新节点更新并以此节点作为根继续搜,维护一个数组:dis,作用为已用点到未用点的最短距离。

说的再通俗一点,每次都循环,把最低的距离的那个点能到的点的距离给更新了,直到不能更新为止。

直接上堆优化罢()

有一种迪杰斯特拉的美感

【模板】最小生成树

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数 \(N,M\),表示该图共有 \(N\) 个结点和 \(M\) 条无向边。

接下来 \(M\) 行每行包含三个整数 \(X_i,Y_i,Z_i\),表示有一条长度为 \(Z_i\) 的无向边连接结点 \(X_i,Y_i\)

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

样例 #1

样例输入 #1

1
2
3
4
5
6
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

样例输出 #1

1
7

提示

数据规模:

对于 \(20\%\) 的数据,\(N\le 5\)\(M\le 20\)

对于 \(40\%\) 的数据,\(N\le 50\)\(M\le 2500\)

对于 \(70\%\) 的数据,\(N\le 500\)\(M\le 10^4\)

对于 \(100\%\) 的数据:\(1\le N\le 5000\)\(1\le M\le 2\times 10^5\)\(1\le Z_i \le 10^4\)

样例解释:

所以最小生成树的总边权为 \(2+2+3=7\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int k,n,m,cnt,sum,ai,bi,ci,head[N],dis[N],vis[N];
struct Edge
{
int v,w,next;
}e[400005];
void add(int u,int v,int w)
{
e[++k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k;
}
//经典链式前向星,不会的话,我这篇写完下一篇就介绍这个()
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
//最小堆,先取出的是最小的意思
void prim()
{
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n)
{
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
sum+=d;
vis[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].w<dis[e[i].v])
{
dis[e[i].v]=e[i].w;
q.push(make_pair(dis[e[i].v],e[i].v));
}
}
}
int main()
{
memset(dis,127,sizeof(dis));
//初始化为无限大
memset(head,-1,sizeof(head));
//链式前向星操作了
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>ai>>bi>>ci;
add(ai,bi,ci);
add(bi,ai,ci);
}
prim();
if (cnt==n)cout<<sum;
else cout<<"orz";
}

Kruskal

Kruskal算法的思想:先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

这个不需要建图

只需要存边就好了

因为只需要边()

3步骤解释:(来自题解)

  1. 快排边长,是为了让每次选的都是所有连接中都能是边长最小的(贪心思想)
  2. 并查集的作用是:判断有没有连成一个环。若两个点在同一个并查集里面,则说明它们在同一个树里,若连接,就会造成一个环
  3. 当到了已连边的个数是点的个数-1时,就要停止循环,因为这个时候,最小生成树已经完成了,所有的并查集都连在了一起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,u,v,total;
struct edge
{
int start;
int to;
long long val;
}bian[100000000];
int f[10000000];
long long ans;
int find(int x)
{
if(f[x]==x)
{
return x;
}
else
{
return f[x]=find(f[x]);
}
}
bool cmp(edge a,edge b)
{
return a.val<b.val;
}
void kruskal()
{
for(int i=1;i<=m;i++)
{
u=find(bian[i].start);
v=find(bian[i].to);
if(u==v)
{
continue;
}
ans+=bian[i].val;
f[u]=v;
total++;
if(total==n-1)
{
break;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
f[i]=i;
}
for(int i=1;i<=m;i++)
{
cin>>bian[i].start>>bian[i].to>>bian[i].val;
}
sort(bian+1,bian+m+1,cmp);
kruskal();
if(total==n-1)
{
cout<<ans;
}
else
{
cout<<"orz";
}
}