最小生成树
一、定义
1、生成树
在一个无向连通图中,如果存在一个连通子图包含原图中的所有结点和部分边,且这个子图不存在回路,那么该子图被称为原图的一棵生成树。
2、最小生成树
所有生成树中,边权和最小的那一棵(或那几棵)叫做最小生成树(MST)。
二、构造算法
有两种算法来构造最小生成树:普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法。
三、普里姆(Prim)算法
算法步骤:
1、在图G=(V, E)(V表示顶点,E表示边)中,从集合V中任取一个顶点(起始点)放入集合 U中,这时 U={v0},集合T(E)为空。
2、从v0出发寻找与U中顶点相邻(另一顶点在V中)权值最小的边的另一顶点v1,并使v1加入U。即U={v0,v1 },同时将该边加入集合T(E)中。
3、重复2,直到U=V为止。
这时T(E)中有n-1条边,T = (U, T(E))就是一棵最小生成树。
构造过程:
算法模板(hdoj1233):
1 /** 2 * 假设图中有n个结点,m条边(代码中m=n*(n-1)/2),使用邻接矩阵map[][]存储图, 3 * 求图中最小生成树的边权值。具体输入输出要求参见hdoj1233。 4 */ 5 #include <algorithm> 6 #include <cstring> 7 #include <cstdio> 8 using namespace std; 9 10 const int INF = 0x7fffffff; 11 const int N = 100 + 10; 12 int map[N][N]; 13 int dist[N]; 14 int n; 15 16 void prim() 17 { 18 int min_edge, min_node; 19 for (int i = 1;i <= n;i++) 20 dist[i] = INF; 21 int ans = 0; 22 int now = 1; 23 for (int i = 1;i < n;i++) 24 { 25 dist[now] = -1; 26 min_edge = INF; 27 for (int j = 1;j <= n;j++) 28 { 29 if (j != now && dist[j] >= 0) 30 { 31 if (map[now][j]>0) 32 dist[j] = min(dist[j], map[now][j]); 33 if (dist[j] < min_edge) 34 { 35 min_edge = dist[j]; //min_edge存储与当前结点相连的最短的边 36 min_node = j; 37 } 38 } 39 } 40 ans += min_edge; //ans存储最小生成树的长度 41 now = min_node; 42 } 43 printf("%d\n", ans); 44 } 45 46 int main() 47 { 48 while (scanf("%d", &n) == 1 && n) 49 { 50 memset(map, 0, sizeof(map)); 51 int a, b, c; 52 int nums = n*(n - 1) / 2; 53 for (int i = 0; i < nums; i++) 54 { 55 scanf("%d%d%d", &a, &b, &c); 56 map[a][b] = c; 57 map[b][a] = c; 58 } 59 prim(); 60 } 61 return 0; 62 }
prim算法涉及到两个集合V和U,V包含了图中所有结点,U则包含了当前最小生成树中的结点。上面代码中的数组dist[]存储了从集合U中的结点到集合V-U的结点的最短距离,如果编号为i的结点已经在U中了,则令dist[i]=-1。每次从V-U中选取结点添加到U时,则选取最小dist[]值对应的那个结点,dist[]在循环过程中是不断更新的。
三、克鲁斯卡尔(Kruskal)算法
算法步骤:
1、初始时所有结点属于孤立的集合;
2、按照边权递增顺序遍历所有的边,若遍历到边的两个定点属于不同的集合(该边即为连通这两个集合的边中权值最小的那条),则确定该边为最小生成树上的一条边,并将这条边的两个顶点所属的集合合并;
3、遍历完所有边后,若原图上所有结点属于同一个集合,则原图结点和被选中的边构成最小生成树;否则原图不连通,最小生成树不存在。
构造过程:
算法模板(hdoj1233):
1 /** 2 * 假设图中有n个结点,m(代码中m=n*(n-1)/2)条边,使用邻接矩阵map[][]存储图, 3 * 求图中最小生成树的边权值。具体输入输出要求参见hdoj1233。 4 */ 5 #include <algorithm> 6 #include <cstring> 7 #include <cstdio> 8 #include <vector> 9 using namespace std; 10 11 struct Edge 12 { 13 int a, b, dist; 14 15 Edge() {} 16 Edge(int a, int b, int d) :a(a), b(b), dist(d) {} 17 bool operator < (Edge edge) //按边长从短到长排序 18 { 19 return dist < edge.dist; 20 } 21 }; 22 23 const int N = 100 + 10; 24 int p[N]; //并查集使用 25 vector<Edge> v; 26 int n; 27 28 int find_root(int x) 29 { 30 if (p[x] == -1) 31 return x; 32 else return find_root(p[x]); 33 } 34 35 void kruskal() 36 { 37 memset(p, -1, sizeof(p)); 38 sort(v.begin(), v.end()); 39 int ans = 0; 40 for (int i = 0; i < v.size(); i++) 41 { 42 int ra = find_root(v[i].a); 43 int rb = find_root(v[i].b); 44 if (ra != rb) 45 { 46 ans += v[i].dist; 47 p[ra] = rb; 48 } 49 } 50 printf("%d\n", ans); 51 } 52 53 int main() 54 { 55 while (scanf("%d", &n) == 1 && n) 56 { 57 int a, b, d; 58 int nums = n*(n - 1) / 2; 59 v.clear(); 60 for (int i = 0; i < nums; i++) 61 { 62 scanf("%d%d%d", &a, &b, &d); 63 v.push_back(Edge(a, b, d)); 64 } 65 kruskal(); 66 } 67 }