算法设计与分析:贪心算法(2)- 最短路问题(DP到贪心的优化)

本文参考UCAS卜东波老师算法设计与分析课程撰写

前言

在前文:算法设计与分析:贪心算法 - 排课问题(DP与贪心的区别与应用)中,我们初步地了解了贪心算法与动态规划的区别,贪心解决了一个问题的简化版本,不必再大张旗鼓地使用动态规划。本文接着借由最短路径问题来讲述贪心算法的应用。

最短路径问题

问题描述与分析

  • 给定一个图 G = V , E G=\lang V,E\rang ,对于图中每条边 e = i , j e=\lang i,j\rang 都有一个距离 d i , j d_{i,j} 。一个起始点s与一个终点t,问从s到t的最短路径是多少?
    首先寻路问题显然是一个多步决策问题,先考虑用动态规划的方式解决,如果我们直接用 O P T ( i , j ) OPT(i,j) 表示点i到点j的最短路径,这个要求太过严苛,因为转移方程无法与明确的 d ( u , v ) d(u,v) 做关联。因此我们引入一个新的变量k,使得 O P T ( i , j ) OPT(i,j) 变成 O P T ( i , j , k ) OPT(i,j,k) (从i到j经过最多k条边的最短路径),又由于起始点固定,我们只用考虑到达点即可,最终定义 O P T ( v , k ) OPT(v,k) :从s到点v最多经过k条边的最短路径。由此我们可以得到状态转移方程如下:
    O P T ( v , k ) = min { O P T ( v , k 1 ) min u , v E { O P T ( u , k 1 ) + d u , v } OPT(v,k) = \min \begin {cases} OPT(v,k-1) \\ \min_{\lang u,v\rang \in E}\{OPT(u,k-1)+d_{u,v}\} \end{cases}

    解释一下,公式右下角指的是存在一条边从u到v,我们要在最多k条边内到达v,有两种可能,一是根本用不到k条边,直接最多用k-1条边就可以到达;二是我们先用最多k-1条边到达u,再用一条uv到达v(这里面可能有多个u满足,取最小者),比较这两种较小者就是 O P T ( v , k ) OPT(v,k)

    这个其实是Bellman Ford算法,一种用于求解单源最短路径的算法,借由上面的动态转移方程,我们可以得到如下伪代码:
    在这里插入图片描述

Bellman Ford算法流程实例

有了上面的伪代码,我们举一个简单例子来实际应用看看,图示如下:
在这里插入图片描述

解释一下右边的OPT矩阵,同一颜色的数字代表同一循环内计算结果,蓝色为初始化结果(前面伪代码中OPT(s,0)初始化应该为0,不受后面无穷的影响),我用紫色的箭头表示点(v,k)是从(u,k-1)而来的,这里u可能为v。这个图的过程建议对照伪代码,自己画一下加深理解。

那么我们很容易知道s到v的最短路径为4,上述表格还可以继续延申,因为k最多可能为8。

Bellman Ford算法优化

  • 优化点1:去除冗余计算
    观察该表发现,偌大一张表存的数实际上就只有几个,注意图中斜向下的箭头和水平向右箭头的区别,前者表示我多走一条边,但是能够让路径更短,后者表示我多走一条边,路径也不会短,干脆不走。那么很多重复的值应该是不必要计算的,为了找到什么时候不再继续计算该点的最小值(即该点不可能再被更新地更小了)。为此,我们定义一个特殊点 v v^* 如下:
    O P T ( v , k 1 ) = m i n v O P T ( v , k 1 ) OPT(v^*,k-1) = min_vOPT(v,k-1)
    即在最多k-1条边中,OPT值最小的点(s点除外),我们将每一轮k对应的 v v^* 添加到一个集合S中,一旦添加就意味着这个点的OPT值在后续不会再发生变化,也就没有计算的必要。证明不用计算如下:

  • 证明:本列最小OPT点无需再计算
    由上面的定义,我们有 v v^* 的转移方程如下:
    O P T ( v , k ) = min { O P T ( v , k 1 ) ( 1 ) min u , v E { O P T ( u , k 1 ) + d u , v } ( 2 ) OPT(v^*,k) = \min \begin {cases} OPT(v^*,k-1) &(1) \\ \min_{\lang u,v^*\rang \in E}\{OPT(u,k-1)+d_{u,v^*}\} & (2) \end{cases}
    我们必然有 ( 2 ) > ( 1 ) (2)>(1) ,因为 O P T ( u , k 1 ) O P T ( v , k 1 ) d u , v 0 OPT(u,k-1)\ge OPT(v^*,k-1) 且 d_{u,v^*}\ge 0 ,前面一个不等式是因为我们定义 v v^* 就是OPT值最小的那个点。所以,我们有
    O P T ( v , k ) = O P T ( v , k 1 ) OPT(v^*,k) = OPT(v^*,k-1) 这个公式告诉我们,该点不会再继续变化了,那我们还计算它干什么?
    上述公式反映在图中如下:
    在这里插入图片描述
    由此,我们得到改进的Bellman Ford算法,伪代码如下:
    在这里插入图片描述
    我们也得到了第一个优化结论:

    • 对于最多利用k-1条边走到v的最近点(体现在表中就是每列OPT值最小的点),它以后的OPT值不会再变化
  • 优化点2:仅考虑相邻最近点
    前面我们已经将保证了一旦算出最短距离后不再冗余计算该点OPT,但我们仍然需要对所有不在已遍历集合S中的 点挨个计算,比较直观的感觉是,y与t离s是很远的,在k=1的时候应该不需要计算 O P T ( y , 1 ) , O P T ( t , 1 ) OPT(y,1),OPT(t,1) (注意这里的远是指离已遍历点远),同样地,在k=2时,已遍历集合中多了u,此时y离已遍历集合近了,但t仍然离 { s , u } \{s,u\} 远(没办法直达),那么这时的 O P T ( t , 2 ) OPT(t,2) 是不是也可以不用计算呢?我们详细看一下使得点的OPT值变化的部分(即斜向下的箭头),我在原图的基础上标红了部分箭头,如下所示:
    在这里插入图片描述
    我们容易发现一个规律,所有最终稳定到最小OPT值的点,其来源均是之前已放入已遍历集合S中的点。

    这么说有点抽象,我们按照k的增长来说明:
    当k=1时,S={s},其更新了u,v.x三个点(它们与s相邻,一步可达),而y,t与s不相邻,因此没必要对y,t做计算。
    当k=2时,S={s,u},其更新了v,x,y(相邻),但t不相邻,虽然t被更新到了5,但是这个数字更不更新对最终结果并没有影响
    当k=3时,S={s,u,v,x},其更新了y,t(相邻),发现此时t被更新了,但它的更新来源于x,与之前的5没有关系(5换成无穷也是一样结果)

    由此,我们知道,那些与已遍历集合不相邻的点无需计算。再进一步,那些相邻的点我们是否全部需要计算?这里先给出一个小结论:

    d ( v ) d(v) 表示从s到v的最短距离,设 u u^* 是所有未遍历相邻点中离已遍历点最近的点,也即 d ( u ) = min w S { d ( w ) + d ( w , u ) } d(u^*)= \min_{w\in S}\{d(w)+d(w,u)\} 。那么路径 P : s . . . w u P: s\to...\to w \to u^* 是s到 u u^* 的最短路径,路径长度为 d ( u ) d(u^*)

    这个结论听起来有点复杂,我们看个图例就能明白:
    在这里插入图片描述

    解释一下图例:
    已遍历集合为{s,x,w},相邻未遍历集合点(也可称一步到达点)为{y, u u^* },不相邻未遍历点为z(不用计算),对于已遍历点,我们知道 d ( x ) = 1 , d ( w ) = 2 d(x) = 1,d(w) = 2 ,而 d ( x ) + d ( x , y ) = 4 d ( w ) + d ( w , u ) = 3 d(x)+d(x,y) = 4,d(w)+d(w,u^*)=3 ,所以3是s到 u u^* 的最短路径长度。相当于 u u^* 的最短路径不会再发生变化

    为什么 u u^* 的最短距离不再变化?用反证法就可证明,假设存在另一个条路径 P : s . . . x y . . . u P':s\to ...\to x\to y \to ...\to u^* 要更短,即 u u^* 不是通过w直达,则一定有 P d ( s , x ) + d ( x , y ) d ( w ) + d ( w , u ) = d ( u ) |P'|\ge d(s,x)+d(x,y) \ge d(w)+d(w,u^*) = d(u^*) ,矛盾。
    由此,我们可以得出第二个优化结论

    • 与已遍历点相邻的最近点 u u^* ,其到s的最短距离已经确定

      这里的最近不要理解为 d ( v , u ) d(v,u^*) 最小,应该是 d ( v ) + d ( v , u ) d(v) + d(v,u^*) 最小

总结

由以上两点优化,我们已经可以将Bellman Ford算法优化为Dijkstra算法。关于Dijkstra算法,我打算专门写一篇新的文章叙述,包括Dijkstra算法的进一步优化。在本文中,我们主要需要掌握Bellman Ford算法与其的两个可优化点,通过该问题的分析与优化过程,让我们在遇到新的动态规划问题的时候就懂得怎么一步步将他转变为贪心,上面的优化其实也是贪心的思想,我们不再考虑长远的点,就只计算最近的相邻点,这就是一种局部最优的思想,所以Dijkstra 算法也属于贪心算法。

如果你觉得文章对你有用,不妨顺手点个赞哦~

发布了46 篇原创文章 · 获赞 99 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/GentleCP/article/details/103099264
今日推荐