Kruskal算法和Prim算法构造它的一棵最小代价生成树的过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kruskal算法和Prim算法构造它的一棵最小代价生成树的过程相关的知识,希望对你有一定的参考价值。

Prim算法复杂度:O(n2), 与边无关,适合求边稠密的网的最小生成树。

算法思想:假设N=V,E是连通网,TE是N上最小生成树中边的集合。算法从U=u0,TE =开始,重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止。

Kruskal算法复杂度:O(eloge),相对于Prim而言,适合求边稀疏的网的最小生成树。

算法思想:最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,),图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去次边而选择下一条代价最小的边。直至T中所有顶点都在同一连通分量上为止。
参考技术A 算法同样是解决最小生成树的问题。

其算法为:在这n个点中的相通的边进行排序,然后不断地将边添加到集合中(体现了贪心的算法特点),在并入集合之前,必须检查一下这两点是不是在一个集合当中,这就用到了并查集的知识。直到边的集合达到了n-1个。

与prim算法的不同:prim算法为单源不断寻找连接的最短边,向外扩展,即单树形成森林。而Kruskal算法则是不断寻找最短边然后不断将集合合并,即多树形成森林。

复杂度的不同:prim算法的复杂度是O(n^2),其中n为点的个数。Kruskal算法的复杂度是O(e*loge),其中e为边的个数。两者各有优劣,在不同的情况下选择不同的算法。

Prim算法用于求无向图的最小生成树

设图G =(V,E),其生成树的顶点集合为U。

①、把v0放入U。

②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。

③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。

其算法的时间复杂度为O(n^2)

Prim算法实现:

(1)集合:设置一个数组set(i=0,1,..,n-1),初始值为 0,代表对应顶点不在集合中(注意:顶点号与下标号差1)

(2)图用邻接阵表示,路径不通用无穷大表示,在计算机中可用一个大整数代替。
先选定一个点,然后从该点出发,与该点相连的点取权值最小者归入集合,然后再比较在集合中的两点与其它各点的边的权值最小者,再次进入集合,一直到将所有的点都归入集合为止。

最小生成树

一、定义

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 }

 

以上是关于Kruskal算法和Prim算法构造它的一棵最小代价生成树的过程的主要内容,如果未能解决你的问题,请参考以下文章

最小生成树

最小生成树

最小生成树-kruskal算法

最小生成树Prim算法和Kruskal算法

最小生成树-Prim算法和Kruskal算法

最小生成树(Prim算法和Kruskal算法)