1. 动态规划(DP)算法的基本原理
动态规划过程:
- 每次决策依赖于当前状态,随即又引起状态转移。
- 一个决策序列就是在变化的状态中产生的,这种多阶段最优化决策解决问题的过程就称为动态规划。
DP求解问题所具有的性质:
[1] 最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构。
[2] 无后效性:与贪心算法一致。不过贪心算法不需要保留上一个问题的最优解,DP需要保留
[3] 子问题重叠:区别于分治法。即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。
最优子结构性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
DP的基本步骤:
动态规划的设计都有着一定的模式,一般要经历以下几个步骤[1] 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
[2] 每个阶段的状态:各个阶段用不同的状态表示出来。
[3] 确定决策,写出状态转移方程(核心):根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
[4] 确定终止条件/边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
DP三要素:
- [1] 问题阶段
- [2] 每个阶段的状态
- [3] 递推关系:从前一个阶段转化到后一个阶段之间的递推关系
2. 动态规划(DP)基本实例
动态规划的本质
- 动态规划是用空间换时间的一种方法的抽象。其关键是发现子问题和记录其结果。然后利用这些结果减轻运算量。因为背包的最终最大容量未知,所以,我们得从1到Capacity一个个试。
DP的典型应用包括:
- 斐波纳契数列
- 0-1背包问题等
- 最大不降子序列/子序列和
- 青蛙跳台阶
- 等
1. 最大不降子序列和
[1] 题目描述
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。
对于给定的一个序列(a1, a2, …,aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8),(1,3,5,9)等。这些子序列中序列和最大为18,即子序列(1, 3, 5, 9)的和。
你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100, 1, 2, 3)的最大上升子序列和为100,而最长上升子序列为(1, 2, 3)。
[2] 输入输出示例
样例输入:
7
1 7 3 5 9 4 8
样例输出:
18
[3] 代码(C)
#include <stdio.h>
int array[1000];
int dp[1000];
int N;
int MaxNoDropSeq()
{
int max = 0;
int i, j;
for (i = 0; i < N; i++)
{
dp[i] = array[i]; //dp[i]表示,第i个元素之前的非降序和
for (j = 0; j < i; j++)
{
if (array[j] < array[i] && dp[i] < dp[j] + array[i]) //第2句保证dp[i]是最大的非降序和
{
dp[i] = dp[j] + array[i]; //递推关系
if (dp[i] > max)
max = dp[i];
}
}
}
return max;
}
int main()
{
scanf("%d", &N);
int i;
for (i = 0; i < N; i++)
{
scanf("%d", &array[i]);
}
printf("%d\n", MaxNoDropSeq());
return 0;
}
- 2. 跳台阶
[1] 题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
[2] 输入输出示例
样例输入:
5
样例输出:
8
[3] 代码(C)
#include <stdio.h>
int main()
{
int n;
while (scanf("%d", &n))
{
int array[n + 1];
array[1] = 1; //1级台阶1种跳法
array[2] = 2; //2级台阶2种跳法
int i;
for (i = 3; i < n + 1; i++)
{
array[i] = array[i - 1] + array[i - 2]; //递推关系
}
printf("%d\n", array[n]);
}
return 0;
}
- 3. 0-1背包问题
[1] 问题描述
一个背包有滴定的承重C,有N件物品,每件物品都有自己的价值,记录在数组V中,也都有自己的重量,记录在数组W中。每件物品只能选择要装入还是不装入背包,要求在不超过背包承重的前提下,选出的物品总价值最大。
在使用动态规划算法求解0-1背包问题时,可选物品为i、i+1、……、n时0-1背包问题的最优值。
价值数组v = {8, 10, 6, 3, 7, 2}
重量数组w = {4, 6, 2, 2, 5, 1}
[2] 问题分析
用dp[i][j]表示前i个物品在总容量不超过j的情况下,放到背包里的最大价值
状态转移方程/递推关系如下
dp[0][j] = 0
dp[i][j] = max{dp[i-1][j], dp[i-1][j-v[i]] + w[i]}; {i,j | 0< i <=n, 0<= j <=capacity})
当决定是否放入i时,我们需要考虑两种子问题
- 【1】 如果放入,那我们考虑在物品数为i-1, 背包容量为j-weight[i]时的价值
- 【2】 如果不放入,物品数为i-1, 重量为j时的价值
- 哪一个大,选择哪一个。
[3] 代码(C)
#include <stdio.h>
#define N 4 //物品数量
#define Capacity 10 //包容量
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main()
{
int weight[N] = {7,3,4,5}; //物品重量
int value[N] = {42,12,40,25}; //物品价值
int dp[N + 1][Capacity + 1];
int i,j; //i:第i件物品; j:容量
for (i = 0; i <= N; i++)
{
for (j = 0; j <= Capacity; j++)
{
if (i == 0 || j == 0)
{
dp[i][j] = 0;
continue;
}
if (j >= weight[i])
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
else
dp[i][j] = dp[i - 1][j];
}
}
printf("%d", dp[N][Capacity]);
return 0;
}
Acknowledgements:
http://blog.csdn.net/lcj_cjfykx/article/details/41691787
http://blog.csdn.net/ebowtang/article/details/45127659(推荐)
http://blog.csdn.net/uestclr/article/details/50760563(DP其他例题)
2017.09.17