最小生成树

最小生成树
EraYes最小生成树
最小生成树(Minimum Spanning Tree,简称 MST)是连接所有节点的边的集合中,权值最小的连通子图的边集合, 即一个加权连通图中,找到一棵生成树,使得所有边的权值之和最小。最小生成树的求解算法主要有两种:
Kruskal 算法
Kruskal 算法也是一种贪心算法,将所有边按照权值`从小到大排序,逐个加入到已有的边集合中,直到所有节点都被覆盖。具体步骤如下:
- 将所有边按照权值从小到大排序。
- 依次将每条边加入到已有的边集合中,如果加入该边后形成了环,则舍弃该边。
- 重复步骤(2),直到所有节点都被覆盖。
- 需要注意的是,无向图的最小生成树可能不唯一,但最小生成树的边数是固定的,即节点数减一。
时间复杂度分析
O(ElogE)或者O(ElogV),其中E代表图中的边的数目,V代表图中的顶点数目。对图中的边按照非降序排列需要O(ElogE)的时间。排序后遍历所有的边并判断添加边是否构成环,判断添加一条边是否构成环最坏情况下需要O(logV),关于这个复杂度等到景禹给你们谈并查集的时候再分析;因此,总的时间复杂度为O(ElogE + ElogV),其中E的值最大为V(V-1)/2,因此O(logV)等于 O(logE)。因此,总的时间复杂度为O(ElogE) 或者O(ElogV)。
代码
- C++
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
using namespace std;
const int maxn = 1e5 + 5;
int fa[maxn];
struct Edge{
int u, v, w;
bool operator<(const Edge &other) const{ //重载操作符
return w < other.w;
}
}e[maxn];
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main(){
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v >> e[i].w;
}
sort(e + 1, e + m + 1);
for (int i = 1; i <= n; i++){
fa[i] = i;
}
int ans = 0, cnt = 0;
for (int i = 1; i <= m; i++){
int fu = find(e[i].u), fv = find(e[i].v);
if (fu != fv){
fa[fu] = fv;
ans += e[i].w;
cnt++;
}
if (cnt == n - 1){
break;
}
}
cout << ans << endl;
// 测试代码
/*
for (int i = 1; i <= n; i++) {
cout << find(i) << " ";
}
cout << endl;
*/
return 0;
}
1 |
|
Prim 算法
Prim 算法是一种贪心算法,它的基本思想是先将图中的所有边按照权值从小到大排序,然后依次选择边,如果该边的两个端点不在同一个集合中,则将该边加入生成树中。最终生成的树即为最小生成树。从任意一个节点开始,每次选择权值最小的一条边,加入到已有的边集合中,直到所有节点都被覆盖。具体步骤如下:
- 从任意一个节点开始,将其加入到已有的节点集合中。
- 选择与已有节点集合距离最小的一条边,将其加入到已有的边集合中。
- 将该边的另一个节点加入到已有的节点集合中。
- 重复步骤(2)和(3),直到所有节点都被覆盖。
- 不同于Kruskal算法,Prim算法每次只考虑一个节点和生成树之间的边,因此在稠密图中效率更高。
时间复杂度分析
上面的代码中,当 i == 1的时候,内层的 while 与 for 循环中 j 的取值范围是从 1 到 n-1,内循环一共计算了 2(n-1) 次,其中n为图中的顶点个数; 当 i == 2 的时候,内层循环还是一共计算 2(n-1)次; 以此类推… i 取最大值 n -1,内层循环还是一共计算2(n-1)次; 所以,整体的执行次数就是(n-1) \* 2(n-1),Prim算法的复杂度是 O(n2)级别的。
代码
- C++
1 |
|
优化
如果要进一步提高Prim算法的效率,可以使用堆优化,将待检查的节点按照权值从小到大加入堆中,这样每次取出堆顶元素时都可以保证该元素是当前权值最小的节点。
参考文献:













