动态规划笔记
由于自己是非计算机专业,而对编程学习比较感兴趣,特学习算法导论一书,根据书上伪代码来练习,一来提高自己算法思维,二来督促自己好好学习,不能虚度光阴。
一:思想
动态规划,通常用来求解最优化问题,这类问题有很多可行解,我们希望寻找其最优解,通常是最大值最小值。
具体步骤就是一个大问题可以分为很多子问题,通过求解小规模的子问题,进而组合子问题得到原问题的解。使用动态规划解决问题,需要具备以下两个因素。
- 最优子结构:一个问题的最优解包含子问题的最优解。也就是一个最优解,它也必须是字问题的最优解。
- 重叠子问题:问题的递归算法要重复求解相同的子问题,而不是一直生成新问题。
二:例题
1 钢条切割问题
对于长度为 i 的钢条的价格 pi 如下。如何切割达到价值最大。
- 最优子结构:当钢条完成首次切割后,变成两段独立的钢条。进而求解两段独立的钢条最优解,然后相加即可。
- 重叠子问题:钢条越切越少,则子问题重叠的越多。
我们将钢条从左边切下的长度记为i的那一段,只对右边长度n-i的一段继续进行切割(递归求解),对左边的不再进行切割。即问题转化为:第一段长度求解如下最大值:rn=max(pi+pn−i),i=1...n.
自顶向下法自然求解
所谓自顶向下法,是直接利用递归的算法求解原问题。再次期间递归调用会继而求解小规模问题。
此算法可以改进,因此它会多次重复的计算规模更小的子问题。解法方法是引入备忘录机制,当 i<n 时,最大收益被计算 ri ,就保存起来,下次计算到的时候,直接查表引用,不必再进行递归计算。
//直接递归计算
//int cut_rod(int p[],int n)
//{
// if(n==0)
// return 0;
// int q=-1;
// for(int i=1;i<=n;i++)
// {
// int temp=p[i]+cut_rod(p,n-i);
// if(q<temp)
// {
// q=temp;
// }
// }
// return q;
//}
//带备忘录:先定义一个数组存放计算过的收益,此数组不能放到递归里。
//int Memoized_cut_Rod(int p[],int n)
//{
// vector<int>r(n,-1);
// return Memoized_cut_Rod_Aux(p,n,r);
//}
//int Memoized_cut_Rod_Aux(int p[],int n,vector<int>r)
//{
// int q=-1;
// if(r[n]>=0)
// return r[n];
// if(n==0)
// q=0;
// else
// {
// for(int i=1;i<=n;i++)
// {
// int temp=p[i]+Memoized_cut_Rod_Aux(p,n-i,r);
// if(q<temp)
// {
// q=temp;
// }
// }
// r[n]=q;
// }
// return q;
//
//}
自底向上法求解小规模问题
自底向上法是直接先求解小规模问题,在小规模问题上逐步求解大规模问题。
//int Bottom_up_cut_rod(int p[],int n)
//{
// vector<int>r;//保存切割的最优值
// vector<int>s;//保存切割点
// r.push_back(0);
// s.push_back(0);
// for(int j=1;j<=n;j++)//逐步求解规模j的问题,一直求解到n,即原问题
// {
// int q=-1;
// for(int i=1;i<=j;i++)
// {
// int temp=p[i]+r[j-i];
// if(q<temp)
// {
// q=temp;
// s.push_back(i);
// }
// }
// r.push_back(q);
// }
// return r[n];
//}
//
实例计算
int main()
//{
// int p[]={0,1,5,8,9,10,17,17,20,24,30};
// cout<<cut_rod(p,5)<<endl;
// cout<<Memoized_cut_Rod(p,5)<<endl;
// cout<<Bottom_up_cut_rod(p,5)<<endl;
// getchar();
//
//}
2 矩阵链相乘问题
给定n个矩阵序列 <A1,...,An> <script type="math/tex" id="MathJax-Element-5"> </script>,如何加括号明确计算次序,进行更优的计算。