参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!
# prim算法精讲 [卡码网:53. 寻宝](https://kamacoder.com/problempage.php?pid=1053) 题目描述: 在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。 不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。 给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。 输入描述: 第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。 接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。 输出描述: 输出联通所有岛屿的最小路径总距离 输入示例: ``` 7 11 1 2 1 1 3 1 1 5 2 2 6 1 2 4 2 2 3 2 3 4 1 4 5 1 5 6 2 5 7 1 6 7 1 ``` 输出示例: 6 ## 解题思路 本题是最小生成树的模板题,那么我们来讲一讲最小生成树。 最小生成树 可以使用 prim算法 也可以使用 kruskal算法计算出来。 本篇我们先讲解 prim算法。 最小生成树是所有节点的最小连通子图, 即:以最小的成本(边的权值)将图中所有节点链接到一起。 图中有n个节点,那么一定可以用 n - 1 条边将所有节点连接到一起。 那么如何选择 这 n-1 条边 就是 最小生成树算法的任务所在。 例如本题示例中的无向有权图为:  那么在这个图中,如何选取 n-1 条边 使得 图中所有节点连接到一起,并且边的权值和最小呢? (图中为n为7,即7个节点,那么只需要 n-1 即 6条边就可以讲所有顶点连接到一起) prim算法 是从节点的角度 采用贪心的策略 每次寻找距离 最小生成树最近的节点 并加入到最小生成树中。 prim算法核心就是三步,我称为**prim三部曲**,大家一定要熟悉这三步,代码相对会好些很多: 1. 第一步,选距离生成树最近节点 2. 第二步,最近节点加入生成树 3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组) 现在录友们会对这三步很陌生,不知道这是干啥的,没关系,下面将会画图举例来带大家把这**prim三部曲**理解到位。 在prim算法中,有一个数组特别重要,这里我起名为:minDist。 刚刚我有讲过 “每次寻找距离 最小生成树最近的节点 并加入到最小生成树中”,那么如何寻找距离最小生成树最近的节点呢? 这就用到了 minDist 数组, 它用来作什么呢? **minDist数组 用来记录 每一个节点距离最小生成树的最近距离**。 理解这一点非常重要,这也是 prim算法最核心要点所在,很多录友看不懂prim算法的代码,都是因为没有理解透 这个数组的含义。 接下来,我们来通过一步一步画图,来带大家巩固 **prim三部曲** 以及 minDist数组 的作用。 (**示例中节点编号是从1开始,所以为了让大家看的不晕,minDist数组下标我也从 1 开始计数,下标0 就不使用了,这样 下标和节点标号就可以对应上了,避免大家搞混**) ### 1 初始状态 minDist 数组 里的数值初始化为 最大数,因为本题 节点距离不会超过 10000,所以 初始化最大数为 10001就可以。 相信这里录友就要问了,为什么这么做? 现在 还没有最小生成树,默认每个节点距离最小生成树是最大的,这样后面我们在比较的时候,发现更近的距离,才能更新到 minDist 数组上。 如图:  开始构造最小生成树 ### 2 1、prim三部曲,第一步:选距离生成树最近节点 选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1 (符合遍历数组的习惯,第一个遍历的也是节点1) 2、prim三部曲,第二步:最近节点加入生成树 此时 节点1 已经算最小生成树的节点。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 接下来,我们要更新所有节点距离最小生成树的距离,如图:  注意下标0,我们就不管它了,下标 1 与节点 1 对应,这样可以避免大家把节点搞混。 此时所有非生成树的节点距离 最小生成树(节点1)的距离都已经跟新了 。 * 节点2 与 节点1 的距离为1,比原先的 距离值10001小,所以更新minDist[2]。 * 节点3 和 节点1 的距离为1,比原先的 距离值10001小,所以更新minDist[3]。 * 节点5 和 节点1 的距离为2,比原先的 距离值10001小,所以更新minDist[5]。 **注意图中我标记了 minDist数组里更新的权值**,是哪两个节点之间的权值,例如 minDist[2] =1 ,这个 1 是 节点1 与 节点2 之间的连线,清楚这一点对最后我们记录 最小生成树的权值总和很重要。 (我在后面依然会不断重复 prim三部曲,可能基础好的录友会感觉有点啰嗦,但也是让大家感觉这三部曲求解的过程) ### 3 1、prim三部曲,第一步:选距离生成树最近节点 选取一个距离 最小生成树(节点1) 最近的非生成树里的节点,节点2,3,5 距离 最小生成树(节点1) 最近,选节点 2(其实选 节点3或者节点2都可以,距离一样的)加入最小生成树。 2、prim三部曲,第二步:最近节点加入生成树 此时 节点1 和 节点2,已经算最小生成树的节点。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 接下来,我们要更新节点距离最小生成树的距离,如图:  此时所有非生成树的节点距离 最小生成树(节点1、节点2)的距离都已经跟新了 。 * 节点3 和 节点2 的距离为2,和原先的距离值1 小,所以不用更新。 * 节点4 和 节点2 的距离为2,比原先的距离值10001小,所以更新minDist[4]。 * 节点5 和 节点2 的距离为10001(不连接),所以不用更新。 * 节点6 和 节点2 的距离为1,比原先的距离值10001小,所以更新minDist[6]。 ### 4 1、prim三部曲,第一步:选距离生成树最近节点 选择一个距离 最小生成树(节点1、节点2) 最近的非生成树里的节点,节点3,6 距离 最小生成树(节点1、节点2) 最近,选节点3 (选节点6也可以,距离一样)加入最小生成树。 2、prim三部曲,第二步:最近节点加入生成树 此时 节点1 、节点2 、节点3 算是最小生成树的节点。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 接下来更新节点距离最小生成树的距离,如图:  所有非生成树的节点距离 最小生成树(节点1、节点2、节点3 )的距离都已经跟新了 。 * 节点 4 和 节点 3的距离为 1,和原先的距离值 2 小,所以更新minDist[3]为1。 上面为什么我们只比较 节点4 和 节点3 的距离呢? 因为节点3加入 最小生成树后,非 生成树节点 只有 节点 4 和 节点3是链接的,所以需要重新更新一下 节点4距离最小生成树的距离,其他节点距离最小生成树的距离 都不变。 ### 5 1、prim三部曲,第一步:选距离生成树最近节点 继续选择一个距离 最小生成树(节点1、节点2、节点3) 最近的非生成树里的节点,为了巩固大家对 minDist数组的理解,这里我再啰嗦一遍:  **minDist数组 是记录了 所有非生成树节点距离生成树的最小距离**,所以 从数组里我们能看出来,非生成树节点 4 和 节点 6 距离 生成树最近。 任选一个加入生成树,我们选 节点4(选节点6也行) 。 **注意**,我们根据 minDist数组,选取距离 生成树 最近的节点 加入生成树,那么 **minDist数组里记录的其实也是 最小生成树的边的权值**(我在图中把权值对应的是哪两个节点也标记出来了)。 如果大家不理解,可以跟着我们下面的讲解,看 minDist数组的变化, minDist数组 里记录的权值对应的哪条边。 理解这一点很重要,因为 最后我们要求 最小生成树里所有边的权值和。 2、prim三部曲,第二步:最近节点加入生成树 此时 节点1、节点2、节点3、节点4 算是 最小生成树的节点。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 接下来更新节点距离最小生成树的距离,如图:  minDist数组已经更新了 所有非生成树的节点距离 最小生成树(节点1、节点2、节点3、节点4 )的距离 。 * 节点 5 和 节点 4的距离为 1,和原先的距离值 2 小,所以更新minDist[4]为1。 ### 6 1、prim三部曲,第一步:选距离生成树最近节点 继续选距离 最小生成树(节点1、节点2、节点3、节点4 )最近的非生成树里的节点,只有 节点 5 和 节点6。 选节点5 (选节点6也可以)加入 生成树。 2、prim三部曲,第二步:最近节点加入生成树 节点1、节点2、节点3、节点4、节点5 算是 最小生成树的节点。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 接下来更新节点距离最小生成树的距离,如图:  minDist数组已经更新了 所有非生成树的节点距离 最小生成树(节点1、节点2、节点3、节点4 、节点5)的距离 。 * 节点 6 和 节点 5 距离为 2,比原先的距离值 1 大,所以不更新 * 节点 7 和 节点 5 距离为 1,比原先的距离值 10001小,更新 minDist[7] ### 7 1、prim三部曲,第一步:选距离生成树最近节点 继续选距离 最小生成树(节点1、节点2、节点3、节点4 、节点5)最近的非生成树里的节点,只有 节点 6 和 节点7。 2、prim三部曲,第二步:最近节点加入生成树 选节点6 (选节点7也行,距离一样的)加入生成树。 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) 节点1、节点2、节点3、节点4、节点5、节点6 算是 最小生成树的节点 ,接下来更新节点距离最小生成树的距离,如图:  这里就不在重复描述了,大家类推,最后,节点7加入生成树,如图:  ### 最后 最后我们就生成了一个 最小生成树, 绿色的边将所有节点链接到一起,并且 保证权值是最小的,因为我们在更新 minDist 数组的时候,都是选距离 最小生成树最近的点 加入到树中。 讲解上面的模拟过程的时候,我已经强调多次 minDist数组 是记录了 所有非生成树节点距离生成树的最小距离。 最后,minDist数组 也就是记录的是最小生成树所有边的权值。 我在图中,特别把 每条边的权值对应的是哪两个节点 标记出来(例如minDist[7] = 1,对应的是节点5 和 节点7之间的边,而不是 节点6 和 节点7),为了就是让大家清楚, minDist里的每一个值 对应的是哪条边。 那么我们要求最小生成树里边的权值总和 就是 把 最后的 minDist 数组 累加一起。 以下代码,我对 prim三部曲,做了重点注释,大家根据这三步,就可以 透彻理解prim。 ```CPP #include