最小生成树(Prim和Kruskal)

本文详细介绍了Prim算法和Kruskal算法在求解最小生成树问题中的应用。Prim算法基于顶点集合进行迭代,每次选择最短边连接新顶点至已有的树结构,避免形成回路。Kruskal算法则基于边集,按权值排序逐个考虑边,通过并查集判断和合并顶点集合,直至所有顶点连通。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Prim:

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

算法描述

1).输入:一个加权连通图,其中顶点集合为V,边集合为E;

2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;

3).重复下列操作,直到Vnew = V:

a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;

4).输出:使用集合Vnew和Enew来描述所得到的最小生成树

以上来源与百度百科,Prim算法通俗来说就是在V这个顶点集合中找出一个点加入到V1集合中,再从V中剩下的顶点中找出离集合V1最近的顶点加到集合V1中,这么做有一个问题是不能判断是否形成了回路,在最小生成树中不允许出现回路,此时可以通过维护两个数组,colsest[i]和lowcost[i],分别表示剩余集合中的节点i到v节点最近的节点closest和最短的距离lowcost[i]。

代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int INF = 0x3fffffff;
  4. const int N = 100;
  5. bool s[N];
  6. int closest[N];
  7. int lowcost[N];
  8. void Prime(int n, int u0, int c[N][N])
  9. {
  10.     int i, j;
  11.     s[u0] = true;//访问
  12.     for (i = 1; i <= n; i++)//初始化
  13.     {
  14.         if (i == u0)
  15.         {
  16.             lowcost[i] = 0;
  17.         }
  18.         else
  19.         {
  20.             lowcost[i] = c[u0][i];
  21.             s[i] = false;//没被访问
  22.             closest[i] = u0;
  23.         }
  24.     }
  25.     for (i = 1; i <= n; i++)
  26.     {
  27.         int temp = INF;
  28.         int t = u0;
  29.         for (j = 1; j <= n; j++)
  30.         {
  31.             if ((!s[j]) && lowcost[j] < temp)
  32.             {
  33.                 t = j;
  34.                 temp = lowcost[j];//当前最小
  35.             }
  36.         }
  37.         s[t] = true;
  38.         for (j = 1; j <= n; j++)
  39.         {
  40.             if ((!s[j]) && c[t][j] < lowcost[j])
  41.             {
  42.                 lowcost[j] = c[t][j];
  43.                 closest[j] = t;//离j最近的是t
  44.             }
  45.         }
  46.     }
  47. }
  48. int main()
  49. {
  50.     int i, j;
  51.     int n, c[N][N], m, u, v, w, sumcost = 0;;
  52.     int u0;
  53.     cin >> n >> m;
  54.     for (i = 1; i <= n; i++)
  55.     {
  56.         for (j = 1; j <= n; j++)
  57.         {
  58.             c[i][j] = INF;
  59.         }
  60.     }
  61.     for (i = 1; i <= m; i++)
  62.     {
  63.         cin >> u >> v >> w;
  64.         c[u][v] = c[v][u] = w;
  65.     }
  66.     cin >> u0;
  67.     Prime(n, u0, c);
  68.     for (i = 1; i <= n; i++)
  69.     {
  70.         cout << lowcost[i] << " ";
  71.         sumcost += lowcost[i];
  72.     }
  73.     cout << endl << sumcost << endl;
  74.     return 0;
  75. }

Prim算法是基于顶点集合是最小生成树算法,时间复杂度为O(n^2),辅助空间为O(n),可以通过找集合V到V1最短边用优先队列,边判断用邻接表,可以将时间复杂度降到O(Elogn);

另一个最小生成树算法,Kruskal算法是基于边集的最小生成树算法

1.概览

Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

 

2.算法简单描述

1).记Graph中有v个顶点,e个边

2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

3).将原图Graph中所有e个边按权值从小到大排序

4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中(同一集合中算法结束)

通俗来讲,该算法是找权值最小的边,如果权值最小的边的两个顶点不再同一个集合,就将该边的权值累加到最终结果中去,并且将两个顶点合并成一个集合,算法过程就是找最小->判断->合并->找最小->判断->合并。。。。直到所有顶点合并成一个集合算法结束。该算法的时间复杂度为O(n^2),在合并的时候产生的,可以通过用并查集将时间复杂度降到O(Elogn)

代码:

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. const int N = 100;
  5. int n, m;
  6. int nodes[N];//节点数组
  7. struct Edge
  8. {
  9.     int u;
  10.     int v;
  11.     int w;
  12. }e[N*N];
  13. bool cmp(Edge x, Edge y)
  14. {
  15.     return x.w < y.w;
  16. }
  17. void init(int n)
  18. {
  19.     for (int i = 1; i <= n; i++)
  20.     {
  21.         nodes[i] = i;
  22.     }
  23. }
  24. int Merge(int a, int b)
  25. {
  26.     int p = nodes[a];
  27.     int q = nodes[b];
  28.     if (p == q)
  29.     {
  30.         return 0;
  31.     }
  32.     for (int i = 1; i <= n; i++)
  33.     {
  34.         if (nodes[i] == p)
  35.         {
  36.             nodes[i] = q;
  37.         }
  38.     }
  39.     return 1;
  40. }
  41. int Kruskal(int n)
  42. {
  43.     int i,ans = 0;
  44.     for (i = 1;i <= m; i++)
  45.     {
  46.         if (Merge(e[i].u, e[i].v))//可以合并
  47.         {
  48.             ans += e[i].w;
  49.             n--;
  50.             if (n == 1)
  51.             {
  52.                 return ans;
  53.             }
  54.         }
  55.     }
  56.     return 0;
  57. }
  58. int main()
  59. {
  60.     int i;
  61.     cin >> n >> m;
  62.     init(n);
  63.     for (i = 1; i <= m; i++)
  64.     {
  65.         cin >> e[i].u >> e[i].v >> e[i].w;
  66.     }
  67.     sort(e, e + m, cmp);
  68.     int ans = Kuskal(n);
  69.     cout << ans << endl;
  70.     return 0;
  71. }

并查集优化后的代码:

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. #define N 100
  5. int father[N];
  6. int n, m;//顶点数和边数
  7. struct Edge{
  8. int u, v, w;
  9. }e[N*N];
  10. void init(int n)
  11. {
  12.    for (int i = 1; i <= n; i++)
  13.    {
  14.         father[i] = i;
  15.     }
  16. }
  17. int Find(int x)
  18. {
  19.     if (x != father[x])
  20.    {
  21.         father[x] = Find(father[x]);
  22.     }
  23.     return father[x];
  24. }
  25. int Merge(int u, int v)
  26. {
  27.     int p = Find(u);
  28.     int q = Find(v);
  29.     if (p == q)return 0;
  30.     if (p > q)
  31.     {
  32.         father[p] = q;
  33.     }
  34.     else
  35.     {
  36.         father[q] = p;
  37.     }
  38. }
  39. bool cmp(Edge x, Edge y)
  40. {
  41.     return x.w < y.w;
  42. }
  43. int Kruskal(int n)
  44. {
  45.     int i, ans = 0;
  46.     for (i = 1; i <= m; i++)
  47.     {
  48.         if (Merge(e[i].u, e[i].v))
  49.         {
  50.             ans += e[i].w;
  51.             n--;
  52.             if (n == 1)return ans;
  53.         }
  54.     }
  55.     return 0;
  56. }
  57. int main()
  58. {
  59.     int i;
  60.     cin >> n >> m;
  61.     init(n);
  62.     for (i = 1; i <= m; i++)
  63.     {
  64.         cin >> e[i].u >> e[i].v >> e[i].w;
  65.     }
  66.     sort(e, e + m, cmp);
  67.     int ans = Krukal(n);
  68.     cout << ans << endl;
  69.     return 0;
  70. }

 

Prim算法Kruskal算法都是用于最小生成树的经典算法Prim算法的基本思想是从一个点开始,每次选择一个与当前生成树距离最近的点加入生成树中,直到所有点都被加入生成树为止。具体实现时,可以使用一个优先队列来维护当前生成树与未加入生成树的点之间的距离,每次选择距离最小的点加入生成树中。 Kruskal算法的基本思想是从边开始,每次选择一条权值最小且不会形成环的边加入生成树中,直到生成树中包含所有点为止。具体实现时,可以使用并查集来判断是否形成环。 下面是Prim算法Kruskal算法的C语言代码实现: Prim算法: ```c #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MAX_VERTICES 1000 int graph[MAX_VERTICES][MAX_VERTICES]; int visited[MAX_VERTICES]; int dist[MAX_VERTICES]; int prim(int n) { int i, j, u, min_dist, min_index, sum = 0; for (i = 0; i < n; i++) { visited[i] = 0; dist[i] = INT_MAX; } dist[0] = 0; for (i = 0; i < n; i++) { min_dist = INT_MAX; for (j = 0; j < n; j++) { if (!visited[j] && dist[j] < min_dist) { min_dist = dist[j]; min_index = j; } } u = min_index; visited[u] = 1; sum += dist[u]; for (j = 0; j < n; j++) { if (!visited[j] && graph[u][j] < dist[j]) { dist[j] = graph[u][j]; } } } return sum; } int main() { int n, m, i, j, u, v, w; scanf("%d%d", &n, &m); for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { graph[i][j] = INT_MAX; } } for (i = 0; i < m; i++) { scanf("%d%d%d", &u, &v, &w); graph[u][v] = graph[v][u] = w; } printf("%d\n", prim(n)); return 0; } ``` Kruskal算法: ```c #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MAX_VERTICES 1000 #define MAX_EDGES 1000000 struct edge { int u, v, w; }; int parent[MAX_VERTICES]; struct edge edges[MAX_EDGES]; int cmp(const void *a, const void *b) { return ((struct edge *)a)->w - ((struct edge *)b)->w; } int find(int x) { if (parent[x] == x) { return x; } return parent[x] = find(parent[x]); } void union_set(int x, int y) { parent[find(x)] = find(y); } int kruskal(int n, int m) { int i, sum = 0; for (i = 0; i < n; i++) { parent[i] = i; } qsort(edges, m, sizeof(struct edge), cmp); for (i = 0; i < m; i++) { if (find(edges[i].u) != find(edges[i].v)) { union_set(edges[i].u, edges[i].v); sum += edges[i].w; } } return sum; } int main() { int n, m, i; scanf("%d%d", &n, &m); for (i = 0; i < m; i++) { scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w); } printf("%d\n", kruskal(n, m)); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值