有两个求最小生成树的算法,prim算法和kruskal算法。这两种算法都可以处理边权为负的情况,并且可以处理有负权回路的情况。接下来会分析一下两个算法的区别。
prim算法
这个算法思路主要是不断向最小生成树中添加点,而这个添加的点是距离生成树最近的点。
这个算法主要用在稠密图里面,因为是不断添加点,所以时间复杂度和边没有关系,就算是稠密图有再多的边,只要点不多就没有问题。
实现 : 稠密图我们可以用邻接矩阵去储存图,所以开个g[i][j],表示从i到j的距离,如果是无向图的话,注意赋值的时候要g[i][j]和g[j][i]都来一遍,我们还需要一个dis[N]数组来记录一下每个点到生成树的距离,首先,我们需要对所有的点都遍历一次,所以最外层有个n次的for循环,对于每次操作,我们必须得找到距离最近的点,并且这个点我们不能加入过,所以我们可以再加一个数组记录一下某个点是否被遍历过,现在是怎么找到距离最近的点呢?定义一个minn变量,初始化为无穷大,如果遍历到的这个点的距离小于minn,就更改minn,这样就ok了,如果最后发现idx(记录距离最小的节点)没有发生改变,说明剩下的点距离都是无穷大了,说明出现了孤立点,它的距离没有被更新,这时候说明这个图没有最小生成树,如果idx被更新了,说明找到了距离最小的点,这时候就得更新其他没有加入过的点到生成树的距离了。还有就是初始化的问题了,dis数组初始化为无穷大,g[i][j]也初始成无穷大。
时间复杂度 : o(n*n)
例题
给定一个 nn 个点 mm 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
给定一张边带权的无向图 G=(V,E)G=(V,E),其中 VV 表示图中点的集合,EE 表示图中边的集合,n=|V|n=|V|,m=|E|m=|E|。
由 VV 中的全部 nn 个顶点和 EE 中 n−1n−1 条边构成的无向连通子图被称为 GG 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 GG 的最小生成树。
输入格式
第一行包含两个整数 nn 和 mm。
接下来 mm 行,每行包含三个整数 u,v,wu,v,w,表示点 uu 和点 vv 之间存在一条权值为 ww 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
数据范围
1≤n≤5001≤n≤500,
1≤m≤1051≤m≤105,
图中涉及边的边权的绝对值均不超过 1000010000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
#define endl "\n"
#define lowbit(x) (x&(-x))
const int N = 510;
int g[N][N];
int dis[N];
bool st[N];
int n,m;
void prim(){
memset(dis, inf, sizeof dis);
dis[1] = 0;
int res = 0;
for(int i = 1; i <= n; i++){
int idx = 0, minn = inf;
for(int j = 1; j <= n; j++){
if(!st[j]&&dis[j]<minn){
minn = dis[j];
idx = j;
}
}
if(idx==0){
cout << "impossible" << endl;
return;
}
res += dis[idx];
st[idx] = true;
for(int j = 1; j <= n; j++){
if(!st[j]&&dis[j]>g[idx][j]) dis[j] = g[idx][j];
}
}
cout << res << endl;
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) g[i][j] = inf;
}
while(m--){
int a,b,c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(c,g[a][b]);
}
prim();
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T=1;
//cin >> T;
while(T--) solve();
return 0;
}
//
//
//
//
//
我在写代码时出现的问题:g[a][b] = g[b][a] = min(c,g[a][b]); 因为有重边,我们要记录最短的一条,所以这样写,而不是g[a][b] = g[b][a] = c; 还有一个是dis[1] = 0; 因为需要起点的位置更新其他的距离,所以如果忘了这个的话,第一次就更新不了idx,然后就输出impossible了。
kruskal算法
这个算法思路主要是不断向最小生成树中添加边。
这个算法主要用在稀疏图里面,因为是不断添加边,所以时间复杂度和点没有关系,就算是稀疏图有再多的点,只要边不多就没有问题。
思路:我们用结构体去储存每一条边,然后把这些边按边权从小到大排序,然后添加的时候先挑边权小的边添加,每当判断一个边的时候,需要看这条边是否可以与之前已经构建的生成树合成一个环,如果能合成一个环的话,就舍弃这一条边,判断能否合成一个环可以用并查集来判断,并查集储存这个边的两个顶点,如果这两个顶点的代表元素相同,那么这两个顶点可以合成一个环。好奇怪,同一个代码第一次没过,找了一个小时bug,没找到问题,结果第二次过了???
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
#define endl "\n"
#define lowbit(x) (x&(-x))
const int N = 1e5+10;
int fa[N];
struct E{
int a,b,c;
bool operator < (const E& rhs){//通过边长进行排序
return this->c < rhs.c;
}
}edg[2*N];
int n,m,res,cnt;
int find(int x){
if(x==fa[x]) return x;
else{
fa[x] = find(fa[x]);
return fa[x];
}
}
void ksl(){
for(int i = 1; i <= m; i++){
int pa = find(edg[i].a), pb = find(edg[i].b);
if(pa!=pb){
res += edg[i].c;
fa[pa] = pb;
cnt++;
}
}
//cout << cnt << endl;
if(cnt>=n-1) cout << res << endl;
else cout << "impossible" << endl;
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i++) fa[i] = i;
for(int i = 1; i <= m; i++){
cin >> edg[i].a >> edg[i].b >> edg[i].c;
}
sort(edg+1,edg+1+m);
ksl();
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T=1;
//cin >> T;
while(T--) solve();
return 0;
}
//
//
//
//
//
结构体排序
bool operator < (const E& tmp){
return this->c < tmp.c;
}
标签:prim,int,kruskal,最小,笔记,生成,算法,dis,define
From: https://blog.csdn.net/2403_85701606/article/details/144392980