Dijkstra算法的产生背景
Edsger Wybe Dijkstra ,著名的荷兰计算机科学家,计算机科学史上最具影响力的人物之一,于1956年构思出了当今世界最经典的算法之一:Dijkstra算法
1956年,Dijkstra 在荷兰数学中心工作,当时他接到了来自荷兰航空公司的需求:帮助该公司规划最优航线。接到该需求以后的某一天,Dijkstra 与他的未婚妻在街上购物,走累了就在一家咖啡馆休息,随即,老爷子(当时还是年轻人)便灵光乍现,仅用20分钟便思考出了后来大名鼎鼎的Dijkstra单源最短路径算法
One morning I was shopping in Amsterdam with my young fiancee, and tired, we sat down on the cafe terrace to drink a cup of coffee. I was just thinking about whether I could do this, and I then designed the algorithm for the shortest path. As I said, it was a 20-minute invention.
------Edsger Dijkstra
这让人不得不感慨,天才跟普通人的差距竟能如此之大...
Dijkstra算法的概念
了解完历史背景后,各位肯定会好奇Dijkstra算法究竟有什么魔力,为什么会在计算机科学里具有如此高的地位?别急,在研究Dijkstra算法前,我们先来了解下面几个概念
源点
源点(Source) ,就是一条路径起始的第一个顶点,并且称最后一个顶点为终点(Destination) ,例如一条路径:V0 ->V1 ->V2 ->V3 ->V4,对整条路径 ,源点就是V0,终点就是V4。
需要注意的是,刚才的例子里着重强调了对整条路径,这是因为对不同路径,源点和终点不是固定的,比如对路径V1 ->V2 ->V3,显然源点是V1,终点是V3,也就是说,只有对给定的一条路径 而言,源点和终点是确定的。 当路径V1 ->V2 ->V3是路径V0 ->V1 ->V2 ->V3 ->V4的子路径 时,整条路径的源点是V0。在从V1出发遍历寻找离源点(也就是V0) 最近的点时,可以称呼V1为起点。
最短路径
最短路径,那肯定就是最短的路径咯(bushi)。更准确的定义是,对无向图而言 ,从顶点Vi 到顶点Vj 所包含的边最少 的路径叫做从源点Vi 到终点Vj 的最短路径那这个最短路径怎么求呢?哎,这时候Dijkstra算法就派上用场了
Dijkstra算法
Dijkstra算法 用于求解单源最短路径 问题,也就是找出从一个连通图 (边权必须非负 )中指定源点出发到图中其他所有顶点的最短距离,该算法所运用到的核心思想是"贪心 策略",具体的实现逻辑如下:
1. 从源点出发,此时我们确定从源点到起点的距离是0(源点和起点不是一个东西!源点和起点不是一个东西!源点和起点不是一个东西!)
2. 每次从所有未知最短距离的顶点中挑选一个离源点最近的顶点
3. 一旦选中该点,就认为找到了到该点的最短路径,然后以该点为新的起点 去更新与它相邻的顶点的距离信息
4. 重复第2个和第3个操作,直到所有点 都被处理后结束
对该算法的代码实现,我们要明确几个数组的用法:
dist数组:用于记录起点到各个点的当前最短距离,初始化为极大值
visited数组:用于标记哪些点的最短路径已经确定,初始化为false
pre 数组:用来存储最短路径的具体走法,pre[i] = j 表示在从源点 到顶点 i 的最短路径上,顶点 i 的上一个前驱节点是顶点 j,初始化为-1
下面我们来看具体的代码实现(C++):
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//定义一个无穷大值
const int INF = 0x3f3f3f3f;
//定义变量
int n,m;//顶点数 边数
vector<int> dist;//记录源点到各顶点的最短距离
vector<bool> visited;//标记是否已确定最短路径
vector<int> pre;//记录路径的前驱节点
//邻接边表存图
vector<vector<pair<int,int>>> Graph;
//dijkstra实现函数
void dijkstra(int source){
//初始化
dist.assign(n,INF);//初始化所有距离为无穷大
visited.assign(n,false);//初始化所有顶点为未访问状态
pre.assign(n,-1);//初始化前驱节点为-1
//源点到起点的距离为0
dist[source] = 0;
//循环遍历直到所有顶点都被处理
for(int i=0;i<n;i++){
//从所有为访问的点中找出dist最小的顶点
int u = -1;//tmp存储dist最小的点的信息
int min_dist = INF;
//遍历所有未访问的节点
for(int j=0;j<n;j++){
if(!visited[j] && dist[j] < min_dist){
u = j;
min_dist = dist[j];
}
}
if(u == -1)break;//若图不是连通图,直接退出
visited[u]=true;//标记该顶点已访问
//遍历顶点u的相邻顶点
for(int k=0; k<Graph[u].size(); k++){
int v = Graph[u][k].first;//顶点u的相邻顶点
int w = Graph[u][k].second;//边权
//松弛操作:发现并更新最短路径的操作
if(dist[u] + w < dist[v]){
dist[v] = dist[u] + w;//更新最短距离
pre[v] = u;//记录前驱,即顶点v的前驱节点是顶点u
}
}
}
}
int main(){
cin>>n>>m;//顶点数 边数
Graph.resize(n);//更新图的顶点个数
//初始化边
for(int i=0;i<m;i++){
int x,y,w;
cin>>x>>y>>w;
//因为是无向图,所以添加双向边
Graph[x].push_back({y,w});
Graph[y].push_back({x,w});
}
int source;//源点
cin>>source;
dijkstra(source);
//输出结果
for(int i=0;i<n;i++){
if(dist[i] == INF){
cout<<source<<"无法到达"<<i<<endl;
}
else{
cout<<source<<"到"<<i<<"的最短距离为:"<<dist[i]<<",路径是";
//利用pre数组回溯路径
vector<int> path;//保存最短路径的倒序
int p = i;
while(p != -1){
path.push_back(p);
p = pre[p];
}
//倒序输出路径
for(int j=path.size()-1;j>=0;j--){
cout<<path[j]<<" ";
}
cout<<endl;
}
}
return 0;
}
/*
9 16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
3 6 3
4 5 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4
0
0到0的最短距离为:0,路径是0
0到1的最短距离为:1,路径是0 1
0到2的最短距离为:4,路径是0 1 2
0到3的最短距离为:7,路径是0 1 2 4 3
0到4的最短距离为:5,路径是0 1 2 4
0到5的最短距离为:8,路径是0 1 2 4 5
0到6的最短距离为:10,路径是0 1 2 4 3 6
0到7的最短距离为:12,路径是0 1 2 4 3 6 7
0到8的最短距离为:16,路径是0 1 2 4 3 6 7 8
*/
时间复杂度为:O(n^2)
写在末尾
以上就是我对Dijkstra算法的理解了,本人水平有限,文字里没有体现的东西也尽力在代码中展示了,若还有细节不周到之处还请多多体谅,希望对大家有所帮助!