最小生成树-prim、kruskal算法
目录
prim算法
kruskal算法
题目练习
(1)AcWing 858. Prim算法求最小生成树 - AcWing
(2)859. Kruskal算法求最小生成树 - AcWing题库编辑
学习之前建议温习一下迪杰斯特拉算法和并查集~
先简单认识下最小生成树:
最小生成树是所有节点的最小连通子图,即:以最小的成本(边的权值)将图中所有节点链接到一起。图中有n个节点,那么一定可以用n-1条边将所有节点连接到一起。那么如何选择这n-1条边就是最小生成树算法的任务所在。
下面我们以一道模板题来讲解如何解决这个问题~~
prim算法
prim算法是从节点的角度采用贪心的策略每次寻找距离最小生成树最近的节点并加入到最小生成树中。prim算法核心就是三步,一定要熟悉这三步,代码相对会好些很多:
- 第一步,选距离生成树最近节点
- 第二步,最近节点加入生成树
- 第三步,更新非生成树节点到生成树的距离(即更新minDist数组,minDist数组用来记录每一个节点距离最小生成树的最近距离)
下面按照这三步解决一下刚刚的题目~
① 初始化mindist数组为 10001【默认每个节点距离最小生成树是最大的,这样后面我们在比较的时候,发现更近的距离,才能更新到minDist数组上】
② 选距离生成树最近节点:刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1(符合遍历数组的习惯,第一个遍历的也是节点1)----->最近节点加入生成树: 此时节点1已经算最小生成树的节点-------->更新非生成树节点到生成树的距离(即更新minDist数组,如下图)
③选距离生成树最近节点 :选取一个距离最小生成树(节点1)最近的非生成树里的节点,节点2,3,5距离最小生成树(节点1)最近,选节点2(其实选节点3或者节点2都可以,距离一样的)加入最小生成树---->最近节点加入生成树:此时节点1和节点2,已经算最小生成树的节点---->更新非生成树节点到生成树的距离(即更新minDist数组)\
④最小生成树(节点1、节点2),节点3,6距离最小生成树(节点1、节点2)最近,选节点3(选节点6也可以,距离一样)加入最小生成树----->此时节点1、节点2、节点3算是最小生成树的节点。----->更新与生成树结点相连接的非生成树节点到生成树的距离
- 节点4和节点3的距离为1,和原先的距离值2小,所以更新minDist[4]为1。
.....
......
......
最后节点7加入最小生成树
绿色的边将所有节点链接到一起,并且保证权值是最小的,因为我们在更新minDist数组的时候,都是选距离最小生成树最近 的点加入到树中
我们要求最小生成树里边的权值总和就是把最后的minDist数组累加一起
完整代码
#include<bits/stdc++.h>
using namespace std;
int main()
{int v,e;cin>>v>>e;//邻接矩阵存储图 1-based索引vector<vector<int>> grid(v+1,vector<int>(v+1,10001));while(e--){int x,y,k;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、选距离生成树最近的节点int cur=-1;int minval=INT_MAX;for(int j=1;j<=v;j++) //1 - v,顶点编号,这里下标从1开始{if(!isintree[j]&&mindist[j]<minval){minval=mindist[j];cur=j;}}//2、最近节点加入生成树isintree[cur]=true;//3、更新非生成树节点到生成树的距离(即更新minDist数组)//只需要关心与 cur 相连的非生成树节点的距离是否比原非生成树节点到生成树节点的距离更小了呢for(int j=1;j<=v;j++){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;}
真的和dijstrk求最短路径的模板很像!!!
kruskal算法
用另外一种算法求解上题,prim 算法是维护节点的集合,而 Kruskal 是维护边的集合。
kruscal的思路:
- 边的权值排序,因为要优先选最小的边加入到生成树里
- 遍历排序后的边
- 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
- 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合
下面我们画图举例说明kruskal的工作过程。
开始从头遍历排序后的边。
判断两个节点是否在同一个集合,就看图中两个节点是否有绿色的粗线连着就行
.......
.......
在代码中,如果将两个节点加入同一个集合,又如何判断两个节点是否在同一个集合呢?
这里就涉及到我们之前讲解的并查集:数据结构--并查集-高效处理连通性问题-CSDN博客
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=10001;
vector<int> fa(N,-1);//并查集标记节点关系的数组
int find(int x){if(x==fa[x]) return x;else return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{int xx=find(x);int yy=find(y);if(xx==yy) return ;fa[yy]=xx;
}
struct Edge
{int l,r,val;// l,r为 边两边的节点,val为边的数值//定义结构体的排序规则bool operator<(const Edge& other) const{return val < other.val;}
};
int main()
{int v,e;cin>>v>>e;vector<Edge> edges;//存储图while(e--){int v1, v2, val;cin>>v1>>v2>>val;edges.push_back({v1,v2,val});}//并查集初始化for(int i=0;i<N;i++) fa[i]=i;//执行kruskal算法int result_val=0;//1、排序sort(edges.begin(),edges.end());//从头开始遍历边for(auto edge:edges){//找出两个节点的祖先int x=find(edge.l);int y=find(edge.r);//如果祖先不同则不在一个集合if(x!=y) {result_val+=edge.val;//这条边可以计入生成树的边merge(x,y);//两个结点加入一个集合}}cout<<result_val;return 0;}
我觉得这个更简单诶
图是稀疏图,点很多但边很少---->kruskal算法
图是稠密图,几乎每个点都和其他点有边相连,点多边多---->prim算法
题目练习
(1)AcWing 858. Prim算法求最小生成树 - AcWing
m很大,几乎每个点都和其他点有边相连,适用Prim算法
#include <bits/stdc++.h>
using namespace std;int main() {int n, m;cin >> n >> m;vector<vector<int>> g(n + 1, vector<int>(n + 1, 10001)); // 初始化为较大的值while (m--) {int u, v, w;cin >> u >> v >> w;if (w < g[u][v]) { // 处理重边,保留最小边权g[u][v] = w;g[v][u] = w; //无向边}}vector<int> dist(n + 1, 10001); // 存储各节点到MST的最小距离vector<bool> visited(n + 1, false);dist[1] = 0; // 起始节点初始化为0int total = 0;for (int i = 0; i < n; ++i) { // 需要选择n次,最后一次确认连通性int u = -1, minDist = 10001;for (int j = 1; j <= n; ++j) {if (!visited[j] && dist[j] < minDist) {minDist = dist[j];u = j;}}if (u == -1) { // 无法找到有效节点,图不连通cout << "impossible";return 0;}visited[u] = true;total += dist[u]; // 累加边权// 更新相邻节点的距离for (int v = 1; v <= n; ++v) {if (!visited[v] && g[u][v] < dist[v]) {dist[v] = g[u][v];}}}cout << total;return 0;
}
(2)859. Kruskal算法求最小生成树 - AcWing题库
n这麽多,m也这麽多,图很大,适合使用 Kruskal 算法
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
vector<int> fa(N,-1);//并查集标记节点关系的数组
int find(int x){if(x==fa[x]) return x;else return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{int xx=find(x);int yy=find(y);if(xx==yy) return ;fa[yy]=xx;
}
struct Edge
{int l,r,val;// l,r为 边两边的节点,val为边的数值//定义结构体的排序规则bool operator<(const Edge& other) const{return val < other.val;}
};
int main()
{int v,e;cin>>v>>e;vector<Edge> edges;//存储图while(e--){int v1, v2, val;cin>>v1>>v2>>val;edges.push_back({v1,v2,val}); //不用去重}//并查集初始化for(int i=0;i<N;i++) fa[i]=i;//执行kruskal算法int edge_count = 0; // 记录加入生成树的边数int result_val=0;//1、排序sort(edges.begin(),edges.end());//从头开始遍历边for(auto edge:edges){//找出两个节点的祖先int x=find(edge.l);int y=find(edge.r);//如果祖先不同则不在一个集合if(x!=y) {result_val+=edge.val;//这条边可以计入生成树的边merge(x,y);//两个结点加入一个集合edge_count++;if (edge_count == v - 1) break; // 提前终止:最小生成树已经完成}}if (edge_count == v - 1)cout << result_val << endl;elsecout << "impossible" << endl;return 0;}