1.1 动态规划简介
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。
动态规划要注意两点:1,状态;2,状态转移方程。
我不太愿意详细讲解下面三个问题的思考过程,一步步推导没有太大的必要,毕竟编程这种事情看一百遍,还不如自己在纸上画一画,然后敲敲代码。
希望能通过下面这三个小问题,来熟悉,学习动态规划。
1.2 硬币问题
问题:如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?
解析:
(1)凑够1元,1个1元硬币
(2)凑够2元,2个1元硬币
(3)凑够3元,3个1元硬币或者1个3元硬币,又1<3,所以选择一个3元硬币。以此类推至11元。
(4)其实是在之前的基础确定的。如,
2元=1元(1个硬币)+1元(1个硬币);//需2个硬币
3元=2元(2个硬币)+1元(1个硬币)。
(5)用数组Coin存储硬币种类,数组Dp存储相应所需最小硬币个数,如Dp[m]=n,代表凑够m至少需要n个硬币。为方便使用,不使用Dp[0]。
(6)构建方程:Dp[i]=Dp[i-Coin[j]]+1;//加1代表需要Coin[j]这个硬币
源码(简单测试可用):Talk is cheap,show me the code!
int main()
{
int coin[Count]={1,3,5};//硬币类型
int Dp[Sum+1];//记录从0-11的银币数目
//算法
int i,j;
//初始化,使每一个钱数为相应个数为1的硬币构成
for(i=0;i<=Sum;i++)
{
Dp[i]=i;
}//Dp[0]=0不使用
for(i=1;i<=Sum;i++)
{
for(j=0;j<Count ;j++)
{
if(i>=coin[j]&&Dp[i-coin[j]]+1<Dp[i])
{
Dp[i]=Dp[i-coin[j]]+1;
}
}
}
//打印
cout<<Dp[Sum]<<endl;
for(i=1;i<=Sum;i++)
{
cout<<Dp[i]<<" ";
}
}
1.3 数塔问题
问题:从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,
要求找出一条路径,使得路径上的数字之和最大.
解析:
(1)自底向上分析。先使用M二维数组存储该数塔,然后创建Dp二维数组,存储每个位置的和,如下图
(2)每次从本层结点的2个分支结点中选出最大值,
构建方程:Dp[i][j]=Max{Dp[i+1][j],Dp[i+1][j+1]}+Arry[i][j];
源码(简单测试可用):Talk is cheap,show me the code!
//数塔问题处理函数,自底向上寻找
void DataTower()
{
int i,j;
//初始化
for (i=0;i<Max;i++)
{
Dp[Max-1][i]=M[Max-1][i];//复制图最后一列
}
//计算
for(i=Max-2;i>=0;i--)//最后一行已经赋值,还有Max-1行没有赋值
{
for(j=0;j<=i;j++)//第i行就有i列
{
//找出左右子节点最大的一个
if(Dp[i+1][j]>Dp[i+1][j+1])
Dp[i][j]=Dp[i+1][j]+M[i][j];
else
Dp[i][j]=Dp[i+1][j+1]+M[i][j];
}
}
}
打印,来源网上
//由Dp数组,打印最终结果
void print ()
{
cout << "最大路径和:" << Dp[0][0] << '\n';
int node_value;
// 首先输出塔顶元素
cout << "最大路径:" << M[0][0];
int j = 0;
for (int i = 1; i < Max; ++i)
{
node_value = Dp[i - 1][j] - M[i - 1][j];
if (node_value == Dp[i][j + 1]) ++j;
cout << "->" << M[i][j];
}
cout << endl;
}
1.4 最长非降子程序
问题:5,3,4,8,6,7
求该数列的最长非降子序
解析:
(1)假设该序列只有一个”5”,那么最长为1;
(2)“5,3”,转换来看“5(1),3(1)”,最长为1;
(3)“5,3,4”,转换“5(1),3(1),4(2)”,因为最长非降序列为“3,4”,最长为2;
(4)不一一列举,看看这条更清楚一些,“5(1),3(1),4(2),8(3),6(3)”。“6”的确定是因为“4”,“3,4,6”在“4”对应的长度上加1,为3;
(5)创建数组Dp,构建方程:Dp[i]=Dp[j]+1;
判断条件是A[i]>=A[j]。
源码(简单测试可用):Talk is cheap,show me the code!
本人依据以上思路写的。
void Long01(int *A)
{
int Dp[Max];//记录i位置对应的相应的非降子序的个数
int i,j;
Dp[0]=1;//第一个元素为肯定为1
for (i=1;i<Max;i++)
{
if(A[i]>=A[i-1])
{
Dp[i]=Dp[i-1]+1;
}
else
{
for(j=i-1;j>=0;j--)
{
if(A[i]>A[j])
{
Dp[i]=Dp[j]+1;
break;
}
}
if(j<0)
{
Dp[i]=1;
}
}
}
}
网上跟上述思路一致,但是更简洁
void Long02(int *A)
{
int d[Max];
int len = 1;
for(int i=0; i<Max; ++i){
d[i] = 1;
for(int j=0; j<i; ++j) //循环i前面的几个元素
if(A[j]<=A[i] && d[j]+1>d[i]) //i前可能存在多个小于i的值,取最大者,个人觉得此处倒着找好一些
d[i] = d[j] + 1;
if(d[i]>len) len = d[i];
}
cout<< len<<endl;
}
1.5 总结
比较简单,如果有错误的地方,烦请大牛指教,上述的代码是在Vs2010中编写的,如果有乱码问题,可以在:https://github.com/AngryCaveman/C-Struct.git中查看下载“018番外”文件夹下本人的源码,其他文件夹中项目是在Vs2013中编写的。