最小生成树

一、定义

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 }

 

posted @ 2017-12-03 22:50  ColdCode  阅读(388)  评论(0编辑  收藏  举报
AmazingCounters.com