提示:这几天对动态规划的问题有很多疑惑,复习了本科动态规划,和看了网上一些资料,总结了对动态规划的理解
一、动态规划的定义和思想:
1,动态规划(Dynamic programming)是一种算法设计技术,是一种解决多段决策过程最优化的通用方法。
多段决策过程最优化:
现实世界有许多问题属于这种情况,它有很多解,应用要求最优解。
穷举法先找出全部解,再选出最优解,耗时较高。
因此,人们为了降低求解问题的难度,把求解过程分为一系列阶段,各个阶段依次按照最优性原则计算,最后再组合各阶段计算得到最优解。
理解:所以不能段化的问题不能用动态规划法求解。
2,最优性原理:最优性原理(Principle of Optimality):多阶段决策过程的最优策略具有这样的性质:即无论过程过去的状态和决策如何,对前面的决策所形成的状态而言,其余决策必须构成最优策略。
3,动态规划思想:
(1).将待求解的问题分解成若干个子问题,并存储子问题的解而避免重复计算子问题,并由子问题的解得到原问题的解。
(2).动态规划算法通常用于求解具有某种最优性质的问题。
动态的含义:当前状态是动态得到的。当前状态与其直接前趋状态有关,对其直接前趋状态施加求解算法,成为当前状态。
4,动态规划算法的基本要素:
(1)、最优子结构性质:
问题的最优解包含着它的子问题的最优解。
即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。
(2)、重叠子问题:
在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些问题被计算多次。
如果把每个子问题的解保存起来,以后再遇到同样的问题时就可以直接引用,不必重新求解。
理解:动态规划就是获取各阶段的递推关系式,并保留中间过程以简化计算过程。状态转移方程,就是定义了问题和子问题之间的关系。可以看出,状态转移方程就是带有条件的递推式。
5,设计动态规划算法的步骤
划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。
选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。
确定决策并写出状态转移方程:状态转移方程就是根据上一阶段的状态和决策来导出本阶段的状态。
理解:动态规划的本质在于状态变化,以及状态关系。关键问题在于状态的抽象。
二、案例理解:
1,斐波纳契数列
状态转移方程:
F(n) =F(n-1) + F(n-2) if n> 1
递归:
if n=0 or n=1:
return n;
else:
return F(n-1) + F(n-2);
动态规划:
A[0]←0,A[1] ←1
for i←2 to n do:
A[i]←A[i-1]+A[i-2]
return A[n]
2,案例二:
有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
分析:动态规划的实现的关键在于能不能准确合理的用动态规划表来抽象出 实际问题。在这个问题上,我们让f(n)表示走上n级台阶的方法数。
那么当n为1时,f(n) = 1,n为2时,f(n) =2,就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为2。那么当我们要走上n级台阶,必然是从n-1级台阶迈一步或者是从n-2级台阶迈两步,所以到达n级台阶的方法数必然是到达n-1级台阶的方法数加上到达n-2级台阶的方法数之和。即f(n) = f(n-1)+f(n-2),我们用dp[n]来表示动态规划表,dp[i],i>0,i<=n,表示到达i级台阶的方法数。
/*dp是全局数组,大小为n,全部初始化为0,是题目中的动态规划表*/
int fun(int n){
if (n==1||n==2)
return n;
/*判断n-1的状态有没有被计算过*/
if (!dp[n-1])
dp[n-1] = fun(n-1);
if(!dp[n-2])
dp[n-2]=fun(n-2);
return dp[n-1]+dp[n-2];
}
3,案例三:
给定两个字符串str1,str2,在给定三个整数ic,dc,rc,分别代表插入,删除和替换一个字符的代价。返回将str1
编辑成str2的代价,比如,str1=”abc”,str2=”adc”,ic=5,dc=3,rc=2,从str1到str2,将’b’换成’d’代价最小,所以返回2.
分析:
在构建出动态规划表的时候,关键是搞清楚每个位置上数值的来源。首先我们生成dp[M+1][N+1]的动态规划表,M代表str1的长度,N代表str2的长度,那么dp[i][j]就是str1[0..i-1]变成str2[0…j-1]的最小代价,则dp[i][j]的来源分别来自以下四种情况:
a、首先将str1[i-1]删除,变成str1[0…i-2],然后将str1[0…i-2]变成str2[0…j-1],那么dp[i-1][j]就代表从str1[0..i-2]到str2[0…j-1]的最小代价,所以:dp[i][j] = dp[i-1][j]+dc;
b、同理也可以是从str1[0…i-1]变成str2[0…j-2],然后在插入str2[j-1],dp[i][j-1]就代表从str1[0…i-1]变成str2[0…j-2]的最小大家,所以:dp[i][j] = dp[i][j-1]+ic;
c、如果str[i-1] == str2[j-1],则只需要将str1[0…i-2]变成str2[0…j-2],此时dp[i][j] = dp[i-1][j-1];
d、如果str1[i-1]!=str2[j-1],则我们只需要将str1[i-1]替换成str2[j-1],此时dp[i][j] = dp[i-1][j-1]+rc;
在这四种情况当中,我们选取最小的一个,即为最小代价。
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
string str1 = "ab12cd3";
string str2 = "abcdf";
//cin>>str1;
//cin>>str2;
const int M = str1.length();
const int N = str2.length();
//vector<int> p(M+1,0);
//vector<vector<int>> dp(N+1,p);
int dp[10][10] = {};
int ic=5,dc=3,rc=2;
//int ic = 1,dc=1,rc=1;
dp[0][0] = 0;
for (int i = 1;i<N+1;i++)
dp[0][i] = ic*i;
for (int i = 1;i<M+1;i++)
dp[i][0] = dc*i;
for (int i=0;i<M;i++){
for (int j = 0;j<N;j++){
int x = min(dc+dp[i+1][j],dp[i][j+1]+ic);
if (str1[i]!=str2[j])
dp[i+1][j+1] = min(dp[i][j] + rc,x);
else
dp[i+1][j+1] = min(dp[i][j],x);
}
}
cout << dp[M][N] << endl;
}