动态规划

一、动态规划

动态规划(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. nutwk的maven中央仓库及配置

    官方maven服务器:https://jfrog.nutz.cn/artifactory/jcenter/ 如果用阿里的maven服务器,特别提醒:

  2. discuz 插件核心函数hookscript分析.

    function hookscript($script, $hscript, $type = 'funcs', $param = array(), $func = '', $scriptextra = ...

  3. cron - 定期执行指定命令的守护程序 (Vixie Cron)

    总览 cron 描述 Cron 应该由 /etc/rc 或者 /etc/rc.local 启动(译注:有很多发行版与此不同的,如 RedHat6.x 使用 /etc/rc.d/init.d/crond ...

  4. log4net小记

    log4net添加: Install-Package Log4net log4net.config配置: <?xml version="1.0" encoding=" ...

  5. NET SignaiR 实现消息的推送,并使用Push.js实现通知

    一.使用背景 1. SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  6. 负对数似然(negative log-likelihood)

    negative log likelihood文章目录negative log likelihood似然函数(likelihood function)OverviewDefinition离散型概率分布 ...

  7. React开发实时聊天招聘工具 -第四章 Redux

    复杂以后 setState 就不太方便了 所以使用Redux来管理 React只负责View. Store.State.Dispatch.Reducer reducer(state,action) { ...

  8. wepy.request 请求成功但是不进入success和fail方法,及请求传参问题

    1.根据wepy官方给的文档如下,用then拿后台返回的数据,如果用then报错,请先在app.wpy中配置promise. 没有success,fail,complete方法,如若用了也是不会进入方 ...

  9. PHP 数组使用之道

    本文首发于 PHP 数组使用之道,转载请注明出处. 这个教程我将通过一些实用的实例和最佳实践的方式列举出 PHP 中常用的数组函数.每个 PHP 工程师都应该掌握它们的使用方法,以及如何通过组合使用来 ...

  10. 我们参与投资36Kr股权众筹项目“易途8”的决策过程

     背景   中文接机.中文送机.中文包车. 当地玩乐   最大的竞争对手:皇包车,15年9月A轮   其它对手:唐人接等,订单量无法和 皇包车.易途8比.    看好理由 1.旅游行业和境外自由行,是 ...