动态规划(Dynamic Programming)——算法三十六计之二

Posted 信息安全club

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划(Dynamic Programming)——算法三十六计之二相关的知识,希望对你有一定的参考价值。

 早时不算计,过后一场空。——杨文奎《儿女团圆》


首先要感谢杨老师制作的主题图,以后《算法三十六计》每期都是这个高大上的图啦

动态规划(Dynamic Programming)——算法三十六计之二


01

什么是动态规划

          以下是百度百科的说法——

        动态规划(dynamic programming)是运筹学的一个分支,通常是用来解决最优化问题的。最初是由数学家在研究多阶段决策过程的优化问题时,提出的优化原理,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决优化问题的方法。动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。

       ——不能说一点用都没有……好吧,就是对理解动态规划一点用都没有动态规划(Dynamic Programming)——算法三十六计之二 还是看Glen的解释吧:

        动态规划类似于分治(递归),但是和一般的递归有一个最大的不同在于其递推公式中有min或者max算符。比如一般的递归的递推公式看起来是这样的,

(斐波那契数列递推公式)

而动态规划的递推公式看起来是这样的,


 (最长公共子串递推公式)

C[i,j]=0C[i1,j1]+1MAX(C[i,j1],C[i1,j])i=0j=0i,j>0xi=yji,j>0xiyj


       值得注意的是dynamic programming 里的programming和程序设计没有任何关系,而是指表格查询法(tabular method),即将每一步计算的结果存储在表格里,供随后的计算使用。看后面我写的代码就好理解了。

02


动态规划的适用条件

        任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。


1.最优子结构性质 (optimal sub-structure)

  最优子结构性质,通俗的说法就是问题的最优解包含其子问题的最优解。任何问题,如果不具备该性质,就不能用动态规划来解决。考察如下示意图:

 

        如果节点A到节点E的最长路径是 ( A > B > C > D > E ) ;那么,节点C到节点E的最长路径必定在此路径上。

2.子问题重叠性质 (subproblems overlap)

Dynamic programming solves each subsubproblem just once and then saves its answer in a table, thereby avoiding the work of recomputing.

  子问题重叠性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。

03


示例

        汉诺塔(hannoi tower)大家都很熟悉。它是典型的分治(递归)的例子。但那是三个柱子的情况,如果是四个柱子,那就是动态规划的问题。怎么考虑这个问题呢?在多一个柱子作为缓冲区的情况下,我们要考虑先移动一部分碟子到缓冲区,然后用另外三个柱子把剩下较大的碟子都转移好,然后再把缓冲区上的碟子放置到目标柱子。要注意其中的每一步都是一个递归的过程。动态规划的思想主要体现在确定移动多少碟子到缓冲区的这个最优化的选择中。递推公式为

f(1)=1

f(n)=min<k=2..n-1>(2f(k)+2^(n-k)-1)

注意,在三个柱子的情况下计算机不可能在你我有生之年完成64个碟子移动的模拟,但在四个柱子的情况下这是很轻而易举的事情。

package org.g5g.algorithm.dynamicprogramming;

import java.util.Arrays;

import java.util.LinkedList;

import java.util.List;


@SuppressWarnings("unchecked")

public class HannoiTower {

public static final int DISK_NUMBER = 64;

public static int[] f = new int[DISK_NUMBER + 1];

public static int[] k = new int[DISK_NUMBER + 1];


static {

f[1] = 1; // f(n)=min<k=2..n-1>(2f(k)+2^(n-k)-1)

k[1] = 0;

for (int idx = 2; idx <= DISK_NUMBER; idx++) {

int k = HannoiTower.k[idx - 1];

long temp = 2 * f[k] + (1L << (idx - k));

for (int i = k + 1; i < idx - 1; i++) {

long newValue = 2 * f[i] + (1L << (idx - i));

if (temp > newValue) {

temp = newValue;

k = i;

}

}

HannoiTower.k[idx] = k;

f[idx] = 2 * f[k] + (1 << (idx - k)) - 1;

}

}


public static List<?>[] poles = new List<?>[] {

new LinkedList<Integer>(), new LinkedList<Integer>(),

new LinkedList<Integer>(), new LinkedList<Integer>() };


static {

for (int i = DISK_NUMBER; i > 0; i--) {

((List<Integer>) poles[0]).add(i);

}

}


public static void doMove(char source, char target) {

if (poles[source - 'A'].size() > 0) {

Integer tmpDisk = (Integer) poles[source - 'A'].get(poles[source - 'A'].size() - 1);

poles[source - 'A'].remove(poles[source - 'A'].size() - 1);

((List<Integer>) poles[target - 'A']).add(tmpDisk);

for (int i = 0; i < 4; i++) {

System.out.println((char) ('A' + i) + ":" + Arrays.toString(poles[i].toArray()));

}

System.out.println("--------------------------");

}

}


public static void resolve4Poles(int diskNumber, char source, char temp1, char temp2, char target) {

if (diskNumber <= 1) {

System.out.println(source + "->" + target);

doMove(source, target);

} else {

int k = HannoiTower.k[diskNumber];

resolve4Poles(k, source, target, temp2, temp1);

resolve3Poles(diskNumber - k, source, temp2, target);

resolve4Poles(k, temp1, source, temp2, target);

}

}


public static void resolve3Poles(int diskNumber, char source, char temp, char target) {

if (diskNumber == 1) {

System.out.println(source + "->" + target);

doMove(source, target);

} else {

resolve3Poles(diskNumber - 1, source, target, temp);

System.out.println(source + "->" + target);

doMove(source, target);

resolve3Poles(diskNumber - 1, temp, source, target);

}

}


public static void main(String[] args) {

resolve4Poles(DISK_NUMBER, 'A', 'B', 'C', 'D');

}

}

04


练习


        纸上得来终觉浅,绝知此事要coding。作为思考题,可以试一下用动态规划求解最长公共子串的问题,递推公式在上文已经给出。

        


以上是关于动态规划(Dynamic Programming)——算法三十六计之二的主要内容,如果未能解决你的问题,请参考以下文章

动态规划-Dynamic Programming(DP)

动态规划(Dynamic Programming)

动态规划(dynamic programming)

动态规划(Dynamic Programming)LeetCode经典题目

算法应用公式动态规划 Dynamic Programming

动态规划算法(Dynamic Programming,简称 DP)