一、课程目标
- 单源最短路径
- dijkstra算法
- 算法设计
- 模拟过程
- 优化算法
二、目标详解
1、单源最短路径
给定一个图,给定起点,求该起点到其它点的最短距离,称之为单源最短路径问题。
2、dijkstra算法
dijkstra算法是一个求单源最短路问题的经典算法,应用前提是图中没有负权边。
理解要点一
--三个集合:
O集合
:还没有被松弛连通的顶点。方案:d[x]==ooU集合
:已计算出距离,但尚未确定最短路径的顶点。方案:!vis[x] 且 d[x]<ooS集合
:已确定最短距离的顶点(和最短距离),显然,如果S包含了所有的顶点,问题也就解决了。方案:顶点用vis[N]记录,最短距离用dis[N]记录。
理解要点二
--三个过程:
求最小值
:对U集合,求最小值mn和对应顶点x加入S集合
:将该最小距离的顶点x加入到S集合更新U集合
:用x顶点,对其邻接边进行”松弛”,更新距离(d[i])。这是一个更新(及新增)U集合的过程
总结
:每次从U集合中选取最短距离的顶点,加入到S集合,然后用该顶点进行松弛操作,更新U集合。显然,这是一个将顶点从O->U->的过程,不断采用贪心策略执行这个迁移过程,直到所有能连通的顶点都加入到了S集合。
3、算法设计
数据设计
:
- vis[N]:记录顶点是否在S集合,初始都为false
- d[N]:起点到某顶点的最短距离,初始d[s]=0,其它为inf,d[x]<inf表示已松弛过
- g[N][N](或g[N]):邻接矩阵、或邻接表
伪代码
(假设s为起点):
初始化(vis[i]都为false、dis[i]都为oo,dis[s]为0);
for(n次)
对所有U集合的边求最小值(第一次只有d[s],其它都是oo)
将最小边加入S集合(vis[min] = true)
用min顶点进行松弛
4、模拟过程
对上图做dijkstra算法,计算从A开始的所有最短路径。
初始
:S集合为空(vis[i]都为false),U集合只有A(d[A] = 0,其它为oo)
以下v为1(红色)表示S集合,d里面红色的表示U集合
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 0 | 0 | 0 | 0 | 0 | 0 |
d | 0 | oo | oo | oo | oo | oo |
第一步
:对U求最小(d[A]),加入S,松弛(d[C]=10, d[E]=30, d[F]=100)
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 1 | 0 | 0 | 0 | 0 | 0 |
d | 0 | oo | 10 | oo | 30 | 100 |
第二步
:对U求最小(d[C]),加入S,松弛(d[D]=60)
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 1 | 0 | 1 | 0 | 0 | 0 |
d | 0 | oo | 10 | 60 | 30 | 100 |
第三步
:对U求最小(d[E]),加入S,松弛(d[D]=50,d[F]=90)
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 1 | 0 | 1 | 0 | 1 | 0 |
d | 0 | oo | 10 | 50 | 30 | 90 |
第四步
:对U求最小(d[D]),加入S,松弛(d[F]=60)
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 1 | 0 | 1 | 1 | 1 | 0 |
d | 0 | oo | 10 | 50 | 30 | 60 |
第五步
:对U求最小(d[F]),加入S,松弛(没有邻接边)
- | A | B | C | D | E | F |
---|---|---|---|---|---|---|
v | 1 | 0 | 1 | 1 | 1 | 1 |
d | 0 | oo | 10 | 50 | 30 | 60 |
第六步
:对U求最小,没有元素了,返回
5、优化算法
priority_queue(优先队列):优先级高的先出队。每次新入队的元素,会自动被排序到合适的位置,时间复杂度为O(logn)。
在dijkstra算法中,每个顶点都要求U集合中的最小距离,而这里除了最近松弛的边之外,其它边上一次求最小距离时都已经计算过一次,属于重复计算。
使用优化队列,每次将最小值出队,即可解决这个重复计算的问题。
由于优化队列的时间复杂度为O(logn),因此总复杂度为O(nlogn)。
理解
:用队列来表示U集合,出队即为找U集合的最短距离,将之加入到S集合,并松弛更新U集合。
三、扩展理解-priority_queue
1、pair方式
priority_queue默认为最大值出队,在dijkstra算法里要最小值出队。
一种方法是要greater<pair<int, int> >,但是整体定义长,容易写错:
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > q;
//也可以先#typedef简化,例如
typedef pair<int, int> Edge;
priority_queue<Edge, vector<Edge>, greater<Edge> > q;
2、自定义结构体
第二种方法是自定义结构体,重载<运算符
struct Edge {
int y, w;
bool operator > (const Edge &e) const {
return w > e.w;
}
};
然后定义priority_queue:
priority_queue<Edge, vector<Edge>, greater<Edge> > q;
3、反边法
不管用pair还是用自定义结构体,都可以用反边的方法来简化定义。
例如用pair时,可以在q.push时将边权负一下,这样最大值出队就变成了最小值出队。
或者自定义结构体,重载<运算符,但代码用return w > e.w, 这样也是改变成了最小值出队。
这两种方法都可以简化priority_queue的定义
priority_queue<Edge> q;