概念
动态规划是运筹学中用于求解决策过程中的最优化数学方法。当然,我们在这里关注的是作为一种算法设计技术,作为一种使用多阶段决策过程最优的通用方法
基本思想
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了实用的信息。
在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其它局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
- 动态规划解决的问题多数有重叠子问题这个特点。为降低反复计算。对每个子问题仅仅解一次,将其不同阶段的不同状态保存在一个二维数组中。
- 动态规划算法与分治法最大的区别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
动规解题的一般思路:
-
将原问题分解为子问题
把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(以走方格问题为例)。
子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。 -
确定状态
在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在走方格的例子里,一共有N×N数字,所以这个问题的状态空间里一共就有N×N个状态。 -
确定一些初始状态(边界状态)的值
以“方格”为例,边界状态就是第一行与第一列,值就是1。
-
确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
能用动规解决的问题的特点:
- 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。
- 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。
- 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到(该性质并非动态规划适用的必要条件,可是假设没有这条性质。动态规划算法同其它算法相比就不具备优势)。
问题描述:
小明从A点要去B点,请问有多少种不同的路线?规则是只能向下和向右走。
思绪:
- 典型的动归问题,假设B在A的右边,此时只有一条路线;B在A的下面,也只有一条路线;
- 然后慢慢累加,如下图所示!
问题扩展:
此时在A到B的路程中有一柱子挡着(柱子不能被翻越)。请问有多少种不同的路线?
思绪
- 同上所述,不考虑柱子占据的位置,慢慢累加!
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(){
int a[10][10] = { 0 };
int x, y;
int x1, y1;
int i, j;
printf("请输入表格的行与列:");
scanf("%d%d", &x, &y);
printf("请输入柱子的坐标:");
scanf("%d%d", &x1, &y1);//可以在图中设置柱子
for (i = 1; i <= x; i++){
for (j = 1; j <= y; j++){
if (i == x1 && j == y1){//如果是柱子则不计数直接跳过
continue;
}
if (i == 1 && j == 1){
a[i][j] = 1;
}
else{
a[i][j] = a[i - 1][j] + a[i][j - 1];
}
}
}
printf("共有%d种不同的路线 ", a[x][y]);
system("pause");
return 0;
}
代码生成图:
问题升级:(类似于数塔问题)
还是由A走到B,但每个格子对应有数字,求A(5)走到B(6),数字最大为多少?
思绪:
- 同上所述,先确定边界状态!确定里面的状态需要用与他相临(上方,左方)较大边界与他相加,确定自身状态!
- 最终确定下来的每个格子状态为下图所示:
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(){
int a[5][5] = { { 0, 0, 0, 0, 0 },
{ 0, 5, 18, 4, 20 },
{ 0, 22, 15, 9, 10 },
{ 0, 14, 16, 12, 21 },
{ 0, 19, 8, 11, 6 } };
int i, j;
for (i = 1; i <= 4; i++){
for (j = 1; j <= 4; j++){
if (a[i-1][j] > a[i][j-1]){
a[i][j] += a[i-1][j];
}
else{
a[i][j] += a[i][j - 1];
}
}
}
printf("数字最大为%d\n", a[4][4]);
system("pause");
return 0;
}
代码生成图: