[总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)
目录
最短路
朴素Dijkstra
时间复杂度: O(n2+m) , n 表示点数,m 表示边数
稠密图👉 存储形式:邻接矩阵
模板: g[x][y]存的是初始化时边权, st[i]表示i是否加入正在逐步扩张的集合, dist[i] 存的是1到i的最短距离,也就是我们的目标
思路:
求1 → n 的最短路径
1. 初始化距离 dist[1] = 0, dist[i] = +∞
2. for i 1~n:
for j 1~n:
t ← 不在集合s中的, 且距离集合最近的点
s ← t (加入组织,即st[t]=true)
for j 1~n:
用 t 来更新其他点的的距离(已在集合中的点到1的距离可能不是最短,dist[j]取值更新为 min(dist[j], dist[t] + g[t][j]) )
849. Dijkstra求最短路 I - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/851/
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int dist[N],g[N][N];
bool st[N]; //标记是否在集合中
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < n; i ++)
{
int t = -1;
for(int j = 1; j <= n; j ++)
if(!st[j] && (t==-1 || dist[j] < dist[t]))
t = j;
//if(t == n) break; //可以提前break, 优化时间开销
st[t] = true;
for(int j = 1; j <= n; j ++) //更新集合中的最短路径
dist[j] = min(dist[j], dist[t]+g[t][j]);
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
int a,b,c;
for(int i = 0; i < m; i++)
{
scanf("%d%d%d", &a, &b, &c);//边数较多,scanf省时
g[a][b] = min(c, g[a][b]); //由题意,图中可能存在重边和自环
}
cout << dijkstra();
}
最小生成树
Prim 算法
(与Dijkstra思想非常相似,只有更新dist时有区别,前者是到集合中与之距离最小的点,后者是到起始点的距离最小的点)
思路:
1. 初始化距离 dist[i] = +∞
2. for i 1~n:
for j 1~n:
t ← 不在集合s中的, 且距离集合最近的点
若i!=1(即不是初始时刻的点)且dist[t]==+∞(该图不连通), 直接退出(补充:只要不是第一个点,dist[t]就表示当前的点和已经连好的的集合里的某点之间的长度)
for j 1~n:
用 t 来更新其他点的的距离(已在集合中的点到1的距离可能不是最短,dist[j]取值更新为 min(dist[j], g[t][j]) ) (补充:min(dist[j], g[t][j])中dist[j]表示之前当点t没有加入集合时 点j到集合的距离, g[t][j] 表示如果点t加入集合时 点j到集合的距离)
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n,m;
int g[N][N],dist[N];
bool st[N];
int prim()
{
memset(dist, INF, sizeof dist);
int res = 0; //最小生成树的权值之和
for(int i = 0; i < n; i++)
{
int t = -1;
for(int j = 1; j <= n; j++)
if(!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
if(i && dist[t]==INF) return INF;
if(i) res += dist[t];//★,在更新之前加, 否则可能有自环的权会被算进来
for(int j = 1; j <= n; j++)
dist[j] = min(dist[j], g[t][j]);//★
st[t] = true;//★
}
return res;
}
int main()
{
cin >> n >> m;
memset(g, INF, sizeof g);
int a, b, c;
while (m -- )
{
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(c, g[a][b]); //★
}
int res = prim();
if(res == INF) cout << "impossible";
else cout << res;
return 0;
}
Kruskal 算法
(本质其实是并查集)
思路:
1. 所有边按权重从小到大排序
2. 枚举每条边ab, 权重c
如果a,b不连通,将此边加入集合
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5+10, INF = 0x3f3f3f3f;
int n ,m;
int p[N];
struct Edge{
int a, b, w;
bool operator < (const Edge& e)const
{
return w < e.w;
}
}e[N];
int find(int x) //并查集(压缩路径)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(e, e+m);
if(e[0].w == INF) return INF;
for (int i = 0; i < n; i ++ ) p[i] = i; //并查集初始化
int res = 0, cntEdge = 0;
for (int i = 0; i < m; i ++ )
{
int pa = find(e[i].a), pb = find(e[i].b);
if(pa != pb){
res += e[i].w;
p[pa] = pb;
cntEdge ++;
}
}
if(cntEdge != n-1) return INF;
else return res;
}
int main()
{
cin >> n >> m;
int a, b, w;
for (int i = 0; i < m; i ++ )
{
scanf("%d%d%d", &a, &b, &w);
e[i] = {a, b, w};
}
int res = kruskal();
if(res == INF) cout << "impossible";
else cout << res;
return 0;
}
本文来自博客园,作者:泥烟,CSDN同名, 转载请注明原文链接:https://www.cnblogs.com/Knight02/p/15799052.html