前言
****
首先我们来看看什么叫是最短路。看上图,顶点代表不同的地方,边的距离代表不同地方之间的距离。假如小屈住在1号顶点,小孙住在5号顶点,小屈想要找到小孙玩耍,小屈可以直接找小孙 1 -> 5需要走10公里,但是小屈如果聪明一些,会选择绕道2号顶点,再去小孙家,1 -> 2 -> 5,一共需要走9公里。这就算一种最短路:)
一、Dijkstra简介
该算法由Edsger Wybe Dijkstra于1959年提出。是求一个点(源点)到其余各顶点的最短路径,也叫“单源最短路径”。Dijkstra算法有一个局限性,无法处理由负权边的图。但还是很有用的,因为现实问题很少有出现负权边。比如你买车票,人家总不能倒贴你钱吧~。重重之重: Dijkstra处理的图一定不能出现负权边,这一点很重要要牢记!!!
二、算法实现
1.算法具体思想:
1.首先我们定义 dis[i] 数组,表示源点到其余顶点的最短距离。拿上图来举例,我们假设源点为 1 号顶点,dis[ 2 ]就表示源点(1号顶点)到 2 号顶点的最短距离。初始化 dis[i] 数组, dis[1] = 0,因为源点到自己的距离就是0嘛; 其余位置初始化为无穷大,因为你现在还不知道去其余顶点的 “ 路 ”。多提一下, dis[1] = 0,此时我们把它标记为已经求出最短路的顶点。你不可能找到比0还短的路了。
2.然后遍历图,然后遍历 1 号顶点(最小值点), 发现分别有两条边 1 -> 2和 1 -> 5,这两条边比我们在dis数组里面记录的距离 (无穷大)要小,所以我们更新dis数组的值。dis[2] = 2 和 dis[ 5 ] = 9。(更新dis数组)
3.然后, 遍历 dis 数组找到值最小的顶点, 当前最小的就是 2号顶点,通过上一轮操作就已经求出了 源点 到 2 号顶点的最短路!把 2 号顶点标记为已经求出最短路的结点。为什么说到 2号顶点的最短路已经求出了 ? 因为你不可能通过其他顶点来缩短 源点到2号顶点的距离! 因为所有边的权值都是正值!因为该图不存在负权边!如果通过其他边,意味着距离的增加! 这里如果读者不明白,可以画图模拟模拟:)
4.通过 n 轮上述操作就可以求出 “ 单源最短路 ”, 请看下面的详细代码(可以帮助理解~
2.算法代码实现:
//读代码建议 :先看变量的定义,在看main()函数:)
//基于贪心算法, 输出的是顶点1 到顶点n 的最短距离, O(n*n)
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100;
//记录点数和边数
int n, m;
//临接矩阵 -- 存储图
int g[N][N];
//记录 dis数组 顶点 1 到顶点 n 的最短距离
int dis[N];
//记录该顶点是否已经求出最短路了 ?
bool st[N];
void dijkstra() {
// 初始化dist数组, 每一个值被初始化为 0x3f3f3f3f, 把0x3f3f3f3f当作无穷大
memset(dis, 0x3f, sizeof(dis));
// 因为 1 是源点,所以 初始化0
dis[1] = 0;
//dijkstra算法核心
//通过n次遍历 , 把n个顶点的加入到最短路集合
for (int i = 0; i < n; i++) {
//TODO::找到距离源点距离最短的点
//t保存的是顶点号
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dis[t] > dis[j])) // 该结点必须是为求出最短路, 求最小值
t = j;
//TODO::加入最短路集合,标记已经求出 源点 到 结点t 最短路了
st[t] = true;
//TODO::通过该顶点更新dis数组,遍历dis数组
for (int j = 1; j <= n; j++)
if (dis[j] > dis[t] + g[t][j])
//如果源点到j的距离 小于 源点到t的距离 + t 到 j 的距离。则进行更新
//就像是换条路来试试 到达目的地的距离能否更短, 这个操作也被称为 “松弛操作”
dis[j] = dis[t] + g[t][j];
}
}
int main() {
//读入点数 和 边数
cin >> n >> m;
//初始化地图, 地图的每一个值被初始化为 0x3f3f3f3f
memset(g, 0x3f, sizeof(g));
//读入边
while(m --) {
int a, b, c;
// 顶点a 到 顶点b 的距离是 c;
cin >> a >> b >> c;
//这里使用min,是图可能存在重边, 选择最小边保存就好
g[a][b] = min(g[a][b], c);
}
//调用dijkstra算法
dijkstra();
//输出结果
cout << "到每个顶点的最短距离 :";
for(int i = 1; i <= n; i++)
cout << dis[i] << ends;
cout << endl;
system("pause");
return 0;
}
/*
输入样例
5 8
1 5 10
1 2 2
2 5 7
2 3 3
3 1 4
3 4 4
4 5 5
5 3 3
输出样例
0 2 5 9 9
*/
可以使用输出样例 和 输入样例 来测试代码~
总结一下
Dijkstra是一种基于贪心策略的算法。众所周知 "贪心的证明 "是很麻烦的~~ ,我也知识口述了一些贪心的思想, 帮助理解
我发出来的基础班Dijkstra算法复杂度为O( N * N )。很明显,求最小值的那一步可以使用堆来优化为O( m*log n ),这就交给各位读者了。
本人写博客的目的也只是为了加深对算法的印象,如果可以帮到大家就更好了。有错误,还请大家指正~
参考资料 《啊哈算法》