基本概念

(Tree)

如果一个无向连通图中不存在回路,则这种图称为树。

生成树 (Spanning Tree)

无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。

生成树是连通图的极小连通子图。这里所谓极小是指:若在树中任意增加一条边,则将出现一条回路;若去掉一条边,将会使之变成非连通图。

最小生成树

一个带权值的连通图。用$n-1$条边把$n$个顶点连接起来,且连接起来的权值最小

应用场景

设想有9个村庄,这些村庄构成如下图所示的地理位置,每个村庄的直线距离都不一样。若要在每个村庄间架设网络线缆,若要保证成本最小,则需要选择一条能够联通9个村庄,且长度最小的路线。

Kruskal算法

知识点:数据结构——并查集

基本思想

始终选择当前可用、不会(和已经选取的边)构成回路的最小权植边。

具体步骤:

1. 将所有边按权值进行降序排序

2. 依次选择权值最小的边

3. 若该边的两个顶点落在不同的连通分量上,选择这条边,并把这两个顶点标记为同一连通分量;若这条边的两个顶点落到同一连通分量上,舍弃这条边。反复执行2,3,直到所有的都在同一连通分量上。【这一步需要用到上面的并查集】

模板题:https://www.luogu.org/problem/P3366

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. int pre[];
  5. int n, m; //n个定点,m条边
  6.  
  7. struct ENode {
  8. int from, to, dis;
  9. bool operator<(ENode p) {
  10. return dis < p.dis;
  11. }
  12. }M[];
  13.  
  14. int Find(int x) {
  15. return x == pre[x] ? pre[x] : pre[x] = Find(pre[x]);
  16. }
  17.  
  18. int kurskal() {
  19. sort(M, M + m);
  20. int N = n, res = ;
  21. for (int i = ; i < m && N > ; i++) {
  22. int fx = Find(M[i].from), fy = Find(M[i].to);
  23. if (fx != fy) {
  24. pre[fx] = fy;
  25. N--;//找到了一条边,当N减到1的时候表明已经找到N-1条边了,就完成了
  26. res += M[i].dis;
  27. }
  28. }
  29. if (N == )//循环做完,N不等于1 表明没有找到合适的N-1条边来构成最小生成树
  30. return res;
  31. return -;
  32. }
  33.  
  34. int main() {
  35. cin >> n >> m;
  36. for (int i = ; i <= n; i++) {
  37. pre[i] = i;
  38. }
  39. for (int i = ; i < m; i++) {
  40. scanf("%d%d%d", &M[i].from, &M[i].to, &M[i].dis);;
  41. }
  42. int ans = kurskal();
  43. if (ans != -)
  44. cout << ans << endl;
  45. else
  46. cout << "orz" << endl;
  47. return ;
  48. }

Prim算法

Prim算法思想

首先将图的点分为两部分,一种是访问过的$u$(第一条边任选),一种是没有访问过的$v$

1: 每次找$u$到$v$的权值最小的边。

2: 然后将这条边中的$v$中的顶点添加到$u$中,直到$v$中边的个数$=$顶点数$-1$

图解步骤:

维护一个$dis$数组,记录只使用已访问节点能够到达各未访问节点最短的权值。

初始值为节点1(任意一个都可以)到各点的值,规定到自己是0,到不了的是$inf$(定义一个特别大的数)。

找当前能到达的权值最短的点。1-->4,节点4

将dis[4]赋值为0,标记为已访问过,同时借助4节点更新dis数组。

后面依次

最后整个dis数组都是0了,最小生成树也就出来了,如果$dis$数组中还有 $inf$ 的话,说明这不是一个连通图。

还是上面那道模板题:https://www.luogu.org/problem/P3366

  1. #include <iostream>
  2.  
  3. #include <fstream>
  4. using namespace std;
  5.  
  6. struct ENode {
  7. int dis, to;//权重、指向
  8. ENode* next = NULL;
  9. void push(int to, int dis) {
  10. ENode* p = new ENode;
  11. p->to = to; p->dis = dis;
  12. p->next = next;
  13. next = p;
  14. }
  15. }*head;
  16. const int inf = << ;
  17. int N, M;
  18. int dis[];
  19.  
  20. int prim() {
  21. int res = ;
  22.  
  23. for (int i = ; i <= N; i++) {
  24. dis[i] = inf;
  25. }
  26.  
  27. for (int i = ; i < N; i++) {//与kurskal区分,找边是N-1条边,找点是N个点
  28. int v = , MIN = inf;
  29. for (int j = ; j <= N; j++) {
  30. //到不了的,访问过的不进行比较
  31. if (dis[j] != && dis[j] < MIN) {
  32. v = j;
  33. MIN = dis[j];
  34. }
  35. }
  36. if (MIN == inf && v != )//这里v!=1是为了把dis的初始化放在循环里面做,也可以放在循环外面做,但是外层循环就只需要做N-1次了
  37. return -;//还没找够n个点,没路了
  38. res += dis[v];
  39. dis[v] = ;
  40. ENode *p = head[v].next;
  41. while (p) {
  42. if (dis[p->to] > p->dis) {
  43. dis[p->to] = p->dis;
  44. }
  45. p = p->next;
  46. }
  47. }
  48. return res;
  49. }
  50.  
  51. int main() {
  52. #ifdef LOCAL
  53. fstream cin("data.in");
  54. #endif // LOCAL
  55.  
  56. cin >> N >> M;
  57. head = new ENode[N + ];
  58. for (int i = ; i < M; i++) {
  59. int from, to, dis;
  60. scanf("%d%d%d", &from, &to, &dis);
  61. //cin >> from >> to >> dis;
  62. head[from].push(to, dis);
  63. head[to].push(from, dis);
  64. }
  65. int ans = prim();
  66. if (ans != -)
  67. cout << ans << endl;
  68. else
  69. cout << "orz" << endl;
  70. return ;
  71. }

两者区别

时间复杂度

prim算法

时间复杂度为$O(n^2)$,$n$为顶点的数量,其时间复杂度与边得数目无关,适合稠密图。

kruskal算法

时间复杂度为$O(e\cdot loge)$,$e$为边的数目,与顶点数量无关,适合稀疏图。

其实就是排序的时间,因为并查集的查询、合并操作都是$O(1)$。

总结

通俗点说就是,点多边少用Kruskal,因为Kruskal算法每次查找最短的边。 点少边多用Prim,因为它是每次找一个顶点。

具体选择用那个,可以用电脑算一下,题目给的数据级别,$n^2$和$e\cdot loge$看看那个小,比如上面的模板题,题目给的数据级别是$(n<=5000,e<=200000)$,粗略估算一下,kurskal算法一定是会快不少的,结果也确实如粗。

实现难度

明眼人都能看出来,kurskal算法要简单太多了。kurskal算法不需要把图表示出来,而Prim算法必须建表或者邻接矩阵,所以从上面的数据也能看出来当边的数目较大时,Prim算法所占用的空间比kurskal算法多了很多。

拓展

堆优化Prim算法

用堆存储当前所有可到达的点和距离,就是把dis数组里的内容一式两份,存在堆里,然后每次取堆顶元素,每次操作为$O(logn)$,所以使用堆优化后的Prim算法理论上时间复杂度为$O(nlogn)$,但是好像没有达到想要的效果

看了测试数据发现,有很多重边,那就合理了,做了很多次的无用循环,所以时间上也和kurskal比较相近。所以在数据可靠、无重边的情况下,这个算法一定是上述几种中最快的一个。

  1. #include <iostream>
  2. #include <fstream>
  3. #include <cstdio>
  4. #include <queue>
  5.  
  6. using namespace std;
  7. struct P {
  8. int dis, v;
  9. P(int d, int v) :dis(d), v(v) {};
  10. bool operator<(P p)const {
  11. return p.dis < dis;
  12. }
  13. };
  14. struct ENode {
  15. int dis, to;//权重、指向
  16. ENode* next = NULL;
  17. void push(int to, int dis) {
  18. ENode* p = new ENode;
  19. p->to = to; p->dis = dis;
  20. p->next = next;
  21. next = p;
  22. }
  23. }*head;
  24. const int inf = << ;
  25. int N, M;
  26. int dis[];
  27. bool fuck[];
  28.  
  29. int prim() {
  30. priority_queue<P>pq;
  31. pq.push(P(, ));
  32. int res = , cnt = N;
  33. dis[] = ;
  34. fill(dis + , dis + N + , inf);
  35.  
  36. while (!pq.empty() && cnt > ) {//与kurskal区分,找边是N-1条边,找点是N个点
  37. int v = pq.top().v, d = pq.top().dis;
  38. pq.pop();
  39. if (fuck[v])continue;
  40. fuck[v] = true;
  41. res += d;
  42. cnt--;
  43. ENode* p = head[v].next;
  44. while (p) {
  45. if (dis[p->to] > p->dis) {
  46. dis[p->to] = p->dis;
  47. pq.push(P(p->dis, p->to));
  48. }
  49. p = p->next;
  50. }
  51. }
  52. if (cnt > )
  53. return -;
  54. return res;
  55. }
  56.  
  57. int main() {
  58. #ifdef LOCAL
  59. fstream cin("data.in");
  60. #endif // LOCAL
  61. cin >> N >> M;
  62. head = new ENode[N + ];
  63. for (int i = ; i < M; i++) {
  64. int from, to, dis;
  65. scanf("%d%d%d", &from, &to, &dis);
  66. //cin >> from >> to >> dis;
  67. head[from].push(to, dis);
  68. head[to].push(from, dis);
  69. }
  70. int ans = prim();
  71. if (ans != -)
  72. cout << ans << endl;
  73. else
  74. cout << "orz" << endl;
  75. return ;
  76. }

图论篇2——最小生成树算法(kurskal算法&prim算法)的更多相关文章

  1. 【数据结构】 最小生成树(三)——prim算法

    上一期介绍到了kruskal算法,这个算法诞生于1956年,重难点就是如何判断是否形成回路,此处要用到并查集,不会用当然会觉得难,今天介绍的prim算法在kruskal算法之后一年(即1957年)诞生 ...

  2. 最小生成树-普利姆(Prim)算法

    最小生成树-普利姆(Prim)算法 最小生成树 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一种特殊的图),或者 ...

  3. 一步一步写算法(之prim算法 下)

    原文:一步一步写算法(之prim算法 下) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前两篇博客我们讨论了prim最小生成树的算法,熟悉 ...

  4. 一步一步写算法(之prim算法 中)

    原文:一步一步写算法(之prim算法 中) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] C)编写最小生成树,涉及创建.挑选和添加过程 MI ...

  5. 算法起步之Prim算法

    原文:算法起步之Prim算法 prim算法是另一种最小生成树算法.他的安全边选择策略跟kruskal略微不同,这点我们可以通过一张图先来了解一下. prim算法的安全边是从与当前生成树相连接的边中选择 ...

  6. 一步一步写算法(之prim算法 上)

    原文:一步一步写算法(之prim算法 上) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面我们讨论了图的创建.添加.删除和保存等问题.今 ...

  7. 图论---最小生成树----普利姆(Prim)算法

    普利姆(Prim)算法 1. 最小生成树(又名:最小权重生成树) 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一 ...

  8. 最小生成树的kruskal、prim算法

    kruskal算法和prim算法 都说 kruskal是加边法,prim是加点法 这篇解释也不错:这篇 1.kruskal算法 因为是加边法,所以这个方法比较合适稀疏图.要码这个需要先懂并查集.因为我 ...

  9. 最小生成树(Minimum Spanning Tree)——Prim算法与Kruskal算法+并查集

    最小生成树——Minimum Spanning Tree,是图论中比较重要的模型,通常用于解决实际生活中的路径代价最小一类的问题.我们首先用通俗的语言解释它的定义: 对于有n个节点的有权无向连通图,寻 ...

随机推荐

  1. Mercari Price Suggestion in Kaggle

    Mercari Price Suggestion 最近看到了一个竞赛,竞赛的内容是根据已知的商品的描述,品牌,品类,物品的状态等特征来预测商品的价格 最后的评估标准为 平均算术平方根误差Root Me ...

  2. cmder是一个增强型命令行工具,不仅可以使用windows下的所有命令,更爽的是可以使用linux的命令,shell命令。

    cmder使用简介 Cmder is a software package created out of pure frustration over the absence of nice conso ...

  3. 由浅入深了解NB-IoT | 我的物联网成长记

    [摘要] 什么是NB-IoT?NB-IoT有什么优势?NB-IoT能做什么?本文将会从NB-IoT技术的发展历程,技术特点,通信协议,应用场景等方面为您全方面解读NB-IoT技术,了解NB-IoT的独 ...

  4. CentOS 7下JumpServer安装及配置

    环境 系统 # cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) # uname -r 3.10.0-693.21.1.el7. ...

  5. jquery的Layer弹出框操作

    在layer中,我们要先获取窗口的索引,然后再进行操作. var index = parent.layer.getFrameIndex(window.name); //获取窗口索引 $("# ...

  6. Oracle 层次查询 connect by

      oracle 层次查询 语法:       SELECT ... FROM            [WHERE condition]                             --过 ...

  7. 【转帖】nmap命令总结

    nmap命令总结 https://www.cnblogs.com/chenqionghe/p/10657722.html 一.nmap是什么 nmap是一款网络扫描和主机检测的非常有用的工具,不局限于 ...

  8. C++删除排序数组中的重复项

    class Solution { public: int removeDuplicates(vector<int>& nums) { if (nums.empty()) { ; } ...

  9. Excel批量添加不同的批注

    Sub 批量添加不同批注() Dim rng As Range Dim i As String Range("A1:D1").ClearComments For Each rng ...

  10. .NET Core如何使用NLog

    1.新建ASP.NET Core项目 1.1选择项目 1.2选择.Net版本 2. 添加NLog插件 2.1 通过Nuget安装 2.2下载相关的插件 3.修改NLog配置文件 3.1添加NLog配置 ...