一: 算法分析
a. 问题介绍:
b. 问题分析:
这就是典型的要求最小生成树的问题,给定一些散落的点,求花费最小代价去生成这个树的答案,这个题目我们求解的思路可以基于贪心有两种求解方法,两种不同的方法的求解思路的注重点会有区别。
第一种------Prim算法:Prim算法注重于对点的选取,并且求解思路和Dijkstra算法基本是一样的,每次选取一个未被选取过的点并且是距离当前已经建好的树集合最近的点将它加入到这个集合当中,再用它去更新其他点到这个已经建好的集合的距离。重复这个过程即可。
第二种------Kruskal算法:Kruskal算法就更注重于对于边的选取,我们利用贪心的原则将边从小到大排序,每次选取权值最小的边看它能否帮助我们建立这个树,如果可以就将这个边选取出来,直到我们将树建好。(具体如何实现见下方代码分析)
c. 算法总结:
Prim算法:选取点 n 次 * 每次更新其他点 n 次
Kruskal算法: 将边加入到优先队列进行排序 m * logm
时间复杂度:
Prim算法: O(n n) ,适用于稠密图。
Kruskal:O(m logm),适用于稀疏图。
二: 代码分析
1. Prim 算法
a. 算法分析:类似于Dijkstra算法 = 重复3步骤
找到未被选取且距离已经选取树集合最近的点 + 加入到选取的集合当中 + 利用这个点更新其他点到树林的最近距离 = 重复这3个步骤即可
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510, inf = 0x3f3f3f3f;
int g[N][N], n, m, dis[N], res=0;
bool vis[N];
int prim(){
// 初始化设置 dis[1] = 0,从 1 开始选取这个树集合
bool flag = true;
dis[1] = 0;
//预计一共进行n次, 选取 n 个点进入树集合即成功
for(int i = 1; i <= n ; i ++ ){
// 第一步: 找到未被选取且距离已经选取树集合最近的点
int t = -1;
for(int j = 1; j <= n; j ++ ){
if(!vis[j] && (t == -1 || dis[j] < dis[t] ) )
t = j;
}
/*判断下是否可以选取,如果为inf其实是不可以再选取到这个点,
即最近的点和选好树集合不可以连接,返回建树失败 */
if(dis[t] == inf) return inf;
// 第二步: 加入到选取的集合当中
vis[t] = true;
res += dis[t];
//第三步: 利用这个点更新其他点到树林的最近距离
for(int j = 1; j <= n ; j ++ )
dis[j] = min(dis[j], g[t][j]);
}
}
int main(){
// 初始化都默认不可以到达,设置距离为无穷远
memset(g, 0x3f, sizeof(g) );
memset(dis, 0x3f, sizeof(dis) );
//输入边,只要保存点之间的最小权值边即可
cin >> n >> m;
for(int i = 0; i < m; i ++ ){
int x, y, z;
cin >> x >> y >> z;
g[x][y] = g[y][x] = min(g[x][y], z);
}
int t = prim();
if(t == inf) puts("impossible");
else cout << res <<endl;
}
2. Kruskal 算法
a. 算法分析:预处理边+不断选取对于建树有帮助的边
预处理边:利用优先队列进行排序即可完成,权值小的边放前面
建树有帮助的边:就是可以帮助本来不相连的两个部分相连,刚好并查集可以完成这个事情,快速判断这两个部分是否是相连
#include<iostream>
#include<algorithm>
#include<map>
#include<queue>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
// 利用优先队列pq储存 <边的权值, <起始点, 到达点 > >
typedef pair<int, pair<int, int> > PIII;
priority_queue<PIII, vector<PIII>, greater<PIII> > pq;
int n, m, res = 0, f[N], cnt;
//并查集功能实现查找是否属于同一个部分当中
int find(int x){
return x == f[x] ? x : f[x] = find(f[x]);
}
int kruskal(){
for(int i = 1; i < N; i ++ ) f[i] = i;
while(!pq.empty() ){
PIII t = pq.top();
pq.pop();
int d = t.first, x = t.second.first, y = t.second.second;
int fx = find(x), fy = find(y);
// 判断是否有利于建树,如果不在同一个部分就可以帮助建树
if(fx != fy){
cnt ++ ;
res += d;
f[fx] = fy;
}
}
/* 判断选取边的数量, 如果选取了n-1个边则正好可以完美建树了,否则存在无法相连的边建树失败 */
if(cnt == n-1) return true;
return false;
}
int main(){
cin >> n >> m;
for(int i = 0; i < m; i ++ ){
int x, y, z;
cin >> x >> y >> z;
pq.push({z, {x, y} } );
}
bool t = kruskal();
if(t == false) puts("impossible");
else cout << res << endl;;
}