最小生成树之Kruskal算法和Prim算法
Kruskal多用于稀疏图,prim多用于稠密图。
根据图的深度优先遍历和广度优先遍历,可以用最少的边连接所有的顶点,而且不会形成回路。这种连接所有顶点并且路径唯一的树型结构称为生成树或扩展树。实际中,希望产生的生成树的所有边的权值和最小,称之为最小生成树。常见的最小生成树算法有Kruskal算法和Prim算法。
Kruskal算法
n个顶点的图最小生成树步骤如下:
1、边的权值升序排序;
2、选取所有未遍历的边中权值最小的边,判断加入后是否形成回路,若形成回路,放弃之,重新从未被遍历的边中选择。
3、重复上述步骤,直到选中n-1条边。
代码(并查集+kruskal):
#include <iostream> #include <algorithm> using namespace std; #define MAX 105 int N, E, Answer; int u[MAX], v[MAX], w[MAX]; int p[MAX], r[MAX]; int cmp(const int i, const int j) { return w[i] < w[j]; } int find(int x) { return (p[x] == x) ? x : p[x] = find(p[x]); } void kruskal() { int cnt = 0; int x, y; for(int i = 0; i < N; i++) p[i] = i; for(int i = 0; i < E; i++) r[i] = i; sort(r, r+E, cmp); //升序排列 for(int i = 0; i < E; i++) { int e = r[i]; x = find(u[e]); //并查集 y = find(v[e]); if(x != y) //祖先不相同,则不会形成环 { Answer += w[e]; p[x] = y; cnt++; } } if(cnt < N - 1) Answer = 0; } int main() { while(cin >> N >> E, N) { Answer = 0; for(int i = 0; i < E; i++) { cin >> u[i] >> v[i] >> w[i]; } kruskal(); cout << Answer << endl; } }
输入:
3 3 1 2 1 1 3 2 2 3 4 4 6 1 2 1 1 3 4 1 4 1 2 3 3 2 4 2 3 4 5 0 0
Prim算法
算法描述:
普利姆算法求最小生成树时候,和边数无关,只和定点的数量相关,所以适合求稠密网的最小生成树,时间复杂度为O(n*n)。
算法过程:
1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。
2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。
3.递归重复步骤2,直到B集合中的结点为空,结束此过程。
4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。
算法实现具体过程:
1.将第一个点放入最小生成树的集合中(标记visited[i]=1意思就是最小生成树集合)。
2.从第二个点开始,初始化dist[i]为跟1点相连(仅仅相连)的边的权值(dist[i]不是这个点的最小权值!在以后会逐步更新)。
3.找最小权值的边。从第二点开始遍历,如果不是最小生成树的集合的点,则找出从2到n的最小权值(dist[j])。
4.将找出来的最小权值的边的顶点加入最小生成树的集合中(标记visited[i] = 1),权值想加。
5.更新dist[j]集合。假设第一次:dist[2]代表与1相连的点的权值,现在加入了index点。则比较index点与2点的边graph[index][2]和dist[2]的大小,若 dist[2]大,则dist[2] = graph[index][2]。(关键步骤:实质就是每在最小生成树集合中加入一个点就需要把这个点与集合外的点比较,不断的寻找两个集合之间最小的边)
6.循环上述步骤,指导将全部顶点加入到最小生成树集合为止。
代码(OJ_1017):
#include <iostream> #include <cstring> using namespace std; #define INF 0x7fffffff #define MAX 105 int N; int graph[MAX][MAX]; int dist[MAX]; bool visited[MAX]; void prime() { int sum = 0; memset(visited, false, sizeof(visited)); //初始化visited visited[1] = true; for(int i = 1; i <= N; i++) { if(visited[i] == false && dist[i] > graph[1][i]) //初始化dist[i] dist[i] = graph[1][i]; } for(int i = 1; i < N; i++) //找生成树集合点集相连最小权值的边 { int min = INF, index; for(int j = 1; j <= N; j++) { if(visited[j] == false && min > dist[j]) { min = dist[j]; index = j; } } visited[index] = true; //加入最小生成树集合 sum += min; //记录权值之和 for(int j = 1; j <= N; j++) //更新dist数组 { if(visited[j] == false && dist[j] > graph[index][j]) dist[j] = graph[index][j]; } } cout << sum << endl; //最小生成树权值之和 } void InitData() { for(int i = 0; i <= N; i++) { for(int j = 0; j <= N; j++) { graph[i][j] = INF; } dist[i] = INF; } } int main() { int a, b, value; while(cin >> N, N) { InitData(); int m = (N - 1) * N / 2; for(int i = 0; i < m; i++) { cin >> a >> b >> value; graph[a][b] = graph[b][a] = value; } prime(); } }
输入:
3 1 2 1 1 3 2 2 3 4 4 1 2 1 1 3 4 1 4 1 2 3 3 2 4 2 3 4 5 0