一、引言
“动态规划” (dynamic programming)一词源于研究优化问题的数学理论,动态规划法的发明人贝尔曼(Richard E.Bellman)称,选择“dynamic”一词纯粹是看中了单词本身的魅力,而不是其内在语义。“programming”在研究优化的领域中标识“搜寻最优程序”的意思。
1、重复子问题
大体上,动态规划法与分治法具有类似的处理方式。使用动态规划法的算法通常先把问题分割成若干子问题,然后求出子问题的答案,最后利用这些答案得出整个问题的最终答案。在动态规划法中,一些子问题的计算结果会用于多个问题的解题过程。因此,在对子问题进行一次计算的情况下,重复利用其结果。为了实现这种方法,需将子问题的答案预先保存到内存,这种保存答案的内存区域就是“缓存”(cache),能够重复利用两次以上的子问题就称为 “重复子问题”。
/*
应用 动态规划法 的最有名的事例之一就是二项式系数的计算
根据二项式系数有如下递归式Cn取r等于Cn-1取r-1加上Cn-1取r
*/
#include <iostream>
using namespace std;
int bino(int n,int r)
{
if(r==0 || r==n)
return 1;
return bino(n-1,r-1) + bino(n-1,r);
}
int main()
{
int n,r;
cout<<"请输入二项式系数n,r"<<endl;
cin>>n>>r;
bino(n,r);//利用递归调用计算二项式系数
cout<<bino(n,r)<<endl;
system("pause");
return 0;
}
bino()内部没有任何循环,因此,可通过计算递归调用的次数来估计bino(n,r)的执行时间,此时需要注意的是,二项式系数的特性会导致很多重复计算。函数的重复调用次数会随着n和r值的增大而呈几何级数增长。如果要计算bino(25,12),就要调用一千万次函数。因此需要想办法避开这些重复计算。当输入值n和r一定的时候,bino(n,r)的返回值也是一定的,利用此原理就可以去掉重复计算。首先定义一个缓存数组,用来保存对不同的n、r组合计算出的结果。每次调用函数时先访问缓存数组,查询有没有相应的结果值,如果有,就返回数组中的值,否则直接计算,计算结果先保存到数组,然后再返回。
像这种先定义保存函数结果值的空间,然后重复使用结果值的优化方法称为制表。下面代码演示了利用制表计算二项式系数:
/*
应用 动态规划法 的最有名的事例之一就是二项式系数的计算
根据二项式系数有如下递归式Cn取r等于Cn-1取r-1加上Cn-1取r
*/
#include <iostream>
#define MAX 100
using namespace std;
int cache[MAX][MAX];//缓存区域用于存储函数结果值,并初始化为-1
int bino2(int n, int r)
{
if (r == 0 || r == n)
return 1;
if (cache[n][r] != -1)
return cache[n][r];
return cache[n][r] = bino2(n - 1, r - 1) + bino2(n - 1, r);//直接计算并保存到数组
}
int main()
{
int n, r;
int i, j;
for (i = 0; i < MAX; i++){
for (j = 0; j < MAX; j++)
cache[i][j] = -1; //初始化为-1
}
cout << "请输入二项式系数n,r" << endl;
cin >> n >> r;
bino2(n, r);//利用制表计算二项式系数
cout << bino2(n, r) << endl;
system("pause");
return 0;
}