$ \text{Dijkstra} $ 算法用于解决单源最短路问题的算法之一,是图论较为基础的部分。
- 提示:本文为作者早期时所写,部分内容可能描述不清,过于复杂,包含错误等。
前言
$ \text{Dijkstra} $ 算法用于解决单源最短路问题的算法之一,是图论较为基础的部分。
实现方法:
1.定义ans[100000],ans[i]代表到达i点的最小花费
2.定义bool数组visit,代表是否来过这里
2.ans[起点]=0,其余的赋值为inf
3.定义一个curr变量,visit[current]=1(访问过),代表现在的位置,初始值为起点。
4.列举所有与curr相联通的的点,将这些点(i)的ans值更新:
$ ans[i]=min(ans[i],ans[curr]+ $ 到这些点需要的花费 $)$
5. 列举所有访问过的的点,找到ans值最小的点,赋值给curr,visit[current]=1(访问过)
6 所有点都访问过(visit[i]都==1),程序结束。此时,ans[i]代表从起点到i的最短路径
伪代码
1 | bool vis[1000000];//是否访问过 |
存图方式
链式前向星
以下是关于链式前向星的介绍 ,会的可以跳过:
边的存储
这种存图方式只需要开一个数组存储每个点引出的第一条边,然后存储每个点作为起点的每条边,这样就可以做到不重不漏。
在链式前向星存图中,我们需要定义一个结构体:
1 | struct EDGE |
和一个数组:
1 | int head[1000000]; |
和一个变量:
1 | int cnt=0;//指针 |
你会发现竟然没存起点!!其实起点是用$head$存的
举例:
如图:这样的一个有向图,输入是:
1 | 1 2 |
逐步分析:
1.输入1 2,代表1连向2。
1 | cnt++;//作为结构体下标,没有意义 |
此时:
$cnt=1$
$edge$ | $cnt=1$ | $cnt=2$ | $ cnt=3$ | $cnt=4$ |
---|---|---|---|---|
$to $ | $2$ | $0$ | $ 0$ | $0$ |
$next$ | $ 0 $ | $0 $ | $0$ | $0$ |
$head $ | 下标$=1 $ | 下标$=2$ | 下标$=3 $ | 下标$=4 $ |
---|---|---|---|---|
值 | $1 $ | $ 0 $ | $ 0 $ | $ 0 $ |
2.输入1 3,代表1连向3。
1 | cnt++; |
此时:
$cnt=2$
$edge$ | $cnt=1$ | $cnt=2$ | $ cnt=3$ | $cnt=4$ |
---|---|---|---|---|
$to $ | $2$ | $3$ | $ 0$ | $0$ |
$next$ | $ 0 $ | $1 $ | $0$ | $0$ |
$head $ | 下标$=1 $ | 下标$=2$ | 下标$=3 $ | 下标$=4 $ |
---|---|---|---|---|
值 | $2 $ | $ 0 $ | $ 0 $ | $ 0 $ |
3.输入1 4,代表1连向4。
此时:
$cnt=3$
$edge$ | $cnt=1$ | $cnt=2$ | $ cnt=3$ | $cnt=4$ |
---|---|---|---|---|
$to $ | $2$ | $3$ | $ 4$ | $0$ |
$next$ | $ 0 $ | $1 $ | $2$ | $0$ |
$head $ | 下标$=1 $ | 下标$=2$ | 下标$=3 $ | 下标$=4 $ |
---|---|---|---|---|
值 | $3 $ | $ 0 $ | $ 0 $ | $ 0 $ |
4.输入2 3,代表2连向3。
此时:
$cnt=4$
$edge$ | $cnt=1$ | $cnt=2$ | $ cnt=3$ | $cnt=4$ |
---|---|---|---|---|
$to $ | $2$ | $3$ | $ 4$ | $3$ |
$next$ | $ 0 $ | $1 $ | $2$ | $0$ |
$head $ | 下标$=1 $ | 下标$=2$ | 下标$=3 $ | 下标$=4 $ |
---|---|---|---|---|
值 | $3 $ | $ 4 $ | $ 0 $ | $ 0 $ |
注意:$edge[cnt].next$ 和$head[1]$存贮的都是结构体下标(即$cnt$的值)若要访问指向的边的编号,分别用$edge[edge[cnt].next].to$,$edge[head[1]].to$
若需要记录权值,在结构体中加入一个元素即可
代码:(带权值)
1 |
|
注意:
这里的next指的是遍历时的下一条边,head指的是遍历时的第一条边,而存边时相当于反过来操作,所以next记录上一条边,而head记录最后一条边。
边的遍历
在遍历以x为起点的所有边时,只需要这样就行
for(int i=head[x];i!=0;i=edge[i].next)
这个循环的结束条件是i等于0,因为最后一条边,也就是存边时第一条边,在把head值存进next时,head还没有更新过,也就是0。所以当next返回0时,就说明这些边遍历完毕了。
优势与特点
既可以存图,也可以存树,比起邻接矩阵,链式前向星的空间复杂度是O(n),大大节省了存储空间,因为按边存储省掉了很多两点无边的空间。并且在遍历的时候,那些与起点无边相连的点也不需要进行处理,可以说时间和空间都占优势,这就是被OIer们广泛使用的原因。
代码
1 |
|
以上是Dijkstra的基础介绍,上面的这份代码是不加优化的Dijkstra,能通过
P3371单源最短路径(弱化版)
不过在这道题中会TLE。
优化
在上面第五步中:5. 列举所有访问过的的点,找到ans值最小的点
这一步由于每次都要遍历一遍所有点,复杂度为$O(n)$
不过可以用堆(STL:优先队列)进行优化,复杂度为$O(logn)$
简单介绍优先队列
一种数据结构,支持找出数列中最大值,弹出最大值
重载运算符后还可以维护最小值
- 代码:
头文件
1 |
定义
1 | priority_queue<int> q; |
加入
1 | q.push(a); |
弹出最大值
1 | q.pop() |
查询最大值
1 | q.top() |
详细的可看P3378 【模板】堆
代码
对以上代码加入堆优化:
由于每个点有两个值需要存储($ans$值,点的编号),所以要用结构体
由于要找最小的点,所以用优先队列时需要重载运算符:
1 | struct node |
代码:
1 |
|