对动态规划算法的简单理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Wengwuhua/article/details/51431019

动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。

首先,我们要找到某个状态的最优解,然后在它的帮助下,找到下一个状态的最优解,“状态"用来描述该问题的子问题的解。“状态”并不是随便给的,大部分情况下,某个状态只与它前面出现的状态有关,而独立于后面的状态。然后分析出状态转移方程以及初始状态。

动态规划经典的问题就是求最长递增序列长度LIS:longest increasing subsequence

例如: 一个序列有n个数:a[1],a[2],…,a[N],求出最长非降子序列的长度。思路如下:

1、要求n个数的序列中最长非降子序列的长度,如果能先求出a[1]、a[2]、a[i](i<N)的最长非降子序列,那么上面的问题变成了原问题的一个子问题(问题规模变小了,你可以让i=1,2,3等来分析) 然后我们定义d(i),表示前i个数中以a[i]结尾的最长非降子序列的长度。这个d(i)就是我们要找的状态。如果我们把d(1)到d(N)都计算出来,那么最终我们要找的答案就是这里面最大的那个。状态找到了,下一步找出状态转移方程。

2、为了方便理解我们是如何找到状态转移方程的,我先把下面的例子提到前面来讲。如果我们要求的这n个数的序列是:

6,3,4,9,6,8

根据上面找到的状态,我们可以得到:(下文的最长非降子序列都用LIS表示)

  • 前1个数的LIS长度d(1)=1(序列:6)
  • 前2个数的LIS长度d(2)=1(序列:3;3前面没有比3小的)
  • 前3个数的LIS长度d(3)=2(序列:3,4;4前面有个比它小的3,所以d(3)=d(2)+1)
  • 前4个数的LIS长度d(4)=3(序列:3,4,9;9前面比它小的有3个数,所以 d(4)=max{d(1),d(2),d(3)}+1=3)

到这里我们基本可以找到状态转移方程了,如果我们已经求出了d(1)到d(i-1),那么d(i)可以用下面的状态转移方程得到:

d(i) = max{1, d(j)+1},其中j<i,a[j]<=a[i],则可写出如下程序:

private void Longest(int[] a, int n) {
int d[]=new int[n];
int len=1;
d[0]=1;
for(int i=1;i<n;i++){
d[i]=1;
for(int j=0;j<i;j++){
if(a[i]>=a[j]&&d[j]+1>d[i]){
d[i]=d[j]+1;
}
if(len<d[i])len=d[i];
}
}
System.out.println(len);
}

还有另一个简单的例子,同样根据动态规划可简单求解

/*
* 有3个硬币面值分别为1,3,5,现要一最小个数地组合凑齐目标面值
* */

private static void Min(int[] v, int n){ //v[]为硬币面值数组
int[] min=new int[n+1];  //目标数值为n的最小硬币个数组合数组
min[0]=0;
for(int k=1;k<=n;k++){
min[k]=n;      //先赋值为最大
}
for(int i=1;i<=n;i++){   //目标数求1到n的最小硬币数
for(int j=0;j<v.length;j++){
if(v[j]<=i&&min[i-v[j]]+1<min[i])   //目标值要大于等于硬币面值
min[i]=min[i-v[j]]+1;       //状态转移函数
}
}
System.out.println(min[n]);
}

难点主要在于确定状态以及找到状态转移方程~一般通过对问题的分解找到子问题并求解,找到“规律”,还是要通过练习慢慢加深理解的。

猜你喜欢

转载自blog.csdn.net/Wengwuhua/article/details/51431019