
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
数据范围
1≤n≤500,
1≤m≤105,
图中涉及边的边权的绝对值均不超过 10000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
题目来源:https://www.acwing.com/problem/content/860/






#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int g[N][N];
int dis[N];
bool st[N];
int n, m;
int prim(){
memset(dis, 0x3f, sizeof dis);
int res = 0;
//注意点1:外层n次大循环
for (int i=0; i<n; i++){
int t = -1; //t用于确定最近点下标
//注意点2:选择最近非树上点
for (int j=1; j<=n; j++){
if (!st[j] && (t == -1 || dis[j] < dis[t])){
t = j; //若是第一次选择树上点,则t==-1,接着选择j==1号点。若不是第一次择点,择t!=-1,由距离集dis[]最小者确定最近非树上点
}
}
if (i && dis[t] == INF) return INF;
if (i) res += dis[t];
st[t] = 1;
//注意点3:利用新入树点更新其余点到树的距离
for(int j=1; j<=n; j++){
dis[j] = min(dis[j], g[t][j]);
}
}
return res;
}
int main(){
memset(g, 0x3f, sizeof g);
cin >>n >>m;
for(int i=0; i<m; i++){
int a=0, b=0, c=0;
cin >>a >>b >>c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) cout<<"impossible" <<endl;
else cout << t <<endl;
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int g[N][N];
int dis[N];
bool st[N];
int n, m;
int prim(int start){
memset(dis, 0x3f, sizeof dis);
dis[start] = 0;
int res = 0;
//注意点1:外层n次大循环
for (int i=0; i<n; i++){
int t = -1; //t用于确定最近点下标
//注意点2:选择最近非树上点
for (int j=1; j<=n; j++){
if (!st[j] && (t == -1 || dis[j] < dis[t])){
t = j; //若是第一次选择树上点,则t==-1,接着选择最近点x即dis[x]==0。若不是第一次择点,择t!=-1,由距离集dis[]最小者确定最近非树上点
}
}
if (i && dis[t] == INF) return INF;
res += dis[t]; //第一个点dis[]==0,可以直接加入res,不必判断是否是第一个加入树的点了
st[t] = 1;
//注意点3:利用新入树点更新其余点到树的距离
for(int j=1; j<=n; j++){
dis[j] = min(dis[j], g[t][j]);
}
}
return res;
}
int main(){
memset(g, 0x3f, sizeof g);
cin >>n >>m;
for(int i=0; i<m; i++){
int a=0, b=0, c=0;
cin >>a >>b >>c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim(1); //某些图仅含1点
if (t == INF) cout<<"impossible" <<endl;
else cout << t <<endl;
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int g[N][N];
int dis[N];
bool st[N];
int pre[N]; //前驱节点记录
int rode[N]; //依次记录先后加入树的节点
int idx;
int n, m;
int prim(int start){
memset(dis, 0x3f, sizeof dis);
dis[start] = 0;
int res = 0;
//注意点1:外层n次大循环
for (int i=0; i<n; i++){
int t = -1; //t用于确定最近点下标
//注意点2:选择最近非树上点
for (int j=1; j<=n; j++){
if (!st[j] && (t == -1 || dis[j] < dis[t])){
t = j; //若是第一次选择树上点,则t==-1,接着选择j==1号点。若不是第一次择点,择t!=-1,由距离集dis[]最小者确定最近非树上点
}
}
rode[idx++] = t;
if (i && dis[t] == INF) return INF;
if (i) res += dis[t];
st[t] = 1;
//注意点3:利用新入树点更新其余点到树的距离
for(int j=1; j<=n; j++){
if (!st[j] && dis[j]>g[t][j]){
dis[j] = g[t][j];
pre[j] = t;
}
}
}
return res;
}
void showtree(){
for(int i=0; i<idx; i++){
cout<<"第"<<i+1<<"个加入树的节点是"<<rode[i] <<"其前缀节点是"<<pre[rode[i]]<<endl;
}
}
int main(){
memset(g, 0x3f, sizeof g);
cin >>n >>m;
for(int i=0; i<m; i++){
int a=0, b=0, c=0;
cin >>a >>b >>c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim(1);
if (t == INF) cout<<"impossible" <<endl;
else cout << t <<endl;
showtree();
return 0;
}
int t = -1;
for(int i=0; i<n; i++){
if (条件1 && (t ==-1 || 条件2)) //可以继续加&&条件3,||条件4……
t = i;
}
条件1的作用是缩小选择范围
条件2的作用是在范围中选择最符合目标的值
t的第一个值为-1
第二个值为0~n中满足条件1的值
第三个及以后的值为0~n中满足条件1和条件2的值
最终值为0~n中满足条件1和最符合条件2的值
由于本题图中点数最少为一个,又要保证生成树的第一个点存在于图中
所以和默认1号点开始一样,指定起点也是从1号点开始加入最小生成树。
唯一的区别在于最终最小生成树总距离res的累加:
默认1号开始生成树,dis[1]初始为+∞,在加入res时候需要区分是否是第一个点
指定起点开始生成树,dis[start]初始为0,在加入res时不需要区分是否是第一个点
本题用不着指定起点开始生成最小生成树,但是其余题目可能用到。

距离登仙境不远了,加油 