动态规划

一、动态规划

动态规划(Dynamic Programming)是一种设计的技巧,是解决多阶段决策过程最优化问题的通用方法。

基本思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解(这部分与分治法相似)。与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。通常可以用一个来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划的基本思路。

采用动态规划求解的问题需要具有两个特性:

  • 最优子结构(Optimal Substructure):问题的一个最优解中所包含的子问题的解也是最优的。

  • 重叠子问题(Overlapping Subproblems):用递归算法对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。

问题具有最优子结构性质,我们才能写出最优解的递归方程;具有重叠子问题特性,我们才能通过避免重复计算来减少运行时间。

综上所述,动态规划的关键是 —— 记忆,空间换时间,不重复求解,从较小问题解逐步决策,构造较大问题的解。

二、最长公共子序列(LCS)问题

下面通过一个具体的例子来学习动态规划方法 —— 最长公共子序列问题。

最长公共子串(Longest Common Substring)最长公共子序列(Longest Common Subsequence)的区别: 子串要求在原字符串中是连续的,而子序列则只需保持相对顺序,并不要求连续。

问题描述:给定两个序列:X[1...m]Y[1...n],求在两个序列中同时出现的最长子序列的长度。

假设 X 和 Y 的序列如下:

X[1...m] = {A, B, C, B, D, A, B}
Y[1...n] = {B, D, C, A, B, A}

可以看出,X 和 Y 的最长公共子序列有 “BDAB”、“BCAB”、“BCBA”,即长度为4。

1) 穷举法

可能很多人会想到用穷举法来解决这个问题,即求出 X 中所有子序列,看 Y 中是否存在该子序列。

  • X 有多少子序列 —— 2m 个
  • 检查一个子序列是否在 Y 中 —— θ(n)

所以穷举法在最坏情况下的时间复杂度是 θ(n * 2m),也就是说花费的时间是指数级的,这简直太慢了。

2) 动态规划

首先,我们来看看 LCS 问题是否具有动态规划问题的两个特性。

① 最优子结构

设 C[i,j] = |LCS(x[1...i],y[1...j])|,即C[i,j]表示序列X[1...i]Y[1...j]的最长公共子序列的长度,则 C[m,n]
= |LCS(x,y)|
就是问题的解。

递归推导式:

在这里就不证明了。从这个递归公式可以看出,问题具有最优子结构性质!

② 重叠子问题

根据上面的递归推导式,可以写出求LCS长度的递归伪代码:

LCS(x,y,i,j)
if x[i] = y[j]
then C[i,j] ← LCS(x,y,i-1,j-1)+1
else C[i,j] ← max{LCS(x,y,i-1,j),LCS(x,y,i,j-1)}
return C[i,j]

C++代码如下:

// 简单的递归求解LCS问题
#include <iostream>
#include <string>
using namespace std; int max(int a, int b)
{
return (a>b)? a:b;
} // Return the length of LCS for X[0...m-1] and Y[0...n-1]
int lcs(string &X, string &Y, int m, int n)
{
if (m == 0 || n == 0)
return 0;
if (X[m-1] == Y[n-1])
return lcs(X, Y, m-1, n-1) + 1;
else
return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));
} int main()
{
string X = "ABCBDAB";
string Y = "BDCABA"; cout << "The length of LCS is " << lcs(X, Y, X.length(), Y.length());
cout << endl; getchar();
return 0;
}

像这样使用简单的递归,在最坏情况下(X 和 Y 的所有字符都不匹配,即LCS的长度为0)的时间复杂度为 θ(2n)。这和穷举法一样还是指数级的,太慢了。

根据程序中 X 和 Y 的初始值,我们画出部分递归树:

递归树中红框标记的部分被调用了两次。如果画出完整的递归树,我们会看到很多重复的调用,所以这个问题具有重叠子问题的特性。

③ 动态规划求解

简单的递归之所以和穷举法一样慢,因为在递归过程中进行了大量的重复调用。而动态规划就是要解决这个问题,通过用一个表来保存子问题的结果,避免重复的计算,以空间换时间。前面我们已经证明,最长公共子序列问题具有动态规划所要求的两个特性,所以 LCS 问题可以用动态规划来求解。

下面是用动态规划(打表)解决LCS问题:

C++代码:

// 动态规划求解LCS问题
#include <iostream>
#include <string>
#include <vector>
using namespace std; int max(int a, int b)
{
return (a>b)? a:b;
} /**
* 返回X[0...m-1]和Y[0...n-1]的LCS的长度
*/
int lcs(string &X, string &Y, int m, int n)
{
// 动态规划表,大小(m+1)*(n+1)
vector<vector<int>> table(m+1,vector<int>(n+1)); for(int i=0; i<m+1; ++i)
{
for(int j=0; j<n+1; ++j)
{
// 第一行和第一列置0
if (i == 0 || j == 0)
table[i][j] = 0; else if(X[i-1] == Y[j-1])
table[i][j] = table[i-1][j-1] + 1; else
table[i][j] = max(table[i-1][j], table[i][j-1]);
}
} return table[m][n];
} int main()
{
string X = "ABCBDAB";
string Y = "BDCABA"; cout << "The length of LCS is " << lcs(X, Y, X.length(), Y.length());
cout << endl; getchar();
return 0;
}

容易看出,动态规划解决LCS问题的时间复杂度为 θ(mn),这比简单的递归实现要快多了。空间复杂度是θ(mn),因为使用了一个动态规划表。当然,空间复杂度还可以进行优化,即根据递推式我们可以只保存填下一个位置所用到的几个位置就行了。(关于如何输出LCS请看另一篇:《输出所有的最长公共子序列》)

总结:动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余(重复计算),这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

从上面的例子中,我们可以总结动态规划解决最优化问题的一般步骤:

  1. 分析最优解的性质,并刻划其结构特征。

  2. 递归地定义最优值。

  3. 以自底向上的方式或自顶向下的记忆化方法计算出最优值。

  4. 根据计算最优值时得到的信息,构造一个最优解。

步骤(1)—(3)是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤(4)可以省略,若需要求出问题的一个最优解,则必须执行步骤(4)。此时,在步骤(3)中计算最优值时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速地构造出一个最优解。

(全文完)

参考:

[1] http://www.algorithmist.com/index.php/Longest_Common_Subsequence

[2] http://www.geeksforgeeks.org/dynamic-programming-set-4-longest-common-subsequence/

【动态规划】Dynamic Programming的更多相关文章

  1. 动态规划Dynamic Programming

    动态规划Dynamic Programming code教你做人:DP其实不算是一种算法,而是一种思想/思路,分阶段决策的思路 理解动态规划: 递归与动态规划的联系与区别 -> 记忆化搜索 -& ...

  2. 6专题总结-动态规划dynamic programming

    专题6--动态规划 1.动态规划基础知识 什么情况下可能是动态规划?满足下面三个条件之一:1. Maximum/Minimum -- 最大最小,最长,最短:写程序一般有max/min.2. Yes/N ...

  3. 动态规划(Dynamic Programming)算法与LC实例的理解

    动态规划(Dynamic Programming)算法与LC实例的理解 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Le ...

  4. 动态规划 Dynamic Programming

    March 26, 2013 作者:Hawstein 出处:http://hawstein.com/posts/dp-novice-to-advanced.html 声明:本文采用以下协议进行授权: ...

  5. [算法]动态规划(Dynamic programming)

    转载请注明原创:http://www.cnblogs.com/StartoverX/p/4603173.html Dynamic Programming的Programming指的不是程序而是一种表格 ...

  6. 最优化问题 Optimization Problems & 动态规划 Dynamic Programming

    2018-01-12 22:50:06 一.优化问题 优化问题用数学的角度来分析就是去求一个函数或者说方程的极大值或者极小值,通常这种优化问题是有约束条件的,所以也被称为约束优化问题. 约束优化问题( ...

  7. 动态规划系列(零)—— 动态规划(Dynamic Programming)总结

    动态规划三要素:重叠⼦问题.最优⼦结构.状态转移⽅程. 动态规划的三个需要明确的点就是「状态」「选择」和「base case」,对应着回溯算法中走过的「路径」,当前的「选择列表」和「结束条件」. 某种 ...

  8. 动态规划 Dynamic Programming 学习笔记

    文章以 CC-BY-SA 方式共享,此说明高于本站内其他说明. 本文尚未完工,但内容足够丰富,故提前发布. 内容包含大量 \(\LaTeX\) 公式,渲染可能需要一些时间,请耐心等待渲染(约 5s). ...

  9. Python算法之动态规划(Dynamic Programming)解析:二维矩阵中的醉汉(魔改版leetcode出界的路径数)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_168 现在很多互联网企业学聪明了,知道应聘者有目的性的刷Leetcode原题,用来应付算法题面试,所以开始对这些题进行" ...

  10. 后台开发 3个题目 array_chunk, 100块钱找零钱(动态规划 dynamic programming), 双向循环链表 llist 删除节点

    1. array_chunk 实现 http://php.net/manual/en/function.array-chunk.php <?php function my_array_chunk ...

随机推荐

  1. codeforces_B. Forgery

    http://codeforces.com/contest/1059/problem/B 题意: For simplicity, the signature is represented as an  ...

  2. CAD绘制固定圆形标注(网页版)

    js中实现代码说明: function DoFixCircleComment() { var ent = mxOcx.DrawCustomEntity("TestMxCustomEntity ...

  3. Java IO(四--字符流基本使用

    在上一节,介绍了字节流的基本使用,本节介绍一下字符流的使用 Reader: public abstract class Reader implements Readable, Closeable { ...

  4. vue列表排序实现中的this问题

    最近在看vue框架的知识,然后其中有个例子中的this的写法让我很疑惑 <!DOCTYPE html> <html> <head> <meta charset ...

  5. Python --- 二叉树的层序建立与三种遍历

    二叉树(Binary Tree)时数据结构中一个非常重要的结构,其具有....(此处省略好多字)....等的优良特点. 之前在刷LeetCode的时候把有关树的题目全部跳过了,(ORZ:我这种连数据结 ...

  6. centos7搭建安装sentry

    Sentry 是一款基于 Django实现的错误日志收集和聚合的平台,它是 Python 实现的,但是其日志监控功能却不局限于python,对诸如 Node.js, php,ruby, C#,java ...

  7. 56.fielddata filter的细粒度内存加载控制

    语法: POST /test_index/_mapping/test_type { "properties": { "test_field": { " ...

  8. HTML5地理定位-Geolocation API

    HTML5提供了一组Geolocation API,来自navigator定位对象的子对象,获取用户的地理位置信息Geolocation API使用方法:1.判断是否支持 navigator.geol ...

  9. 后端传前端数据乱码(返回json字符串到前端)

    中文乱码的问题,在开发过程中难免会遇到,而在配置好编码之后,不管是数据库,还是其他地方都配置好统一UTF-8编码之后,后端从数据库取出数据传回前端,还会乱码,这里以ssm框架为例,因为是我自己遇到的, ...

  10. MSP430F5529时钟系统深究

    1.为什么要进行时钟管理? 时钟系统是一个数字器件的命脉,对于普通的51单片机来说,它的时钟来源只有外部晶振,然后每12个振荡周期完成一个基本操作,所以也叫做12T单片机,但对于当前高级一点的单片机来 ...