动态规划算法详解及走方格问题
动态规划算法:
基本概念:
动态规划是运筹学中用于求解决策过程中的最优化数学方法。当然,我们在这里关注的是作为一种算法设计技术,作为一种使用多阶段决策过程最优的通用方法
基本思想:
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了实用的信息。
在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其它局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
因为动态规划解决的问题多数有重叠子问题这个特点。为降低反复计算。对每个子问题仅仅解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的区别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
动规解题的一般思路:
将原问题分解为子问题
把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(以走方格问题为例)。
子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
确定状态
在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在走方格的例子里,一共有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 m, n;
int i = 0, j = 0;
printf("请输入表格的行与列:");
scanf("%d%d", &m, &n);
for (i = 1; i <= m; i++){
for (j = 1; j <= n; j++){
if (i == 1 && j == 1){
a[i][j] = 1;
}
else{
a[i][j] = a[i - 1][j] + a[i][j - 1];
}
}
}
printf("共有%d种不同的路线\n", a[m][n]);
system("pause");
return 0;
}
运行结果:
**拓展:**假如恰好A与B之间有一条河流不能通过,
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(){
int a[10][10] = { 0 };
int m = 0, n = 0;
int M, N;
int i = 0, j = 0;
printf("请输入表格的行与列:");
scanf("%d%d", &m,&n);
printf("请输入河流的的坐标:");
scanf("%d%d", &M, &N);
for (i = 1; i <= m; i++){
for (j = 1; j <= n; j++){
if (i == M && j == N){
continue;
}
if (i == 1 && j == 1){
a[i][j] = 1;
}
else{
a[i][j] = a[i - 1][j] + a[i][j - 1];
}
}
}
printf("A到B共有%d种不同的路线\n", a[m][n]);
system("pause");
return 0;
}
运行结果:
动态规划之数塔问题
问题描述
由A走到B,但每个格子对应有数字,求A走到B,数字最大为多少?
假设方格中填充的数字:
{ 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 }
思路:
要求出当前位置的最大值,必须知道上面和左边的哪一个大,然后与当前位置进行相加
#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 = 0, j = 0;
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;
}
运行结果:
动态规划之青蛙跳台
问题描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。
解题思路:
找到递推公式: 台阶数: 跳法: 1 1jump1 2 2jump2 3 3jumpn 4 5 5 8 ‘’’’
根据得到的递推公式能够很明显的得到 当台阶数从第3层开始每上升一层就是前两层之和 (实际上也就类似于斐波那契数)
*递归:
#include <stdio.h>
#include <stdlib.h>
int jump(int n) {
if (n == 1)
return 1;
if (n == 2)
return 2;
return jump(n - 1) + jump(n - 2);
}
int main () {
printf("%d\n",jump(10));
system ("pause");
return 0;
}
运行结果:
非递归:
#include <stdio.h>
#include <stdlib.h>
int jump(int n) {
if (n == 1)
return 1;
if (n == 2)
return 2;
int jump1 = 1;
int jump2 = 2;
int jumpn;
for (int i = 3; i <= n;i++) {
jumpn = jump1 + jump2;
jump1 = jump2;
jump2 = jumpn;
}
return jumpn;
}
int main () {
printf("%d\n", jump(5));
system ("pause");
return 0;
}
运行结果:
拓展:
假如依次能跳1个台阶或者2个台阶或者3个台阶,求一共有n个台阶时,一共有多少种跳法?
台阶数: 跳法 : 1 1 2 2 3 3 4 6…
#include <stdio.h>
#include <stdlib.h>
int jump(int n) {
if (n == 1)
return 1;
if (n == 2)
return 2;
if (n == 3)
return 3;
return jump(n - 1) + jump(n - 2) + jump(n - 3);
}
int main () {
printf("%d\n",jump(4));
system ("pause");
return 0;
}
运行结果: