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算法构造它的一棵最小代价生成树的过程的主要内容,如果未能解决你的问题,请参考以下文章