本文主要是介绍最小生成树刷题笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
算法基础:
最小生成树是所有节点的最小连通子图!!!!
首先是prim算法三部曲:
(1)找到距离最小生成树最近的节点。
(2)将距离最小生成树最近的节点加入到最小生成树中。
(3)更新非最小生成树节点到最小生成树的距离。
实现步骤:
首先我们利用一个for循环遍历n - 1遍,因为我们从第1个节点开始将其加入到生成树之中后知道添加到还剩两个节点时,我们可以发现当我们添加玩倒数第二个节点后,最后一个节点的mindist数值在处理倒数第二个节点的第三部更新过程中已经得到了,这个距离不是倒数第二个节点到它的距离grid[cur][j]就是之前已经得到的它与某个节点之间的距离mindist[j]。
(1)寻找最近节点:
判断最近节点的三个条件:1.未在生成树中。 2.距离生成树距离最短 3.选取最短距离我们先随便选出一个节点然后再与其他节点比较
if(!isvisited[j] && (cur = -1 || mindist[cur] > mindist[j]) Cur = j;
这里:Mindist[cur] < mindist[j]包含了两层含义:首先我们要明确我们要找离生成树距离最短的点,mindist数组中存的只就是节点到生成树的最小距离,如果后续的节点的mindist的值要小于前面得出的mindist值,那么就将当前节点的标记j赋值给cur。
这里我们每遍历一层就会将cur置为-1以便我们可以最快选取一个随机节点并遍历后面的节点与之比较。而第二层的mindist[j]的值已经在第一层的第三步更新操作中得到。
(2)将最近节点加入生成树:
isintree[cur] = true;
(3)更新非生成树节点到生成树距离:
利用一个for循环遍历所以非生成树节点,并更新起距离mindist[j] < grid[cur][j] ? Mindist[j] = mindist[j] : mindist[j] = grid[cur][j]
这列的更新操作有两层意思:第一是遍历的外部节点需要与生成树有连接,也就是grid[cur][j] < mindist[j] == INT_MAX,第二是当前节点已经跟生成树有连接但还没选入进入生成树(预备党员哈哈)因为之前节点的更新扩散操作使得与之前节点有连接的j节点有了mindist的数值,所以我们拿grid[cur][j]与之前得到的最近距离mindist作比较保留更小的那个值赋值给mindist[j]
经过上面的分析,我认为prim算法的关键一定是要明确mindist数组的含义:是当前节点到最小生成树的最小距离。因为我们的第一步选点用到mindist数组来根据各个点到生成树(第一步时就是到源点的距离)的最小距离来选取最近节点,我们的第三步更新也是由于新加入的点导致未加入节点到生成树的距离改变,通过grid[cur][j] 与mindist[j]来判断是否需要更改mindist的值。
#include<iostream>
#include<vector>
using namespace std;
int main() {
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;
}
// 所有节点到最小生成树的最小距离
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;
}
leetcode - 1584:连接所有点的最小费用
由于这道题给出的是点集,我们可以想到使用prim来处理点集的最小生成树问题,这道题不好使用kruskal来解决因为还要求边那就需要我们去求各个点的组合会复杂很多。
就是利用题目中的公式建立邻接矩阵在套用上面的模板即可:
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int n = points.size();
vector<vector<int>> grid(n, vector<int>(n, 0));
// 计算任意两点之间的曼哈顿距离并填充到grid矩阵中
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
int x = abs(points[i][0] - points[j][0]);
int y = abs(points[i][1] - points[j][1]);
grid[i][j] = grid[j][i] = x + y;
}
}
vector<bool> isvisited(n, false);
vector<int> mindist(n, INT_MAX);
mindist[0] = 0; // 从第一个点开始
int res = 0;
for(int i = 0; i < n - 1; i++){
int cur = -1;
// 选择当前距离生成树最近的节点
for(int j = 0; j < n; j++){
if(!isvisited[j] && (cur == -1 || mindist[j] < mindist[cur])){
cur = j;
}
}
isvisited[cur] = true;
// 更新其他节点到生成树的距离
for(int j = 0; j < n; j++){
if(!isvisited[j] && mindist[j] > grid[cur][j]){
mindist[j] = grid[cur][j];
}
}
}
for(int i = 1; i < n; i++){
res += mindist[i];
}
return res;
}
这篇关于最小生成树刷题笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!