动态规划:
Dynamic Programming(此处“Programming”为“规划”,而非指“程序”、“编程”),研究多步决策过程最优化问题的一种数学方法,英文缩写DP。在动态规划中,为了寻找一个问题的最优解(即最优决策过程),将整个问题划分成若干个相应的阶段,并在每个阶段都根据先前所作出的决策作出当前阶段最优决策,进而得出整个问题的最优解。
能采用动态规划求解的问题的一般要具有3个性质:
1.最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
2.无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
3.有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
解题步骤:
1.拆分问题
2.定义状态(并找出初状态)
3.状态转移方程
例1:
有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,如图所示; 从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大。
把当前位置(i,j)看成一个状态,然后定义状态(i,j)的指标函数d(i,j)为从格子(i,j)出发时能得到的最大和(包括格子(i,j)本身的值)。在此状态的定义下,原问题的解为d(i,j)。
状态转移方程:
d(i ; j) = a(i ; j) + max fd(i + 1; j); d(i + 1; j + 1)}
实现方式:
1.递归计算
int solve(int i,int j)
{
return a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
}
2.记忆化搜索
memset(d,-1,sizeof(d));
int solve(int i,int j)
{
if(d[i][j]>=0) return d[i][j];
return d[i][j]=a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
}
3.递推计算
//递推
int i,j;
for(j=1;j<=n;j++)
d[n][j]=a[n][j];
//递推
for(i=n-1;i>=1;i--)
{
for(j=1;j<=i;j++)
{
d[i][j]=a[i][j]+max(d[i+1][j],d[i+1][j+1]);
}
}
例2:
如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元? (表面上这道题可以用贪心算法,但贪心算法无法保证可以求出解,比如1元换成2元的时候)
状态:d(i)表示凑够i元需要的最少硬币数量
状态转移方程:
d(i) = min {d(i - vj ) + 1},其中i - vj >= 0,vj表示第j个硬币的面值。
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i>=v[j])
{
d[i]=min(d[i],d[i-v[j]]+1);
}
}
}