动态规划(多阶段决策)
意义:求解决策过程最优化的数学方法
基本思想:将待求解的问题分为若干个阶段,即若干个互相联系的子问题,在求解子问题的过程中逐步推导出原问题的解。
核心:在求解子问题的过程中,存储子问题的解。
注1:动态规划的思想实际上和递归相似。都是通过逐步推导,得到答案。而用它们解题时的核心也都是求出“递推公式”(类似 高中数学中数列的递推公式an=an-1+n)。但动态规划和递归的一大区别是:动态规划将递归的每一步的结果都存储下来,在下次使用时直接调用而非重复运算。从而节省了大量的计算资源。
注2:最简单的动态规划可以通过建立一个缓存数组配合略微修改原递归函数来实现。只要将递归过程中得到的解存入缓存数组,并在递归求解的过程中优先在缓存数组中寻找结果。就能避免重复的运算,使递归函数的效率大大提高。这实际上就是“用空间换时间”的方法。
递归和动态规划的区别:
递归:对于求f(n)时,通过f(n)和f(n-1)的关系,循环缩减n的值,最终到达f(1)或f(0)等数量级时,给出答案。再通过给出的答案,循环计算出f(n)的值(如f(1)=1,则可以通过f(n)=f(n-1)+1得出f(2)=2)。
计算f(n)需要f(n-1),计算f(n-1)需要f(n-2)...计算f(2)需要f(1)。f(1)=1所以f(n)=...;
动态规划:建立循环i=1~i=n,然后直接通过f(1)的值循环计算f(i),最终得到f(n)。
计算f(n)需要用到f(1)到f(n-1)所有的值,所以直接循环计算从f(2)到f(n-1)的所有值并存储。则当需要f(n)的值时,就可以直接取用需要的值了,避免了大量重复的循环。
对于以下
圆代表地点,圆内的数字代表地点编号,圆间的箭头代表通路,箭头上的数字代表开销
要求求从0到6的最小开销。
代码示例:
#include <stdio.h>
#include <stdlib.h>
/**/
#define n 7
#define x 9999
/*建立地图数组,map[i][j]=n代表i和j点直接的开销为n,将x作为一个大数填充入不可通行的路径*/
int map[n][n]=
{
{x,4,5,8,x,x,x},
{x,x,x,6,6,x,x},
{x,x,x,5,x,7,x},
{x,x,x,x,8,9,9},
{x,x,x,x,x,x,5},
{x,x,x,x,x,x,4},
{x,x,x,x,x,x,x}
};
int main()
{
int cost[n];//cost数组用于存储从0点到达每个点的最小开销
int path[n]={0};//path用于存储当前点的最小开销路径的上一个点
int i,j;
int minCost,minNode;
cost[0]=0;
/*循环计算每一个点的最小开销*/
for(i=1;i<n;i++)
{
minCost=x;
for(j=0;j<i;j++)
{
if(map[j][i]!=x)
if((cost[j]+map[j][i])<minCost)
{
minCost=cost[j]+map[j][i];
minNode=j;
}
}
cost[i]=minCost;
path[i]=minNode;
}
/*输出结果*/
printf("最短路径的开销为:%d\n",cost[n-1]);
printf("从终点向前推,最短路径经过了:");
i=n-1;
while(i!=0)
{
printf(" %d",path[i]);
i=path[i];
}
printf("\n");
return 0;
}
结果:
解析:
以上即是使用了动态规划原理的解题。
代码示例:
#include <stdio.h>
#include <stdlib.h>
/*
描述
需要你做的就是写一个程序,得出最长公共子序列。
tip:最长公共子序列也称作最长公共子串(不要求连续),
英文缩写为LCS(Longest Common Subsequence)。
其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,
且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
输入
第一行给出一个整数N(0<N<100)表示待测数据组数
接下来每组数据两行,分别为待测的两组字符串。每个字符串长度不大于1000.
输出
每组测试数据输出一个整数,表示最长公共子序列长度。每组结果占一行。
*/
int main()
{
/*t接收测试数据的组数*/
int t=0;
scanf("%d",&t);
while(t-->0){
/*ch1,ch2接收两个字符串*/
char ch1[100]={'\0'};
char ch2[100]={'\0'};
scanf("%s",ch1);
scanf("%s",ch2);
/*lcs数组用于动态规划*/
int lcs[100][100];
int i,j;
for(i=0;i<strlen(ch1);i++){
for(j=0;j<strlen(ch2);j++){
/*核心算法*/
if(ch1[i]==ch2[j])
lcs[i+1][j+1]=lcs[i][j]+1;
else
lcs[i+1][j+1]=lcs[i][j+1]>lcs[i+1][j]?lcs[i][j+1]:lcs[i+1][j];
}
}
/*输出结果*/
printf("%d\n",lcs[strlen(ch1)][strlen(ch2)]);
}
return 0;
}
结果:
解析:
1.对于两个序列s1:{x0,x1,x2,x3,x4,x5,....,xn},s2:{y0,y1,y2,y3,y4,y5,....,ym}。
任取其中的两个元素xk,yv(k不一定等于v)。
设lcs(n,m)为s1,s2的最大公共子序列的长度n,m分别代表取s1、s2序列的前n、m项加入考量
则
当xk=yv,lcs(k,v)=lcs(k-1,v-1)+1; (如果xk=yv,则xk和yv必然是最大公共子序列的一部分,所以将上一层的最大公共子序列记录的长度加一)
当xk!=yv,lcs(k,v)=Max(lcs(k-1,v),lcs(k,v-1)); (如果xk!=yv,啧xk和yv必然不能同时作为最大公共子序列的一部分,所以将只取xk的情况和只取yv的情况分别讨论,并将两种的大值作为当前的最大公共子序列长度)
且,k/v任意一个等于0时,lcs(k,v)=0 (k/v任意一个等于0,则其中一个字符串为空,自然不可能有公共子序列)
以上即代码核心算法。
2.使用动态规划原理逐层计算lcs数组,最终得到期望的结果。