[总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)_数据结构

目录

最短路

朴素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]) )

[总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)_数据结构_02

输入样例:

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到集合的距离)

[总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)_#include_03

输入样例:

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不连通,将此边加入集合

[总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)_dijkstra_04

输入样例:

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;
}