INTRODUCTION
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
--百度百科
概述
在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得
的 w(T) 最小,则此 T 为 G 的最小生成树。
最小生成树其实是最小权重生成树的简称。
--百度百科
最小生成树的性质:
最小生成树性质:设G=(V,E)是一个连通网络,U是顶点集V的一个非空真子集。若(u,v)是G中一条“一个端点在U中(例如:u∈U),另一个端点不在U中的边(例如:v∈V-U),且(u,v)具有最小权值,则一定存在G的一棵最小生成树包括此边(u,v)。--百度百科
通俗来说
对于一个连通图,若已经求出最小生成树A,则若能找出最小生成树B与A不同
则这个图中,至少有两条边边权相等,且两条边在同一个环上,删去这两条边中的任意一条不影响原图的连通性,
且在生成树A,B中等边权的边的数量相等
推论
对于一个连通图,将他制成一颗生成树,使他的最大边最小,则这棵树是该图的最小生成树
不存在比最小生成树中最大边i更小的边j,当j将i替换后原图联通,若替换后仍联通,则j的权值必定比i大
因为在求最小生成树时已经保证最小联通
最小生成树的求法:
稀疏图Kruskal快,稠密图Prim快,根据题意选择使用的算法
当然你也可以选择两个算法都写,在输入时判断一下,如果是稠密图就跑Prim不然的话就跑Kruskal
1,Kruskal
Kruskal 的时间复杂度为 O(mlogm)
基本思路
I 设最小生成树为 T = (V,ET),设置边集 ET 的初始状态为空集。 I 将 E 中的边按权值从小到大排序;从小到大开始依次选取边。 I 若选取的边使生成树 T 不形成回路(即仍然是一棵树或森林),则把 它加入 ET 中,保留作为 T 的一条边;否则将其舍弃。 I 如此进行下去,直到 ET 中包含 n−1 条边为止。最后的 T 即为最小 生成树
大致就是说将每一条边按照边权从小到大排序,之后按这个顺序扫一遍,若这条边的起点和终点尚未联通或间接联通,那加上这条边,把这两个点连起来,那么怎么判断联通呢?自然要靠并查集了
1 #include<iostream>
2 #include<cstdio>
3 #include<algorithm>
4 #include<vector>
5 using namespace std;
6 int f[100086];
7 struct edge
8 {
9 int from;
10 int to;
11 int weight;
12 };
13 int n, m;
14 int tot;
15 long long ans;
16 vector<edge>e;
17 int find(int x)//普通的并查集
18 {
19 if (f[x] == x)
20 return x;
21 else
22 return f[x] = find(f[x]);
23 }
24 void merge(int a, int b)
25 {
26 int x = find(a);
27 int y = find(b);
28 if (f[x] != f[y])
29 f[y] = x;
30 }
31 bool cmp(edge a, edge b)
32 {
33 return a.weight < b.weight;
34 }
35 int main()
36 {
37 cin >> n >> m;
38 for (int i = 1; i <= n; i++)
39 f[i] = i;//并查集初始化
40 for (int i = 1; i <= m; i++)
41 {
42 int x, y, z;
43 cin >> x >> y >> z;
44 edge t;
45 t.from = x;
46 t.to = y;
47 t.weight = z;
48 e.push_back(t);
49 t.from = y;
50 t.to = x;
51 e.push_back(t);
52 }
53 sort(e.begin(), e.end(), cmp);
54 for (int i = 0; i < e.size(); i++)
55 {
56 if (find(e[i].from) != find(e[i].to))//若你们俩没连在一起,那就连一下
57 {
58 merge(e[i].from, e[i].to);//合并并查集
59 ans += e[i].weight;
60 }
61 }
62 cout << ans << endl;
63 return 0;
64 }
2,Prim
Prim 的时间复杂度为 O(n2)
基本思路
设置一个顶点的集合 S 和一个边的集合 ET,S 和 ET 的初始状态均 为空集。 I 选定图中的任意一个顶点 K,从 K 开始生成最小生成树:将 K 加入 到集合 S; I 重复下列操作,直到选取了 n−1 条边:选取一条权值最小的边 x−y, 其中 x∈S, y̸∈S;将顶点 y 加入集合 S,边 x−y 加入集合 ET。 I 得到最小生成树 T = (S,ET)。
1 #include<iostream>
2 #include<algorithm>
3 using namespace std;
4 struct edge
5 {
6 int from;
7 int to;
8 int weight;
9 int next;
10 }e[500005];
11 const int inf = 20041001;
12 int head[100086];
13 int dis[100086];
14 int vis[100086];
15 int tot;
16 long long ans;
17 int n, m;
18 void add(int x, int y, int z)
19 {
20 tot++;
21 e[tot].to = y;
22 e[tot].from = x;
23 e[tot].next = head[x];
24 e[tot].weight = z;
25 head[x] = tot;
26 }
27 long long prim()
28 {
29 for (int i = 2; i <= n; i++)
30 dis[i] = inf;
31 for (int i = head[1]; i; i = e[i].next)
32 dis[e[i].to] = min(dis[e[i].to], e[i].weight);
33 int now = 1;
34 for (int t = 1; t < n; t++)
35 {
36 int minn = inf;
37 vis[now] = 1;
38 for (int i = 1; i <= n; i++)
39 {
40 if (!vis[i] && minn > dis[i])
41 {
42 minn = dis[i];
43 now = i;
44 }
45 }
46 ans += minn;
47 for (int i = head[now]; i; i = e[i].next)
48 {
49 int to = e[i].to;
50 if (dis[to] > e[i].weight && !vis[to])
51 dis[to] = e[i].weight;
52 }
53 }
54 return ans;
55 }
56 int main()
57 {
58 cin >> n >> m;
59 for (int i = 1; i <= m; i++)
60 {
61 int x, y, z;
62 cin >> x >> y >> z;
63 add(x, y, z);
64 add(y, x, z);
65 }
66 cout << prim() << endl;
67 return 0;
68 }