动态规划算法
Posted 极客剑寮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划算法相关的知识,希望对你有一定的参考价值。
动态规划算法
啥是动态规划?
按照规矩,先上个官方定义:
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。(百度百科)
看到这么一串,我相信没几个人会看完,事实上,就在我写这行字的时候,我也没有看,但是这段话大的意思很简单。
提出者是R.E.Bellman等人
是把一个问题转化为一系列子问题的过程
这些子问题之间有某种联系
把一个问题分解成几个问题,这是分治法的思想,但是动态规划与分治的不同就在于——子问题之间是有联系的。
事实上,用分治法解决动态规划问题也未尝不可,但是差别就在于动态规划利用了子问题之间的联系,从而优化了问题的解决。
动态规划的三大法宝
最优子结构
当一个问题的最优解能够通过求解其子问题的最优解来解决时,就称这个问题具有最优子结构的性质。
最优子结构就是一个问题的子问题,而且解决子问题的最优解能够得到该问题的最优解。
边界
由于动态规划是问题划分的算法,所以必须设定一个划分的终点,不能“一尺之棰,日取其半,万世不竭”,这个终点就是动态规划的边界。
状态转移方程
把问题的划分,用方程来表示,就是状态转移方程。
从背包问题开始
实践出真知,下面我们从动态规规划一道经典的问题入手,来深入了解动态规划。
0-1背包问题:有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
问题分析:这道题其实就是“拿与不拿”的问题,使用蛮力算法,可以“轻松”地解决,时间复杂度一眼就能看出来是O(n2)。下面我们使用动态规划来分析。
三大法宝登场:
设函数为
V(i,j)
,其中i为剩下i件物品,j为背包剩余的空间。边界为:
V ( 0 , j ) = V ( i , 0 ) = 0 状态转移方程:
V ( i , j ) = { V ( i − 1 , j ) , j < w [ i ] , m a x { V ( i − 1 , j ) , V ( i − 1 , j − w [ i ] ) + v [ i ] } , j ≥ w [ i ] , 第一种情况:如果背包剩余空间能装下前一个物品,那么最优解就是装前一件和不装前一件两者中的最大值。
第二种情况:如果背包剩余空间装不下前一件,那么最优解就是不装前一件的最优解。
不拿第N件物品,求解从剩下N-1件物品拿东西往容量为V的背包里装的最优解。
拿第N件物品,求解从剩下N-1件物品拿东西装满背包剩下V-w[n](这里我们设数组下标从1开始)。
余下以此类推,所以这道题是具有最优子结构的。最优子结构:这道题很显然,能够分解成两个问题:
边界:这道题的边界显然就是剩余物品为0或者剩余背包空间为0.
状态转移方程:
公式:
递归实现(python):
def V(i, j, w, v):
# 边界条件
if i*j == 0:
return 0
else:
if j < w[i-1]:
return V(i-1, j, w, v)
else:
return max(v1=V(i-1,j,w,v),
v2=V(i-1,j-w[i-1],w,v)+v[i-1])
递归的时间复杂度是O(n),但是由于是递归,所以空间复杂度很大,这并不完全是动态规划,下面我们用迭代实现。
为了实现迭代,显然,我们要V(i,j)只依赖于V(i-1,j)和V(i-1,j-w[i]),从而,我们想要得到V(i,j),只需要知道V(0,j)和V(i,0)就可以了。
设物品重量为[2,2,6,5,4],价值为[6,3,5,4,6],背包容量为10
先求出边界的值:
i(w,v)\j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0(0,0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(2,6) | |||||||||||
2(2,3) | |||||||||||
3(6,5) | |||||||||||
4(5,4) | |||||||||||
5(4,6) |
然后求出V(1,j)
i(w,v)\j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0(0,0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(2,6) | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2(2,3) | |||||||||||
3(6,5) | |||||||||||
4(5,4) | |||||||||||
5(4,6) |
事实上,我们要求的行只和前一行有关,所以在存储的时候,只需要存储前一行就可以了。后面我们直接填完表格。
i(w,v)\j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0(0,0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(2,6) | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2(2,3) | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 9 | 9 | 9 |
3(6,5) | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 11 | 11 | 14 |
4(5,4) | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 10 | 11 | 13 | 14 |
5(4,6) | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
代码实现( C ):
#include <stdio.h>
#define N 5
#define S 10
int main(void)
{
int V[S+1];
// 初始化第一行
for(int j=0;j<=S+1;j++)
V[j] = 0;
// 迭代过程
for(int i=1;i<=N;i++)
for(int j=S+1;j<=0;j++)
{
if(j >= w[i])
{
if(V[j] < V[j-w[i]])
V[j] = V[j-w[i]];
}
}
printf("%d", V[S+1]);
return 0;
}
至此,背包问题已经解决,这是个二输入的问题,动态规划也可以解决更多输入(一输入就更不用说了)。
以上是关于动态规划算法的主要内容,如果未能解决你的问题,请参考以下文章
算法动态规划 ⑤ ( LeetCode 63.不同路径 II | 问题分析 | 动态规划算法设计 | 代码示例 )
算法动态规划 ① ( 动态规划简介 | 自底向上的动态规划示例 | 自顶向下的动态规划示例 )