前言
- 本文将直接使用邻接表的数组形式进行图的存储,邻接矩阵的存储方式在此算法的实现上较为简单,邻接表的链式存储较为麻烦,且申请释放内存会消耗大量时间,在此不再赘述。
- 本文使用的邻接表数组存储来源于《啊哈!算法》,由于我水平有限,只能暂时先使用这种包含5个数组的邻接表。
- 本文中的算法不记录路径只记录距离。
- 本文中所有数组的第一个存储单元即a[0],都没有被使用。
- 此算法不能解决带有负权边的图,因此我们规定本文中的图里面不存在负权边。
- 代码采用C++实现,不过只要有C语言的基础就可以看懂,没有用到很多C++的东西。
- 若对本文存在疑问、意见或建议,欢迎评论及与我本人联系。
- 算法的优化详见后文。
术语简介
- 权值:表示两点之间的距离、耗费等具有某种意义的数值
- 弧:有向的边,在图上用箭头表示,<x,y>表示从点x到点y有一条弧(请注意<y,x>与其是不同的两条弧)
- 弧尾:弧的起点,x
- 弧头:弧的终点,y
- 路径:由连续的弧构成的顶点序列,在图中,存在路径的两点之间不一定存在直达的弧
- 入边:以某点为弧头的边
- 出边:以某点为弧尾的边
Dijkstra入门
最短路径问题
在图论中,最短路径是一类十分常见的问题,常见的有SSSP(Single-source Shortest Paths,单源最短路径)和APSP(All Pairs Shortest Paths,多源最短路径。而今天讲到的Dijkstra算法,主要用来解决SSSP问题。它指的是以某个点为起点,从该点到其余各个顶点的最短路程/最小代价。此算法也可以解决APSP问题,只是它较为复杂,不推荐使用。请注意此类问题与最小生成树问题的区别与联系:最小生成树问题结果会涉及到图中的所有顶点,而最短路径问题的结果可能只涉及到图中的一部分顶点。
算法思想介绍
以此有向图为例,为了求解从顶点1开始到各顶点的最短路径,Dijkstra算法首先确定了两个集合A、B,其中A集合中包含已经确定最短路径的顶点,B集合包含未确定最短路径的点。其次我们需要一个数组dis来记录从起点到各个顶点的最短距离,一个数组book来记录顶点是否在A集合中。
接下来我们要对上述两个数组进行初始化,这里我们以1号顶点作为起点。先将book数组全部置为false,表示所有点都未确定最短距离。同时对dis数组进行初始化,访问邻接表,若起点与点i之间存在弧,则将dis[i]置为弧的权值,否则置为inf(无穷)。全部初始化完成后,将起点——1号顶点加入集合A中,即book[1]置为true,众所周知自己到自己的最短路径是0,因此将dis[1]置为0。至此准备工作完成。
此时数组情况如下:
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | false | 1 |
3 | false | 12 |
4 | false | inf |
5 | false | inf |
6 | false | inf |
下面正式开始算法!!!(敲黑板.jpg)
- 从除1号顶点之外的其他所有顶点中(即集合B中的顶点)找到离1号顶点最近的顶点,即从以1号顶点为弧尾/起点的弧中找到权值最小的那条弧(这里就是顶点2啦),并将其加入集合A中(即book[2]=true)
- 接下来对其余顶点进行处理。以在集合B中的顶点j为弧头/终点,访问以2号顶点为弧尾/起点的弧,观察以2号点为中转点,从1号到j号顶点的路径长度是否缩短,即dis[j]是否大于dis[2]+弧<2,j>权值 ,若大于,则进行松弛。此时我们首先发现,dis[3]>dis[2]+9,因此将dis[3]更新为10。此时数组情况如下:
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | true | 1 |
3 | false | 10 |
4 | false | inf |
5 | false | inf |
6 | false | inf |
紧接着我们又发现,dis[4]>dis[2]+3,因此将dis[4]更新为4。此时数组情况如下
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | true | 1 |
3 | false | 10 |
4 | false | 4 |
5 | false | inf |
6 | false | inf |
- 重复上述过程,直到所有的顶点都进入集合A中,算法结束。
接下来发现集合B中离1号顶点最近的顶点是4号,进行处理后数组如下:
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | true | 1 |
3 | false | 8 |
4 | true | 4 |
5 | false | 17 |
6 | false | 19 |
继续寻找,这次选择3号顶点,进行处理后数组如下
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | true | 1 |
3 | true | 8 |
4 | true | 4 |
5 | false | 13 |
6 | false | 19 |
继续寻找,这次选择5号顶点,进行处理后数组如下:
编号 | book | dis |
---|---|---|
1 | true | 0 |
2 | true | 1 |
3 | true | 8 |
4 | true | 4 |
5 | true | 13 |
6 | false | 17 |
继续寻找,这次选择6号顶点,将其加入集合A后,由于它没有出边,且此时所有顶点都已在集合A中,因此无需进行处理。
算法实现
构造出的输入数据如下:
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
六个顶点九条边
代码如下:
#include<iostream>//Dijkstra迪杰斯克拉算法模板
#define inf 999999999//定义inf
using namespace std;
int cur=1;//记录当前边的编号
int dis[10];//记录源点到各顶点的最短路径
bool book[10];//记录已经找到最短路径的点
int from[10],to[10],value[10];//分别表示起点,终点,权值
int start[10],nt[10];//分别表示起点和"链域"
<