- Dijkstra 算法 -
Dijkstra(迪杰斯特拉)算法:
Dijkstra 算法是典型的单源最短路径算法
,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想)
,直到扩展到终点为止。核心思路
是从顶点 A 往外延伸,不断更新 A 到其他点的距离,我们称这个操作为松弛
。该算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。
复杂度:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
优点: 可优化。如果经过堆优化
(用优先队列priority_queue
),Dijkstra的时间复杂度能达到 O(nlogn)
,如果这个图特别稠密的话,也就是m特别大(比如完全图就是n^2),那么 O(nlogn) 是要小于m的。
缺点: 不能解决带有“负权边”
的问题。当不存在源s可达的负回路时,可用Bellman-Ford算法
实现,时间复杂度为O(VE)
。
算法过程:
1、初始化点与点之间的距离为无穷大(即INF),相同的点之间的距离为0,而估计值(即最短路径估计 shortest-path estimate) 为源点到各个点之间的距离,标记源点为已访问。
for(int i=0; i<N; i++)
for(int j=0; j<N; j++)
map[i][j]=(i==j ? 0 : INF);
2、 遍历与节点 i 相连的所有节点,找到距离最近的一个,把这个节点标记为已访问,并更新最短路。因为新加入一条边,可能有很多的最短路会发生改变,所以要更新所有与 最短路径包含的点(即已访问的点) 相连的点(即未访问的点)的最短路。
void Dijkstra(int s) { //s为源点
int minr, temp; //minr记录最近的点的距离,temp记录该点的位置
memset(vis, 0, sizeof(vis)); //初始化所有节点为未访问
vist[s]=1; //标记源点为已访问
for(int i=1; i<n; i++)//设有n个点,则重复进行n-1次
dis[i]=map[s][i];
for(int i=1; i<=n; i++) {
minr=INF;
for(int j=1; j<=n; j++) {
if(dis[j]<minr && !vis[j]) {
minr=dis[j];
temp=j;
}
}
vis[temp]=1;
for(int j=1; j<=n; j++) {
if(minr+map[temp][j]<dis[j] && !vis[j]) {
dis[j]=minr+map[temp][j];
}
}
}
return ;
}
例题:
一个人的旅行
题意:
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是 time 小时;(a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。
Output 输出草儿能去某个喜欢的城市的最短时间。
数据范围:
1=<(a,b)<=1000
解题思路:
Dijkstra 算法
考虑到本题中有多个起点,而Dijkstra 算法本质上只能求一个点到其他所有点的最短距离,于是就需要把问题转化为单源点,那么设草儿家的编号为0,城市编号为1~n,草儿家到邻近城市的距离为0,以草儿家即 0 号为源点即可。
以下时用邻接矩阵
实现 Dijkstra 的代码,另外也可以采用邻接表
来实现(然小生暂时还不会,ԾㅂԾ,~~)。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
#define INF 0x3f3f3f
typedef long long ll;
const int N=1005;
int map[N][N]; //存储图
int dis[N]; //存储估计值
int vis[N], n;//vis数组标记是否走过
void Dijkstra() {
int minr, temp;
memset(vis, 0, sizeof(vis));
vist[0]=1;
for(int i=0; i<=n; i++)
dis[i]=map[0][i];
for(int i=1; i<=n; i++) {
minr=INF;
for(int j=1; j<=n; j++) {
if(dis[j]<minr && !vis[j]) {//注意"!vist[j]"的条件
minr=dis[j];
temp=j;
}
}
vis[temp]=1;
for(int j=1; j<=n; j++) {
if(minr+map[temp][j]<dis[j] && !vis[j]) {
dis[j]=minr+map[temp][j];
}
}
}
return ;
}
int main() {
int t, s, d, sx[N], ex[N], a, b, time;
while(scanf("%d %d %d", &t, &s, &d)!=EOF) {
n=0;
for(int i=0; i<N; i++) for(int j=0; j<N; j++) map[i][j]=(i==j ? 0 : INF);//i和j从0开始而不是1
while(t--) {
scanf("%d %d %d", &a, &b, &time);
if(map[a][b]>time)//注意可能有重边,需要加这步去重操作
map[a][b]=map[b][a]=time;
n=max(max(n, a), b);
}
for(int i=1; i<=s; i++) {
scanf("%d", &sx[i]);
map[0][sx[i]]=map[sx[i]][0]=0;//不是dis[sx[i]]=0
}
for(int i=1; i<=d; i++)
scanf("%d", &ex[i]);
Dijkstra();
int res=INF;
for(int i=1; i<=d; i++)
res=min(res, dis[ex[i]]);
printf("%d\n", res);
}
return 0;
}