@@ -1,155 +1,506 @@
思考一下边的权值为负数的情况
如果你的图相对较小且比较密集,而且你更注重简单性和空间效率,数组实现可能更合适。
# 寻宝
如果你的图规模较大,尤其是在稀疏图中,而且你更注重时间效率和通用性,优先级队列实现可能更合适。
[卡码网: 53. 寻宝 ](https://kamacoder.com/problempage.php?pid=1053 )
其关键 在于弄清楚 minDist 的定义
题目描述:
在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。你是一名探险者,决定前往这些岛屿,但为了节省时间和资源,你希望规划一条最短的路径,以便在探索这些岛屿时尽量减少旅行的距离。
给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。每个岛屿都需要被至少访问一次,你的目标是规划一条最短路径,以最小化探索路径的总距离,同时确保访问了所有岛屿。
输入描述:
第一行包含两个整数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或者节点5都可以, 距离一样的) 加入最小生成树。
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 <iostream>
#include <vector>
#include <queue>
#include <climits>
#include<iostream>
#include<vector>
using namespace std;
// 定义图的邻接矩阵表示
const int INF = INT_MAX ; // 表示无穷大
typedef vector < vector < int >> Graph ;
// 使用Prim算法找到最小生成树
void primMST ( const Graph & graph , int startVertex ) {
int V = graph . size ();
// 存储顶点是否在最小生成树中
vector < bool > inMST ( V , false );
// 存储最小生成树的边权重
vector < int > key ( V , INF );
// 优先队列,存储边权重和目标顶点
priority_queue < pair < int , int > , vector < pair < int , int >> , greater < pair < int , int >>> pq ;
// 初始顶点的权重设为0, 加入优先队列
key [ startVertex ] = 0 ;
pq . push ({ 0 , startVertex });
while ( ! pq . empty ()) {
// 从优先队列中取出权重最小的边
int u = pq . top (). second ;
pq . pop ();
// 将顶点u标记为在最小生成树中
inMST [ u ] = true ;
// 遍历u的所有邻居
for ( int v = 0 ; v < V ; ++ v ) {
// 如果v未在最小生成树中, 且u到v的权重小于v的当前权重
if ( ! inMST [ v ] && graph [ u ][ v ] < key [ v ]) {
// 更新v的权重为u到v的权重
key [ v ] = graph [ u ][ v ];
// 将(u, v)添加到最小生成树
pq . push ({ key [ v ], v });
}
}
}
// 输出最小生成树的边
cout << "Edges in the Minimum Spanning Tree: \n " ;
for ( int i = 1 ; i < V ; ++ i ) {
cout << i << " - " << key [ i ] << " - " << i << " \n " ;
}
}
int main() {
// 例子:无向图的邻接矩阵表示
Graph graph = {
{ 0 , 2 , 0 , 6 , 0 },
{ 2 , 0 , 3 , 8 , 5 },
{ 0 , 3 , 0 , 0 , 7 },
{ 6 , 8 , 0 , 0 , 9 },
{ 0 , 5 , 7 , 9 , 0 }
};
int v, e;
int x, y, k;
cin >> v >> e;
// 填一个默认最大值, 题目描述val最大为10000
vector<vector<int>> grid(v + 1, vector<int>(v + 1, 10001));
while (e--) {
cin >> x >> y >> k;
// 因为是双向图,所以两个方向都要填上
grid[x][y] = k;
grid[y][x] = k;
// 从顶点0开始运行Prim算法
primMST ( graph , 0 );
}
// 所有节点到最小生成树的最小距离
vector<int> minDist(v + 1, 10001);
// 这个节点是否在树里
vector<bool> isInTree(v + 1, false);
// 我们只需要循环 n-1次, 建立 n - 1条边, 就可以把n个节点的图连在一起
for (int i = 1; i < v; i++) {
// 1、prim三部曲, 第一步: 选距离生成树最近节点
int cur = -1; // 选中哪个节点 加入最小生成树
for (int j = 1; j <= v; j++) { // 1 - v, 顶点编号, 这里下标从1开始
// 选取最小生成树节点的条件:
// ( 1) 不在最小生成树里
// ( 2) 距离最小生成树最近的节点
// ( 3) 只要不在最小生成树里, 先默认选一个节点 ,在比较 哪一个是最小的
// 理解条件3 很重要,才能理解这段代码:(cur == -1 || minDist[j] < minDist[cur])
if (!isInTree[j] && (cur == -1 || minDist[j] < minDist[cur])) {
cur = j;
}
}
// 2、prim三部曲, 第二步: 最近节点( cur) 加入生成树
isInTree[cur] = true;
// 3、prim三部曲, 第三步: 更新非生成树节点到生成树的距离( 即更新minDist数组)
// cur节点加入之后, 最小生成树加入了新的节点,那么所有节点到 最小生成树的距离( 即minDist数组) 需要更新一下
// 由于cur节点是新加入到最小生成树, 那么只需要关心与 cur 相连的 非生成树节点 的距离 是否比 原来 非生成树节点到生成树节点的距离更小了呢
for (int j = 1; j <= v; j++) {
// 更新的条件:
// ( 1) 节点是 非生成树里的节点
// ( 2) 与cur相连的某节点的权值 比 该某节点距离最小生成树的距离小
// 很多录友看到自己 就想不明白什么意思,其实就是 cur 是新加入 最小生成树的节点,那么 所有非生成树的节点距离生成树节点的最近距离 由于 cur的新加入, 需要更新一下数据了
if (!isInTree[j] && grid[cur][j] < minDist[j]) {
minDist[j] = grid[cur][j];
}
}
}
// 统计结果
int result = 0;
for (int i = 2; i <= v; i++) { // 不计第一个顶点, 因为统计的是边的权值, v个节点有 v-1条边
result += minDist[i];
}
cout << result << endl;
return 0 ;
}
```
## 拓展
上面讲解的是记录了最小生成树 所有边的权值,如果让打印出来 最小生成树的每条边呢? 或者说 要把这个最小生成树画出来呢?
此时我们就需要把 最小生成树里每一条边记录下来。
此时有两个问题:
* 1、用什么结构来记录
* 2、如何记录
如果记录边,其实就是记录两个节点就可以,两个节点连成一条边。
如何记录两个节点呢?
我们使用一维数组就可以记录。 parent[节点编号] = 节点编号, 这样就把一条边记录下来了。( 当然如果节点编号非常大, 可以考虑使用map)
使用一维数组记录是有向边,不过我们这里不需要记录方向,所以只关注两条边是连接的就行。
parent数组初始化代码:
```CPP
#include <iostream>
#include <vector>
#include <climits>
vector<int> parent(v + 1, -1);
```
using namespace std;
接下来就是第二个问题,如何记录?
// 定义图的邻接矩阵表示
const int INF = INT_MAX; // 表示无穷大
typedef vector<vector<int>> Graph;
我们再来回顾一下 prim三部曲,
// 使用Prim算法找到最小生成树
void primMST(const Graph& graph, int startVertex) {
int V = graph.size();
1. 第一步,选距离生成树最近节点
2. 第二步,最近节点加入生成树
3. 第三步, 更新非生成树节点到生成树的距离( 即更新minDist数组)
// 存储顶点是否在最小生成树中
vector<bool> inMST(V, false);
大家先思考一下,我们是在第几步,可以记录 最小生成树的边呢?
// 存储每个顶点的权重
vector<int> key(V, INF);
在本面上半篇 我们讲解过:“我们根据 minDist数组, 选组距离 生成树 最近的节点 加入生成树,那么 **minDist数组里记录的其实也是 最小生成树的边的权值** 。”
// 初始化起始顶点的权重为0
key[startVertex] = 0;
既然 minDist数组 记录了 最小生成树的边,是不是就是在更新 minDist数组 的时候, 去更新parent数组来记录一下对应的边呢。
// 存储最小生成树的边权重
vector<int> parent(V, -1);
// 构建最小生成树
for (int count = 0; count < V - 1; ++count) {
// 从未在最小生成树中的顶点中找到权重最小的顶点
int u = -1;
for (int v = 0; v < V; ++v) {
if (!inMST[v] && (u == -1 || key[v] < key[u])) {
u = v;
所以 在 prim三部曲中的第三步, 更新 parent数组, 代码如下:
```CPP
for ( int j = 1 ; j <= v ; j ++ ) {
if ( ! isInTree [ j ] && grid [ cur ][ j ] < minDist [ j ]) {
minDist [ j ] = grid [ cur ][ j ];
parent [ j ] = cur ; // 记录最小生成树的边 (注意数组指向的顺序很重要)
}
}
// 将顶点u标记为在最小生成树中
inMST[u] = true;
// 更新u的邻居的权重和父节点
for (int v = 0; v < V; ++v) {
if (graph[u][v] != 0 && !inMST[v] && graph[u][v] < key[v]) {
key[v] = graph[u][v];
parent[v] = u;
}
}
}
// 输出最小生成树的边
cout << "Edges in the Minimum Spanning Tree:\n";
for (int i = 1; i < V; ++i) {
cout << parent[i] << " - " << key[i] << " - " << i << "\n";
}
}
int main() {
// 例子:无向图的邻接矩阵表示
Graph graph = {
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
// 从顶点0开始运行Prim算法
primMST(graph, 0);
return 0;
}
```
代码中注释中,我强调了 数组指向的顺序很重要。 因为不少录友在这里会写成这样: `parent[cur] = j` 。
这里估计大家会疑惑了, parent[节点编号A] = 节点编号B, 就表示A 和 B 相连,我们这里就不用在意方向,代码中 为什么 只能 `parent[j] = cur` 而不能 `parent[cur] = j` 这么写呢?
如果写成 `parent[cur] = j` ,在 for 循环中,有多个 j 满足要求, 那么 parent[cur] 就会被反复覆盖,因为 cur 是一个固定值。
举个例子, cur = 1, 在 for循环中, 可能 就 j = 2, j = 3, j =4 都符合条件,那么本来应该记录 节点1 与 节点 2、节点3、节点4相连的。
如果 `parent[cur] = j` 这么写,最后更新的逻辑是 parent[1] = 2, parent[1] = 3, parent[1] = 4, 最后只能记录 节点1 与节点 4 相连,其他相连情况都被覆盖了。
如果这么写 `parent[j] = cur` , 那就是 parent[2] = 1, parent[3] = 1, parent[4] = 1 ,这样 才能完整表示出 节点1 与 其他节点都是链接的,才没有被覆盖。
主要问题也是我们使用了一维数组来记录。
如果是二维数组,来记录两个点链接,例如 parent[节点编号A][节点编号B] = 1 , parent[节点编号B][节点编号A] = 1, 来表示 节点A 与 节点B 相连,那就没有上面说的这个注意事项了,当然这么做的话,就是多开辟的内存空间。
以下是输出最小生成树边的代码,不算最后输出, 就额外添加了两行代码,我都注释标记了:
```CPP
#include<iostream>
#include<vector>
using namespace std ;
int main () {
int v , e ;
int x , y , k ;
cin >> v >> e ;
vector < vector < int >> grid ( v + 1 , vector < int > ( v + 1 , 10001 ));
while ( e -- ) {
cin >> x >> y >> k ;
grid [ x ][ y ] = k ;
grid [ y ][ x ] = k ;
}
vector < int > minDist ( v + 1 , 10001 );
vector < bool > isInTree ( v + 1 , false );
//加上初始化
vector < int > parent ( v + 1 , - 1 );
for ( int i = 1 ; i < v ; i ++ ) {
int cur = - 1 ;
for ( int j = 1 ; j <= v ; j ++ ) {
if ( ! isInTree [ j ] && ( cur == - 1 || minDist [ j ] < minDist [ cur ])) {
cur = j ;
}
}
isInTree [ cur ] = true ;
for ( int j = 1 ; j <= v ; j ++ ) {
if ( ! isInTree [ j ] && grid [ cur ][ j ] < minDist [ j ]) {
minDist [ j ] = grid [ cur ][ j ];
parent [ j ] = cur ; // 记录边
}
}
}
// 输出 最小生成树边的链接情况
for ( int i = 1 ; i <= v ; i ++ ) {
cout << i "->" parent [ i ] << endl ;
}
}
```
按照本题示例,代码输入如下:
```
1->-1
2->1
3->1
4->3
5->4
6->2
7->5
```
注意,这里是无向图,我在输出上添加了箭头仅仅是为了方便大家看出是边的意思。
大家可以和我们本题最后生成的最小生成树的图 去对比一下 边的链接情况:

绿色的边 是最小生成树,和我们的 输出完全一致。
## 总结
此时我就把prim算法讲解完毕了, 我们再来回顾一下。
关于 prim算法, 我自创了三部曲, 来帮助大家理解:
1. 第一步,选距离生成树最近节点
2. 第二步,最近节点加入生成树
3. 第三步, 更新非生成树节点到生成树的距离( 即更新minDist数组)
大家只要理解这三部曲, prim算法 至少是可以写出一个框架出来,然后在慢慢补充细节,这样不至于 自己在写prim的时候 两眼一抹黑 完全凭感觉去写。
这也为什么很多录友感觉 prim算法比较难, 而且每次学会来, 隔一段时间 又不会写了,主要是 没有一个纲领。
理解这三部曲之后,更重要的 就是理解 minDist数组。
**minDist数组 是prim算法的灵魂, 它帮助 prim算法完成最重要的一步, 就是如何找到 距离最小生成树最近的点** 。
再来帮大家回顾 minDist数组 的含义:记录 每一个节点距离最小生成树的最近距离。
理解 minDist数组 , 至少大家看prim算法的代码不会懵。
也正是 因为 minDist数组 的作用,我们根据 minDist数组, 选取距离 生成树 最近的节点 加入生成树,那么 **minDist数组里记录的其实也是 最小生成树的边的权值** 。
所以我们求 最小生成树的权值和 就是 计算后的 minDist数组 数值总和。
最后我们拓展了如何求职 最小生成树 的每一条边,其实 添加的代码很简单,主要是理解 为什么使用 parent数组 来记录边 以及 在哪里 更新parent数组。
同时,因为使用一维数组,数组的下标和数组 如何赋值很重要,不要搞反,导师结果被覆盖。
好了,以上为总结,录友们学习愉快。