写在前面:好久没看图论了,今天写作业的时候发现我已经忘记了。俗话说的好,好记性不如烂笔头,我翻了翻之前的笔记,竟然没有关于Dijkstra的(手动狗头)。赶紧连夜写了一篇博客,只为勾引我对Dijkstra的些许记忆。
单源最短路径-Dijkstra算法
从一个顶点到其余各顶点的最短路径算法,解决的是有权图(不存在负权边)中最短路径问题
1.算法思想
它是由E.W.Dijkstra提出的一种按照路径长度递增的次序分别产生到各顶点最短路径的贪心算法。
该算法把带权图的的所有顶点分成两个集合S和V-S。集合S中存放的是已经找到最短路径的顶点。V-S中存放未找到最短路径的顶点。算法按照路径长度递增的次序。逐一将V-S中的元素移动到S中。直至V-S为空。
其中,用到一个非常重要的原理:如果当前v0到vi的最短路径已经找到,那该条路径也一定是v0到路径中所有点的最短路径。换句话说,如果要找V0到Vi的最短路径,那么在Vi之前的i-1个顶点肯定已经找到最短路径了,即i-1个顶点都在集合S中。
如图中,0-4的最短路径为0-1-7-8-2-5-4.那么从v0到v8的最短路径一定是0-1-7-8.
我们以8号结点为例,进行证明:
假设v0-v8的最短路径是0-7-6-8,那么也就说明图中红色路径的长度小于0-1-7-8的长度。
那么从起点0-4的最短路径就是下图中的红色所示。
这与前提不符合,即假设不合理,所以从v0到v8的最短路径一定是0-1-7-8.
2.基本实现过程
Dijkstra算法按照从给定起点到图中顶点的距离,顺序求出最短路径。首先求出从起点到最接近点的顶点之间的最短路径。然后求出第二近的,一次类推。推而广之,在第i次迭代开始以前,该算法已经确定了i-1个顶点的最短路径。假定上一次(第i-1次迭代)加入集合S的顶点为T。那么在集合V-S中,与A相邻的顶点集合称为“边缘顶点”,他们作为候选对象。而Dijkstra算法可以从这些候选对象中选出下一个最接近源点V0的顶点,并把它移动到V-S集合中。
- 将所有的顶点分为两部分:已知最短路径的顶点集合S和未知最短路径的顶点集合V-S。可考虑用book数组实现,book[i]=1,可表示在S中,book[i]=0,则表示在S-V中。
- 初始化设置源点V0到自己的最短路径为0.若存在有源点能直接到达的顶点i,就把V0-Vi的最短路径设置为对应的长度。同时把其他顶点的最短路径设置为∞。同时book数组清零。
- 在集合S-V中选择一个距离远点最近的顶点i,加入到集合S中。并考察所有以i为起点的边,对每一条边进行松弛操作。例如存在一条从顶点u到顶点v的边,则可通过u拓展一条从s到v的路径即s -> u -> v,这条路径的长度为dis[ u ]+e[ u ][ v ],如果这个值比dis[ v ]的值要小,我们可以用新值替代当前dis[ v ]中的值
- 如此重复第三步,如果集合V-S为空,则结束。最终dis数组中的值就是源点到各点的最短路径
实现代码模板
void dijkstra(int s) //s为源点即起点
{
int i=0,j=0;
//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
for(i = 1;i <= n;i++) //将起点到每一个点的距离存入dis数组
dis[i] = e[s][i]; //e[][]数组是邻接矩阵村的图
book[s] = 1; //起点的最短距离已确定,移入S集合
for(i = 1;i < n;i++) //循环除起点外所有点共n-1次
{
//寻找距离1号顶点最近的顶点
int minn = INF;
for(j = 1;j <= n;j++) //遍历集合V-S,即book为0的点
{
if(dis[j] < minn && book[j] == 0) //找出这些点中离源点最近的点
{
minn = dis[j]; //记录最近距离
u = j; //记录最近的点
}
}
book[u] = 1; //顶点u已经确定最短距离,移入集合S,标记为1
//松弛:遍历顶点u的边缘顶点,借助刚确定的u顶点作为中转点刷新最短距离
for(j = 1;j <= n;j++)
if(e[u][j] < INF) //如果u点到j点有路
//源点s到j点的距离大于借助u点中转的距离(就是源点到u的距离加u到j的距离)
if(dis[j] > dis[u] + e[u][j])
dis[j] = dis[u] + e[u][j]; //更新源点到j顶点的最短距离
}
}
Tips:个人回忆小博客,不足之处,希望大佬纠正。