dijkstra算法是用于解决单源最短路问题的著名算法
问题的提出
给一个n=6
个顶点的边带正权的有向图e
,求起点1
到其余顶点的最短路径
准备工作
与Floyd算法一样,我们依然采用邻接矩阵表示法存放 e e e的信息:
除此之外,我们还需要用一个一维数组dis[N]
来存储起点1
到其余各个顶点的初始距离:
可以看见dis
最初其实就是邻接矩阵e
第一行的拷贝
接下来我们将所有顶点划分为两类:
- A类:顶点
1
和该类中的顶点之间的最短距离已求得; - B类:A类以外的其他顶点;
顶点1
到自身的距离就是最短距离,显然在最初,顶点1
属于A类,其余属于B类。
算法步骤
-
在B类中寻找离顶点
1
最近的顶点,即确定一个i
使得dis[i]
最小,将i
加入A类。当前离顶点1
最近的是顶点2
,则此时dis[2]
的值就是顶点1
到顶点2
的最短距离。为什么会有这一结论,我们来分析一下:"当前顶点
2
离顶点1
最近,也就是说对任意i!=2
都有dis[i]>=dis[2]
,
而且对任意的i,j
都有e[i][j]>0
(即没有负权边),
所以对任意的k
都有dis[k]+e[k][2]>dis[2]
,换句话说,引入其他任意一个顶点作为中转点,顶点1
和顶点2
之间的距离都会变大,
这就证明了此时dis[2]
的值就是顶点1
到顶点2
的最短距离 " -
对于步骤1找到的
i
,对它的出边进行松弛操作。 有2->3
和2->4
这两条出边。我们先来看2->3
,讨论通过这条边能否缩短顶点1
到顶点3
的距离,也就是判断
dis[2]+e[2][3]<dis[3]
是否成立,发现dis[2]+e[2][3]=1+9=10
而dis[3]=12
,判断条件成立,则dis[3]
的值更新为dis[2]+e[2][3]
的值10
,松弛成功。接下来对2->4
进行同样的操作,将dis[4]
更新为4
。
对顶点2
的所有出边进行松弛操作后,dis
数组已经更新为:
-
重复步骤1、2的操作,直到A类包含
e
中的所有顶点。 这里列出每次经过步骤2后dis
数组的更新情况:
在这个例子中顶点6
没有出边,所以dis
不作更新,最终的dis
数组如下:
这便是顶点1
到各顶点的最短路径。
献上代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <stdio.h>
#include <limits.h>
#include <cmath>
#include <stdlib.h>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <utility>
#include <windows.h>
#include <climits>
#define LL long long
#define INF 999999
using namespace std;
const double pi = atan(1.) * 4.;
const int N = 51;
const int MOD = 1e9 + 7;
int n, m;
int a, b, c;
int e[N][N]; //邻接矩阵
int dis[N]; //顶点1到顶点i的距离
int book[N]; //记录顶点i是否属于A类
int dmin; //和顶点1距离的最小值
int imin; //和顶点1距离最近的顶点号
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
//输入顶点数、边数
cin >> n >> m;
//准备工作-----------------------------------
//构建邻接矩阵
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
e[i][j] = (i == j) ? 0 : INF;
//输入每条边的信息
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
e[a][b] = c;
}
//dis最初其实就是邻接矩阵e第一行的拷贝
for (int i = 1; i <= n; i++) {
dis[i] = e[1][i];
}
//将顶点1归到A类
book[1] = 1;
//核心步骤-----------------------------------
//B类有n-1个点,循环n-1次
for (int k = 1; k <= n - 1; k++){
//遍历数组dis,寻找i使得dis[i]最小
dmin = INF;
for (int i = 2; i <= n; i++){
if (book[i] == 0 && dis[i] < dmin) {
dmin = dis[i];
imin = i;
}
}
//将寻找到的最小i归到A类
book[imin] = 1;
//对imin的所有出边进行松弛操作
for (int j = 1; j <= n; j++) {
dis[j] = min(dis[imin] + e[imin][j], dis[j]);
}
}
//输出最终的dis数组
for(int i = 1; i <= n; i++)
cout << dis[i] << ' ';
}
/*
input:
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
output:
0 1 8 4 13 17
*/
优化
实际上,上面描述的只是dijkstra算法的一个最基础的版本,它的复杂度是 O ( n 2 ) O(n^2) O(n2),在大数量级的顶点数和边数下效率并不高,我们可以从以下两方面着手,进行对算法的优化:
- 对于边数很少的稀疏图( m < n 2 m<n^2 m<n2)来说,邻接矩阵的存储效率很低,就像大盘子装小菜一样,会浪费很多无用的空间,在搜索的时候也会因为遍历太多“不存在的边”而效率下降;而用邻接表表示法只存储有效边,每次搜索的时候,有多少边就检查多少边,效率大大提高;
- 每次寻找离顶点1最近的顶点的时间复杂度为 O ( n ) O(n) O(n),可以用二叉堆来优化这一查找过程,每次获取距离最小的顶点,只需要直接拿出堆顶的数据即可。
优化后算法的复杂度可以降至 O ( n l o g n ) O(nlogn) O(nlogn)。
优化后的代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <stdio.h>
#include <limits.h>
#include <cmath>
#include <stdlib.h>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <utility>
#include <windows.h>
#include <climits>
#define LL long long
#define INF 999999
using namespace std;
const double pi = atan(1.) * 4.;
const int N = 51;
const int MOD = 1e9 + 7;
int n, m;
int a, b, c;
struct node { //邻接表的结点结构
int v; //终端顶点
int w; //边权
node(int vv, int ww) :v(vv), w(ww) {}
bool operator>(const node& other) const {
return w > other.w;
}
};
vector<node> e[N]; //邻接表
int dis[N]; //顶点1到顶点i的距离
int book[N]; //记录顶点i是否属于A类
priority_queue<node, vector<node>, greater<node> > pq; //由于每次获取最小值,所以使用小顶堆
int imin; //和顶点1距离最近的顶点号
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
//输入顶点数、边数
cin >> n >> m;
//准备工作-----------------------------------
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
e[a].push_back(node(b, c));
}
memset(dis, INF, sizeof(dis));
dis[1] = 0;
for (int i = 0; i < e[1].size(); i++) {
dis[e[1][i].v] = e[1][i].w;
pq.push(e[1][i]);
}
book[1] = 1; //将顶点1归到A类
//核心步骤-----------------------------------
while (!pq.empty()) {
imin = pq.top().v; //获取和顶点1距离最近的顶点号(堆顶元素)
pq.pop();
if (book[imin] == 1) //若已属于A类则舍弃它
continue;
//将imin归到A类
book[imin] = 1;
for (int i = 0; i < e[imin].size(); i++) {
int tv = e[imin][i].v;
int tw = e[imin][i].w;
if (book[tv] == 0 && dis[tv] > dis[imin] + tw) {
dis[tv] = dis[imin] + tw;
pq.push(node(tv, dis[tv]));
}
}
}
for (int i = 1; i <= n; i++) cout << dis[i] << ' ';
}
/*
input:
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
output:
0 1 8 4 13 17
*/
如果我的总结有不足之处,十分欢迎读者指出,希望借此机会互相学习进步!!