动态规划算法
一、 概念:
每次决策依赖于当前状态,又随即引起状态的转移。
一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化策略解决问题的过程就成为动态规划。
二、 特性:
能采用动态规划求解的问题的一般具有3个性质:
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前的状态有关。
(3) 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。
三、 基本思想:
动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。
该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值的解。
若存在若干个取最优值的解的话,它只取其中一个。
在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,动态规划允许这些子问题不独立,也允许其通过自身子问题的解做出选择,该方法对每个子问题只解一次,并将结果保存起来,避免每次碰到时都要重复计算。
四、 基本步骤:
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。
这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线。
初始状态à|决策1|à|决策2|à…à|决策n|à结束状态
(1) 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2) 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。状态的选择要满足无后效性。
(3) 确定决策并写出状态转移方程:因为决策和状态的转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果确定了决策,状态转移方程也就可以写出。但事实上常常是反过来做,根据相邻的两个阶段的状态之间的关系来确定决策方法和状态转移方程。
如:给定k阶段状态变量x(k)的值后,如果这一阶段的决策变量一经确定,第k+1阶段的状态变量x(k+1)也就完全确定,即x(k+1)的值随x(k)和第k阶段的决策u(k)的值变化而变化,那么可以把这一关系看成(x(k),u(k))与x(k+1)确定的对应关系,用x(k+1)=Tk(x(k),u(k))表示。
(4) 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
实际中,可以简化步骤来进行设计:
(1) 分析最优解的性质,并刻画其结构特征。
(2) 递归的定义最优解。
(3) 以子底向上或自顶向下的记忆化方式(备忘录法)计算出最优值。
(4) 根据计算最优值时得到的信息,构造问题的最优解。
五、 算法说明:
确定动态规划的三要素:
(1) 问题的阶段;
(2) 每个阶段的状态;
(3) 从前一个阶段转化到后一个阶段之间的递推关系。
递推关系必须是从次小的问题开始到较大的问题之间的转化,往往可以通过递归程序来实现。
确定了动态规划的三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值。
f(n,m) = max{f(n-1,m),f(n-1,m-w[n])+P(n,m)}
六、 复杂度:
动态规划算法一般是n步叠代计算局部最优解,每一步叠代需要计算m个子项,那么时间复杂度就是O(m*n)。如果只保存一步叠代的结果,空间复杂度就是O(m);
如果需要保存k步叠代结果,空间复杂度就是O(m*k)。
七、 应用领域:
常用软件:MATLAB、LINGO
作用:在编程中常用解决最长公共子序列问题、矩阵连乘问题、凸多边形最优三角剖分问题、电路布线等问题。
一般来说,只要问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解,则可以考虑用动态规划解决。
动态规划问世以来,在工程技术、经济管理等社会各个领域有着广泛的应用,并且获得了显著的效果。
在经济管理方面,动态规划可以用来解决最优路径问题、资源分配问题、生产调度问题、库存管理问题、排序问题、设备更新问题以及生产过程最优控制问题等,是经济管理中一种重要的决策技术。许多规划问题用动态规划的方法来处理,常比线性规划或非线性规划更有效。特别是对于离散的问题,由于解析数学无法发挥作用,动态规划便成了一种非常有效的工具。
动态规划可以按照决策过程的演变是否确定分为确定性动态规划和随机性动态规划;也可以按照决策变量的取值是否连续分为连续性动态规划和离散性动态规划。虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划)。只要人为的引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便的求解。
最经典的动态规划算法案例
背包问题(Knapsack Problem):
假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可得之总价物品,假设是水果,水果编号、单价与重量如下所示:
编号 名称 重量 单价
0 李子 4KG NT$4500
1 苹果 5KG NT$5700
2 橘子 2KG NT$2250
3 草莓 1KG NT$1100
4 甜瓜 6KG NT$6700
解法:背包问题是关于最佳化的问题,可以采用动态规划进行解答,从空集合开始,每增加一个元素就先求出该阶段的最佳解,直到所有的元素加入至集合中,最后得到的就是最佳解。
步骤:使用两个阵列value和item,分别表示目前的最佳解所得之总价和最后一个放至背包的水果,假设有负重量1~8的背包8个,并对每个背包求其最佳解。
逐步将水果放入背包中,并求该阶段的最佳解:
(1)放入李子:
背包负重 1 2 3 4 5 6 7 8
value 0 0 0 450 450 450 450 900
item -- -- -- 0 0 0 0 0
(2)放入苹果:
背包负重 1 2 3 4 5 6 7 8
value 0 0 0 450 570 570 570 900
item -- -- -- 0 1 1 1 0
(3)放入橘子:
背包负重 1 2 3 4 5 6 7 8
value 0 225 225 450 570 675 795 900
item -- 2 2 0 1 2 2 0
(4)放入草莓:
背包负重 1 2 3 4 5 6 7 8
value 110 225 335 450 570 680 795 905
item 3 2 3 0 1 3 2 3
(5)放入甜瓜:
背包负重 1 2 3 4 5 6 7 8
value 110 225 335 450 570 680 795 905
item 3 2 3 0 1 3 2 3
由最后一个表格可以看出,在背包负重8公斤时,最多可以装入9050元的水果,而最后一个装入的水果是3号,也就是草莓,装入了草莓,背包只能再放入7公斤的水果,所以必须看背包负重7公斤的最佳解,最后一个放入的是2号,也就是橘子,现在背包剩下负重量5公斤,所以看负重5公斤的最佳解,最后让入的是1号,也就是苹果,此时背包负重量剩下0公斤,无法再放入水果,所以求出最佳解为放入草莓、橘子与苹果,而总价为9050。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#define LIMIT 8 // 重量限制
#define N 5 // 物品种类
#define MIN 1 // 最小重量
struct body
{
char name[20];
int size;
int price;
};
typedef struct body object;
int main(void)
{
int item[LIMIT+1] = {0};
int value[LIMIT+1] = {0};
int newvalue, i, s, p;
object a[] = {{"李子", 4, 4500},
{"苹果", 5, 5700},
{"橘子", 2, 2250},
{"草莓", 1, 1100},
{"甜瓜", 6, 6700}};
for(i = 0; i < N; i++)
{
for(s = a[i].size; s <= LIMIT; s++)
{
p = s - a[i].size;
newvalue = value[p] + a[i].price;
if(newvalue > value[s]) // 找到阶段最佳解
{
value[s] = newvalue;
item[s] = i;
}
}
}
for(i = LIMIT; i >= MIN; i = i - a[item[i]].size)
{
printf("%s\t%d\n",a[item[i]].name, a[item[i]].price);
}
printf("合计\t%d\n", value[LIMIT]);
return 0;
}