一、分类
n:点数,m:边数
注: 1、单源最短路:一个起点到其它所有点的最短路。
2、多源最短路:多个起点到其它所有点的最短路。
3、稀疏图和稠密图区分:稠密图:n^2~m,稀疏图:n~m。
4、有向无向不影响最短路,因为无向可拆分为两个有向。
4、初始化所有点距离:
memset(dist,0x3f,sizeof(dist)); dist[1]=0;
dist[x]:当前1~x最短路间距离
cnt[x]:………………边数
二、各个算法
1、 朴素Dijkstra算法
S:当前已经确定最短距离的点
模板思路:
1️⃣初始化所有点的距离
2️⃣迭代:
for(i:1~n)
{ t←不在S中的距离min的点 n^2次
S←t O(1) n次
用t更新其他点的距离 m次
(看dist[x]>dist[t]+w(权重))
}
例:
模板:
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N];//稠密图存储方式
int dist[N], n, m;
bool st[N];
void dijkstra()//Dijkstra算法
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if (dist[n] == INF) puts("-1");
else printf("%d\n", dist[n]);
}
int main()
{
memset(g, 0x3f, sizeof g);//初始化
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c);
}
dijkstra();
return 0;
}
2、堆优化Dijkstrar
迭代与朴素的区别
for(i:1~n)
{ t←不在S中的距离min的点 n次
S←t O(1) n次
用t更新其他点的距离 mlogn次
(看dist[x]>dist[t]+w(权重))
因为稀疏图,∴存储方式改为邻接表
void add (int a;int b,int c)//邻接表
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
模板
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, M = N, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int h[N], e[N], ne[N], w[N], idx;
int dist[N], n, m;
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue < PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
while (q.size())
{
auto t = q.top(); q.pop();
int u = t.second;
if (u == n) break;
if (st[u]) continue;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
if (dist[v] > dist[u] + w[i])
{
dist[v] = dist[u] + w[i];
q.push({dist[v], v});
}
}
}
if (dist[n] == INF) puts("-1");
else printf("%d\n", dist[n]);
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
dijkstra();
return 0;
}
3、Bellman-Ford
模板思路:
struct{
int a,b,w;
}edge[m];
for n次
memcpy(backup,dist,sizeof dist)//备份
for 所有边 a,b,w //a→b,w:权重
dist[b]=min(dist[b],+dist[a]+w);//松弛操作:更新最短路
循环后所有边一定满足dist[b]>=dist[a]+w //三角不等式
模板:有边数限制的最短路
#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010, INF = 0x3f3f3f3f;
int dist[N], backup[N], n, m, k;
struct Edge {
int a, b, c;
} e[M];
void Bellman_Ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
while (k -- )
{
memcpy(backup, dist, sizeof dist);
for (int i = 0; i < m; i ++ )
{
int a = e[i].a, b = e[i].b, c = e[i].c;
dist[b] = min(dist[b], backup[a] + c);
}
}
if (dist[n] > INF / 2) puts("impossible");
else printf("%d\n", dist[n]);
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
Bellman_Ford();
return 0;
}
4、SPFA---Bellman的优化
一般的Dijkstra算法也可以用SPFA
模板优化:那更新过的点更新带更新的点。
对松弛操作的优化:
queue←1
while queue不空
1、t←q.front(队头);
q.pop();
2.更新t的所有出边 t→b w边权
queue←b
模板:
1、spfa找最短路
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
q.push(1);
st[1]=true;
while(q.size()){//拿更新过的点更新代更新的点
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){//比较,如果成立就更新
dist[j]=dist[t]+w[i];
if(!st[j]){//更新完将当前最短路入队
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t=spfa();
if(t==0x3f3f3f3f)cout<<"impossible";
else cout<<t;
return 0;
}
2、spfa也可判断负环
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N],cnt[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
for(int i=1;i<=n;i++){
st[i]=true;
q.push(i);//全部入队,当找到一个最短路的时候,就false一下
}
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
***** cnt[j]=cnt[t]+1;//更新走过的步数;
**** if(cnt[j]>=n)return true;//当前步数大于总步数
**** //说明存在副回路,有负权回路
if(!st[j]){//更新完将当前最短路入队
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t=spfa();
if(t)cout<<"Yes";
else cout<<"No";
return 0;
}
Floyd
模板
d[i,j]//初始化,邻接矩阵存所有边
for(k=1,k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
d[i,j]=min(d[i][j],d[i,k]+d[k][j]);
//存i→j最短路径的长度
例题:Floyd求最短路
#include <bits/stdc++.h>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int dist[N][N], n, m, Q;
void Floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
memset(dist, 0x3f, sizeof dist);
for (int i = 1; i <= n; i ++ ) dist[i][i] = 0;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
dist[a][b] = min(dist[a][b], c);
}
Floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
if (dist[a][b] > INF / 2) puts("impossible");
else printf("%d\n", dist[a][b]);
}
return 0;
}