赣州新闻网

首页 / 科技

七点告诉你为何特斯拉过去死不了还在变更好

2020-02-07 · 赣州新闻网 · 科技

Dynamic Programming

Dynamic Programming是五大常用算法策略之一,简称DP,译作中文是“动态规划”,可就是这个听起来高大上的翻译坑苦了无数人,因为看完这个算法你可能会觉得和动态规划根本没太大关系,它对“动态”和_ ? v Q h k“规划”都没有太深的体现。

举个最简单的例子去先浅显的理解它,有个大概的雏形,找一个数组中的最大元素,如果只有一个元# A前最大元素和新加入的进行比较,较大的就是数组中最大的,这就是典型的DP策略,将小问题的解保存起来,解决大问题的时候: ] @ ] : 1就可以直接使P g j ; W T M用。

刚刚说的如果还是感觉有点迷糊,不用慌,下面几个$ 1 V I d P U b简单的小栗子让你4 S p ^ )明白这句话的{ X 8 B意思。

0、Fibonacci再讨论(易)

26 R t W T、小青蛙跳台阶再讨论(易)

3、最长公共子序列问题(偏难)

Fibonacci再/ Z J讨论

第一个数是1,第二个数也是1,从第三个数开始,后面每个数都等于前两个数之和。要求:输入一个n,输出第n个斐波那契数。

还是我们上节讨论递归与分治策略时候举的第一个例子——Fibonacci数列问题,它实在{ u m w L # 5 =太经典了,所以将其反r s c . %复拿出来说。

我们如果深入分析一# I 2 o t ( e |下上节说过的递归方法解决Fibonacci数列,就会发现出现了很多重复运算,比如你在计算f(5)的时候,你要计算f(4)和f(3),计算f(4)又要计算(3)和f(2),计算f(3),又要计算f(2)和f(1),看下面这个图! 2 + n _ {

五大常见算法策略之——动态规划策略(Dynamic Programming)

对f(3)和f(2)进行了重复运算,这还是因为5比较小,如T 2 ) t 3 S / $ F果要计算f(100),那你可能要等到天荒地老它还没执行完(手动滑r z B % x % # i稽),感兴趣的朋友可以试试,反正h I 5 P d =我已经试过了。

public static int fibonacci(int n)z q Y{//递归解9 ! ^ q A y R d法
if(n == 1) return 1;
else if(n == 2) retm = z C s A R 6 Zurn 1;
else return fibonacci(n - 1) + fibonacci(n - 2);
}

上面就是递归的解法,代码看着很简单易懂,但是算法复杂度已经达到了O(2^n),指数级别的复杂度,再加上如果n较大会造成更大的栈内存开销,所以非常低效。

我们再来说说DP策略解决这个问题

我们知道导致这个算^ H |法低效的根本原因就是递归栈内存开销大,对越小的数要重复计算的次数越7 [ D D R K # : ~多,那既然我们已经将较小的数,诸如f(2),f(3)...这些计算过了,为什么还要重复计算呢,这时就用到了DP策略,将计算过的f(n)保存起来。我们看看代码:

/**
* 对斐波那契数列求法的优化:如果单纯使用递归,那重复计算的次数就太多了,为此,我们对其做一些优化
* 假设最多计算到第1O 6 ? L 800个斐波那契数
* 用ar@ V o - 7 S z + Pr这个数组来保存已经计算过的Fibonacci数,以确保不会重复计算某些数
*/
private static int arr = new in7 R X f 2t;
public static int 5 O A G 4 Ft Fibonacci(int n){
if(n <= 2){
return 1;
}els{ m O V 4 A w : Ge{
if(arr != 0) //判断是否计算过这个f(n)
return arr;
else{
arr = Fibonacc* r = 2 #i(n-1)+Fibonacci(3 , H U z * :n-2);
return arr;
}
}
}

arr数组初始化为0,arr就表示fD j n a c b h s(i),每次先判断arr是否为0,如果为0则表示未计算过,则递归计算,如果不为0,则表示已经计算过,那就直接返回。

这样的好处避免了很大程度上重复的计算,但是对栈内存的开销虽然有减小但还不是很显著,因为只要有递归,栈内存就免不了有较大开销。m o m W s y W c所以针对Fibonacci数列A P f我们还有一个递推的方式来计算,其实这也符合DP, J s C ! = m A策略的思想,都是将计算过的值保存起@ g T G S来。

//甚至可以使用递推来求解Fibonacci数U n @列
public stax 4 W I P , ztic int fibonacci(int n){
if(n &l& d @ ) l M + yt;= 2) return 1;
int f1 = 1, f2 = 1` 0 O C a g ] z, sum = 0;
for(int i = 3; i <2 & v= n; i++){
sum = f1+f2;
f1 = f2;p r f R _ *
f2 = sX 1 A i y 6 0 eum;
}
return sum;
}

求j , V O解路径个数

一个机b V i K j器人每次只能向右或者下走一步,问它试图到达右下角“Finish”,共有多少条+ F X不同的路径?(7*3的网格)

五大常见算法策略之——动态规划策略(Dynamic Programming)

DP策略类的题最重) N : w a S / y要是要找状态转移方程,恰恰也是最难的一步。

五大常见算法策略之——动态规划策略(Dynamic Programming)

  • 1、我们通过这个图可以看出其实要到达(i,j)也就两种情况V 1 X,一种是从(i,j-1)向右走一步到达,另一种是从(i-1,j)向下走一步到达,所以将这两种情况的K 0 ] i E J K路径数相加就是到(i,j)的所有J b N Q 4 u `路径数。由此列出状态转移方程:f(i,j)=f(i-1,j)+f(i,j-1)
  • 2、根据DP的思路,将已经计~ i h ) ! t ^ m c算过的存储起来并用于后面复用其结果,这里我们a R V | l S 1 V考虑用二维数组来存储f(i,j)。
  • 3、问题规模从小到大计算,大规模的问题复用小规模问题的解进行计算。代码实现
/**
* 此题是求解路径个数,让你从(1,1)走到某个特定的位置,求一共有多少种走法
* @param i
* @param j
* @return
*/
public static int Count_l 8 H UPath(int ie ) , int j){
int result = new_ R ? ] R ? b 8 int;
for (int k = 0; k < i; k++) { //将二维数组初始化为1
Arrays.B c z 3 r d B Sfill(result,1);
}
for (iny w W / : } : h `t k = 1; k < i; k++) {
for (int l = 1; l < j; l++){
result = result+result;
}
}
return result[j;
}

小青蛙跳台阶再讨论

又是那只熟悉的青蛙,和上节递归与分治中相同的a j l a u g = ! Q例题,一只青蛙一次可以跳上1级台阶,x # H % j也可以跳上2级,求该青蛙跳上一个n级的台阶共有多少种跳法。详细思路可以看看上一篇文章——递归与分治策略。

我们下面先回顾一下上, m g D C次用E d a s o的递归算法:

pu0 5 8 ; zblic static int Jump_Floor1(int n){
if(n <= 2){
return n;
}else{ //这里涉及到两种跳法,1、第一次跳1级就还有n-1级要跳,2、第一次跳2级就还有n-2级要7 j O ( &跳
r- W { : m j k ; *eturn Jump_Floor1(n-1)+Jump_Floor1(n-. # M 5 6 8 = A 32);
}
}

其b j 6 - 3 d J实和第一个例子斐波那契一样,之所以把它又拉出来讨论,是因为它的递归解法中涉及的重复计算实在太多了,我们需要将已经计7 ; i s O算过的数据保E S l ) , 7 + .存起来,以避免重复计算,提高效率。这里大家可以先自己试着改一下其实和第一个例子的G r ( O g ? { a =改进方法是一样t [ G O p 的,用一个数组来缓存计算过的数据。

/**
* 看完递归的方法不要先被它的代码简洁所迷惑,可以分析一下6 & J P # @ f复杂度,就会发现有很多重复的计算- S R q } g K m
* 而且看完这个会发现和Fibonacci的递归方法有点像
* @非递归
*/
private statG [ 4ij W M z ! 8c int result = new int;
public stat] y 2 ric int Jump_Floor2(int n){
if(n <= 2){
return n;
}else{
if(result != 0)
return result;
else{
result = Jump_Floor2(n-1)+Jump_Floor2(n-2);
return result;) # D I )
}
}
}

下面将难度做一提升,W 6 ! ) 2 # (我们来讨论一道DP策略里的经典例题——最长公共子列问题

最长公共子序列问题

给定两个序列,G x A l需要求出两个序列最长的公共子序列,这里的子序列不同于字串,字串要求必须是连续的一个串,子序列并没有这么严格的连续要求,我们举个例子:

比如A = \"LetMeDownSlowly!\" B=\"LetYouDownQuickly!\" A和B的最长公共子序列就是6 G = U c Y R\"LetDow& b Fnly!7 / z Z\"

比H 1 $ *如字符串1:BDCABA;字符串2:ABCBDAB,则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA

我们设 X=(x1,x2,.....xn) 和 Y={y1,y2,.....ym} 是两个序列,将 X 和 Y 的最长公共子序列记为LCS(X,Y),要找它们的最长公共子序列就是要求最优化问题,有以下几种情况:

  • 1、n = 0 || m = 0,不E , 9 I ) X 5 P用多说最长的也只能是0,& A X = iLCS(n,m) = 0
  • 2_ f u l S K t `、X(n) = Y(m),说明当前序列也是相等的,那就给这两( g S u b %个元素匹配之前的最长长度加一,即LCS(n,m)=LCS(n-1,m-1)+1
  • 3、X(n) != Y(m),这时候说明这两个元素并没有匹配上,4 s C . X Y - ] 0那所以最长的公共子序列长度还是这两个元素匹配之前的最长长度,即max{LCS(n-1,m),LCS(n,m-1)}
    由此我们可以列出状态转移方程:(用的别人的图)
五大常见算法策略之——动态规划策略(Dynamic Programming)

我们可以考虑用一个二维数组来保存LCS(n,m)的值,n表示行,m表示列,O ` 1作如下演示,比如字符串1:ABCBDAB,字符串2:BDCABA;

1、先对其进行初始化操作,即将m=0,或者n=0的行和列的值全填为0

五大常见算法策略之——动态规划策略(Dynamic Programming)

2、判断发现h K W S DA != B,则LCS(Y f W Q ) m 1 71,1) = 0,填入其中

五大常见算法策略之——动态规划策略(Dynamic Programming)

3、判断B == B,则LCS(1,2) = LCSV 1 l 4 . _ _(0,1)+1=1,填入其中

五大常见算法策略之——动态规划策略(Dynamic Programming)

4、判断B != C,则LCS(1,3)就应该等于LCS(0,3)和LCS(1,2)中较大的那一个,即等于1,通过观察我们发现现在的两个序列是{B}和{ABC}在比较,即使现在B{ 9 2 O + 两者相等,但是只能是LCS(n-1,m-1)+1,所以还是1,因为一个B只能匹配一次啊,举个t B 5 # p L Y :例子:就好像是DB和ABCB来比较,当第一个序列的B和第二个C K u P a W 5序列的第二个B匹配时,就应该是D和ABC的最长子序列+1,所以如下填表:

五大常见算法策略之——动态规划策略(Dynamic Programming)

6、掌握规律后,我们直接完整填完这个表

五大常见算法策略之——动态规划策略(Dynamic Programming)

代码实现:

/**
* 求最长公共v a V { v / u -子序列问题
* Talk is cheap, show me the code!
* 参考公式(也是最难的一步):
* { 0 i = 0, j = 0
* c = { c+1 i,j>0, x == y
* { max{c,c} i,j>0, x != y
* 参考书目:算法设计与分析(第四版)清华大学出版社 王晓东f h Y 1 编著
* 参考博客:https:. ^ e a y ; M//www.cnblogs.com/hapjint j h S B / g/p/5572q 6 )483.html
* 比如A = \"LetMeDowi G X jnSlowly!\" B=\"LetYouDownQuickly!\R y , ~ q C" A和B的最长公共子序列就是\"LetDownly!\"
* @param x
* @param y
* @Param c 用c表示:(x1,x2.x ! d l...xi) 和 (y1,y2...yj) 的**最长**公共子序列的长度
* @retuc 4 h 9 R ` Z & crn 最长公共子序列的长度
*/J u i ~ @ ( A ` [

//p o D =maybe private method
pN o N @ ! _ @ ^ ?rivate static int lcsLi R L 3 Uength(String x, String y, int9 _ m * c){
int m = x.length();
int n = y.length();
//下面是初始化操作,其实也没必要,因为Jah - m o p y F Nva中默认初始化y e F [ ( s & @为0,其他语言随机应变
for (in^ N / F B o } St i = 0; i <= m; i++) c = 0;
for (int i = 0; i <= n; i++) c = 0;

//用 9 T E W一个序列的每个元素去和另一! d L L $个序列分别比较
for (i] , y : F fnt i = 1; i <4 u F ) 1 A;= m; i++t @ e) {
for (int j = 1; j <= n; j++) {
if(x.chaX 9 Z u -rAt(i-1) == y.charAt(j-1)){ //如果V . - n W U u s遇到相等的,就给序5 & & f列的上一行上一列的加1
c = c+1;
}else if(c >= c){ //取上一次最大的,保证最长子序列的最长要求
c = c;
}else{
c = c;
}
}
}
return c;
}

0-1背包问题

也是很经典的一道算法题:0-1背包问题说的是,给定背包容量W,一系列物品{weiht,- d F wv7 ( o Nalue},每个物品只能取一件,计算可以获得的valu7 * R J ? & ( x [e的最大值。

最优解问题,当然是我们DP,最难的一步还7 F u x是状态转移方程,我们先把方程v U 8 ~给出来,再对其进行讨论.

m = max{ m[j-w]+v , m}

m表示1,2,...,i个物品,背包容量为j时候的最大valueB # r,w表示第i个物品的重量,v表示第i个物品的value# ( U c r f - 3 Z

我们用一个二维数组来存储这个m,i表示1,2,...,i个物品,j表示背包容量

对于每一个物品来说,要计算当前最大的value,A Q n A y + 7 g分为两种情况:1、将这个物品放进去,不将这个物品放进去

  • 1、我们先考虑不将其放进去, 很好理解,m就是不将第i个物品放入背包的最大value,不放就是1,2,...,i-1个物品,背包容量为j
  • 2、再考虑放进去的情况,既然要将其放进去,那就在背包中给其预先留好位置,mA 4 m 5 : 9 + C[j-w]G | {表示在背包中先给第i个物品把地方腾出来,然后背包可用空间就是j-w,在这些可用空间里1,2,K k + W C Y P...,i-1个物品放的最大value就是m[j-w],将其放进去只需要再给加上v即可,即m[j-w]+v。W 7 S j y ( R所以状态转移方程就是取放进去和不放进去两种情况的最大值m = max{ mJ | a b A L ^[i { r r [j-w[i# F j V ]+v , m}

代码实现

/**
* 此函数用于计算背包中能存放的最大valp K P L j i ( ]ues
* @par. x _ay [ * 0 b =m m mH # $ S T [ b , h用于记录1,2,...,i个物品在背包容量为j时候的最大value
* @pQ ? 7 Param w w数组存放了每个物品的重量weight,w表示第i+1个物品的weight
* @param v v数组存( ^ ) F D放了每个物品的价值valF b Iue,v表示第i+1个物品的value
* @w G ? j e 4param C C表示背包最大容量
* @param sum sum表示物品个数
* 状态转移方程: m = max{ m[j-w]+v , m}
* m很好理解,就是不将第i个物品X K A 9 c a = 0 l放入背包的最大value
* m[j-w]+v表示将第i个物品放? ( =入背包,m[j-w]表示在背包中先给第i个物品把地方腾出来
* 然} q x 5 E : N | s后背包可用空间就是j-w,在这些可用空间里$ r t W , d 5 A u1,2,...,i-1个物品放的最大value就是m[j-w],那
* 最后再加上第i个物品的value,就是将第i个物品放入背包的最大value了
*/
public static void knap(int m, int w,int v, int C, i. X _ Bnt sum){
for(int j = 0; j < C; j++){ //初始化 stuttering
if(j+1 >= w){ //第一行只有一个物品,如果物品比背_ - B h i P H包容量大就放进去,否则最大value只能为0
m = v;
}N w celse{
m = 0;
}
}
for(int i = 1; i < sum; i++){
for(int j = 0; j < C; j++){
int a = 0, b6 D % J p _ ; = 0; //a表示将第iA ) p H M @ ~ ^个物品放入背包的value,b表示不放第i个物品
if(j >= w] a e j g)
a =M w p T _ D O 5 m[j-w]+v;
b = m;
m = (a>b?a:b);
}| ) e q 8 ( 1
}
}

大学确保文章试试指数经典告诉设计本地新闻