数据与算法之美

用数据思维解决意想不到的问题!

递推与储存,是动态规划的关键

0?wx_fmt=gif&wxfrom=5&wx_lazy=1



0?wx_lazy=1小智最近由于项目需要,经常要接触到一些规划类的问题。那今天就给大家讲一讲旅行商问题及其解法吧。


旅行商问题,即TSP问题(Travelling Salesman Problem)。问题是,有一个旅行商人要拜访n个城市,每个城市只能拜访一次,而且最后要回到原来出发的城市。这位商人如何设计拜访顺序,使走过的路径最短? 


640?wx_fmt=png&wxfrom=5&wx_lazy=1这种求最短距离问题有非常多实际场景会涉及到,比如,快递公司为了使城市A到城市B的快递件运输速度达到最快,他们会选择城市A到城市B的最短路径来进行运输。再比如,游戏当中的角色移动时,需要寻找到一条最短路径进行移动,这样游戏中的角色才不会失真。又比如,网络当中需要有路由算法,用于计算最短的信息跳转路径。


0?wx_fmt=gif&wxfrom=5&wx_lazy=1

一款RPG游戏的角色移动演示,你会发现角色走路沿最短路径。如果不这样设计,游戏的体验感则会非常糟糕


0?wx_fmt=png这么多实际的精彩案例,旅行商问题确实值得研究,而且学习完说不定能派上用场。


对于这类问题,我个人这次倾向使用动态规划算法来解这道问题,得到他的精确解。


0?wx_fmt=png动态规划这种技术也是有非常多实际场景会涉及到,比如,以动态规划为基础原理的维特比算法,现今常用于语音识别这个领域。再比如,生物信息学中需要进行DNA之间的比对,找出最长的公共DNA部分。


0?wx_fmt=gif

语音识别技术使得人机交互的难度不断下降,其背后离不开数学


0?wx_fmt=png这里首先讲解一下动态规划的步骤:

1、分段:将原问题分解为若干个相互重叠的子问题。

2、分析:分析问题,找出递推式。

3、求解:利用递推式自底向上计算,实现动态规划过程。


640?wx_fmt=png其实,动态规划的精妙之处在于,每个子问题只求解一次,并将解保存在一个表格中,当需要再次求解此子问题时,只是简单地通过查表获得该子问题的解,避免大量的重复计算。


0?wx_fmt=png为了更好地在旅行商问题上讲解动态规划算法,这里讲一个简单的例子,隔壁村的老王要去这4座城市拜访,城市之间的距离如下图。


0?wx_fmt=png

四座城市之间的距离示意图


图中一共有4座城市,城市0、城市1、城市2、城市3。小智目测走完这几个城市的最短路程为10(城市0→城市1→城市2→城市3→城市0)


0人总是有直觉,而且有时会非常准。


当然,我们要用科学而完整的方法而得到最短的拜访路径,不能总是依赖直觉,尽管直觉经常是对的。我们先用穷举法列一下所有路径:


0?wx_fmt=png


这是暴力的穷举法,一共需要运算3!×4=24次加法。


0?wx_fmt=png暴力法是能得到结果,但是时间复杂度是阶乘阶O(n!),是算法当中复杂度极高的等级。

常见的算法时间复杂度由小到大依次为:

常数阶Ο(1)<对数阶Ο(logn)<线性阶Ο(n)<线性对数阶Ο(nlogn)<平方Ο(n^2)<立方阶Ο(n^3)<…<指数阶Ο(2^n)<阶乘阶Ο(n!)。

 

从动态规划的角度看,假如我们找到一条最短的路径:城市0→城市1→城市2→城市3→城市0


0?wx_fmt=png那么,城市1→城市2→城市3→城市0必然是城市1到城市0的一条最短路径。

假设该路径不是城市1到城市0的一条最短路径,设该路径的总路程为d,那么会有一条新的路径作为城市1到城市0的最短路径d’, d > d’,那么城市0→新路径→城市0为完成拜访的最短路径,与原假设”找到一条最短路径:城市0→城市1→城市2→城市3→城市0”矛盾。


按照上述结论,可以将路径进行分解。设最短路径的符号标记为 L。那么:


L(城市0→城市0) = L(城市0→城市1) + L(城市1到城市0)


请注意,城市0是起点城市和终点城市。


假如已经获知各座城市到城市0的最短路径,那么从城市0到城市0归结起来一共有三种路径:


L(城市0→城市1) + L(城市1到城市0)

L(城市0→城市2) + L(城市2到城市0)

L(城市0→城市3) + L(城市3到城市0)


在上述路径找到最短的路径,即为拜访所有城市的最短路径。


上述路径可以继续分解。比如,以第1条路径为例:


L(城市1到城市0) = L(城市1→城市2) + L(城市2到城市0)


就这样,路径一直可以被分解,到什么时候结束呢?假设继续以上述的L(城市1到城市0)为例,继续对L(城市2到城市0)进行分解:


L(城市2到城市0) = L(城市2→城市3) + L(城市3到城市0)


由于城市1、城市2之前已经出现过了,因此L(城市3到城市0)只能等于L(城市3→城市0)


640?wx_fmt=png至此,城市间的路径全部可以被分解。


我们以符号来表达上述的过程。假设 d(i, V) 表示从城市 i经过城市集合 V各点一次后返回到出发点的最短距离。d(i, V) 可以按照一下方式分解:


0?wx_fmt=png


其中,Cki 为城市 k 到城市的距离。


这样,动态规划的递推式出来了。


从城市0出发,经过城市1、2、3后回到城市0的最短路径长度为:


0?wx_fmt=png


这个是最后一个阶段的决策,它必须依据 d(1, {2,3})d(2, {1,3}) 和 d(3, {1,2})的计算结果,继续分解:


0?wx_fmt=png


当然,还可以继续进行分解:


0?wx_fmt=png


上述过程可以发现,在计算的时候,可以不断引用先前的计算结果,这就是动态规划的特点。我们将上述过程在表格中把填写一下:


0?wx_fmt=png


0?wx_fmt=png至此,发现拜访完所有城市的最短距离为10,印证了小智原来的想法。


上述执行加法的次数:


0?wx_fmt=png


时间复杂度为O(2n)。动态规划相比起穷举法下降了一个等级,这个算法起到了重要的价值。


完成原理阐述,是时候写代码了,先整理一下伪代码:

1、初始化一个表格d,用于记录计算结果。并以距离初始化第0列

2、循环j=1:2n-1-1

循环 i=(1:n,且不包含在V[j])

        循环 V[j]中的所有元素

         d[i][j] = min(c[i,k], d[k][V[j]去掉k元素后对应的序号])

3、计算d[0][2n-1-1] = min(c[0,k] +d[k][2n-1-2],此为最短路径长度


0?wx_fmt=png这里需要注意的是,集合V是从小变大,要先算V较小的部分,因此要找一个方式来表达集合V,然后对计算进行顺序。


这里将数字转换为二进制序列,以表示集合V拥有的元素,比如:[1,0,0],可表示集合V只包含元素1,[0,1,1],可表示集合V包含元素2和元素3。


二进制数可以映射到自然数区,比如:


0 →[0,0,0]、1 → [1,0,0]、2 → [0,1,0]、3 → [1,1,0]

4 →[0,0,1]、5 → [1,0,1]、6 → [0,1,1]、7 → [1,1,1]


为了实现集合从小到大的计算顺序,对二进制序列进行特别的排序,其中第一排序条件是序列的总和,升序。第二排序条件是序列代表的数字,升序,转换为以下排序。


0?wx_fmt=png


这样,按照上述表格中的序号作为顺序进行计算即可。


0?wx_fmt=png可以动手编程了。我们挑一道更难的题目,有10座城市的TSP问题:


0?wx_fmt=png

另一道题目:10座城市之间的距离矩阵


有了想法很快就可以付诸实践了(回复“TSP程序”可以获得程序),这是最美妙的地方。按照上述流程,最短路径距离为284,最短路线:


0→3→1→5→7→6→2→8→4→9→0


0?wx_fmt=png当然,这个并不是一个完美的解法,随着城市数量的增加,计算难度呈指数增长,目前关于TSP在多项式时间内的解法目前还没出现,但我相信未来一定会诞生。


0?wx_fmt=png

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

递推与储存,是动态规划的关键

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭