最短路径问题(单源+多源)
Dijkstra简介:
迪杰斯特拉算法(Dijkstra)是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
注意该算法要求图中不存在负权边。
核心思想:
找离自己相邻的最近的点,更新距离,重复操作。
其实这和人的思维方式有些相似。
模拟过程更利于理解
在理解Dijkstra的基础上,我们可以用
Dijkstra可解决以下几种最短路问题:
- 单源最短路问题(一个起点到一个终点 )
- 延伸到—>任意起点到终点
- 多源最短路问题( 多起点到多终点 )
单源问题
例如:
Dijkstra求最短路I–>简单入门的模板
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤105,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
模板,直接记。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int 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[t] > dist[j]))
t = j;
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c);
}
printf("%d\n", dijkstra());
return 0;
}
自问自答:
int t = -1;
t 是用来干嘛的?
t 是用来找临近的最近的点的。
这是朴素版的Dijkstra的算法,适用于稠密图。
那稀疏图怎么办?
用堆优化版的Dijkstra!
Dijkstra求最短路 II–>优化版
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1
号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 −1。
数据范围
1≤n,m≤1.5×105,
图中涉及边长均不小于 0,且不超过 10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e6+10;
typedef pair<int,int> PII;
int n,m;
int e[N],ne[N],h[N],idx;
int w[N],dist[N];
bool st[N];
void add(int a,int b,int c)
{
w[idx]=c;e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int Dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>> heap;//小根堆
heap.push({0,1}); //第一个元素存距离,第二个元素存点
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j] > dist[ver] + w[i])
{
dist[j] =dist[ver] + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout<<Dijkstra();
return 0;
}
搜索一次使用好像不太够,能不能多次搜索?
上例题!
畅通工程续–> 开工!
某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。
现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。
Input
本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。
Output
对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
Sample Input
3 3
0 1 1
0 2 3
1 2 1
0 2
3 1
0 1 1
1 2
Sample Output
2
-1
代码如下:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int x, y;
int g[N][N];
int dist[N];
bool st[N];
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st); //搜索前初始化,就可多次搜索
dist[x] = 0;
for (int i = 0; i <= n-1; i ++ )
{
int t = -1;
for (int j = 0; j <= n-1; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
for (int j = 0; j <= n-1; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[y] == 0x3f3f3f3f) dist[y] =-1;
}
int main()
{
while( scanf("%d%d",&n,&m) ! = EOF )
{
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
cin>> x >> y;
dijkstra();
printf("%d\n", dist[y]);
}
return 0;
}
能不能多次搜索关键在于处理st[ ](初始化为 false)。因为st[ ]用来判断相邻的点是否搜过,标记当前的状态,所以一次搜索过后想要再次搜索,只要把st[ ]初始化即可。
单源问题已解决,那多源问题怎么解决?
多源问题( 多起点到多终点 )
初学者遇到多源问题可能会很茫然,认为会很复杂,其实很简单,看下面的例题就懂了。
一个人的旅行 -->来一场说走就走的旅行
虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中
会遇见很多人(白马王子,0),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。
Input
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b
之间可能有多条路) 接着的第T+1行有S个数,表示和草儿家相连的城市; 接着的第T+2行有D个数,表示草儿想去地方。
Output
输出草儿能去某个喜欢的城市的最短时间。
Sample Input
6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10
Sample Output
9
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1200;
const int MAX = 0x3f3f3f3f;
int t, s, d;
int g[N][N], dist[N], f[N], w[N];
bool st[N];
int dijkstra(int x)
{
memset(st, false, sizeof st);
memset(dist,0x3f,sizeof dist);
dist[x] = 0;
for(int i = 0; i < N; i++)
{
int t=-1;
for(int j = 0; j < N; j++)
if(!st[j] && (t==-1 || dist[t] > dist[j] ))
t = j;
st[t] = true;
for(int j = 0; j < N; j++)
dist[j] = min( dist[j] , g[t][j] + dist[t] );
}
int res = MAX;
for(int i = 0; i < d; i++)
res = min( res, dist[w[i]] ); //找当前起点到所有终点的最短距离
return res;
}
int main()
{
while(scanf("%d%d%d", &t, &s, &d) != EOF)
{
memset(g,0x3f,sizeof g);
int x, y, time;
for(int i = 0; i < t; i++)
{
scanf("%d%d%d", &x, &y, &time);
g[x][y] = g[y][x] = min(g[x][y],time);
}
for(int i = 0; i < s; i++)
scanf("%d", &f[i]);
for(int i = 0; i < d; i++)
scanf("%d", &w[i]);
int rmin = MAX;
for(int i = 0; i < s; i++)
rmin = min(rmin, dijkstra(f[i]));
//输入一个起点就得到所有终点的最短距离,把所有起点输入,就得到离终点的最短距离
printf("%d\n", rmin);
}
return 0;
}
看完后是不是发现只加了几行代码,就有不一样的效果!
Dijkstra搜索一次顺便求出一个起点到多终点的最短距离,多次搜索便可得出最短距离。
个人认为Dijkstra多源最短路的解决方法是:将多起点到多终点拆分成两步。
- 求单个起点到所有终点的最短距离;
- 遍历所有起点,记录最小的距离;
用Dijkstra大多数情况下用于解决单源最短路,少数情况下解决多源最短路。
不足之处,请指正。
欢迎点赞与评论~
记得收藏