动态规划的基本思想是,将原问题拆分为若干子问题,自底向上的求解。其总是充分利用重叠子问题,即通过每个子问题只解一次,把解保存在一个表中,巧妙的避免了子问题的重复求解。
递归方法,采用的是自顶向下的思想,拆分为若干子问题,但是造成了子问题的重复求解。
备忘录方法,采用的也是自顶向下的思想,但是该方法维护了一个记录子问题解的表,虽然填表动作的控制结构更像递归方法,但是的确避免了子问题的重复求解。
下面以字符串的相似度来展示一下各方法的特点:
递归:略
备忘录:
- #include <iostream>
- #include <string>
- #include <vector>
- using namespace std;
- const int N = 100;
- int dist[N][N];
- int minValue(int va, int vb, int vc)
- {
- int temp = va;
- if(vb < temp)
- temp = vb;
- if(vc < temp)
- temp = vc;
- return temp;
- }
- int distance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)
- {
- if(dist[pABegin][pBBegin]>=0)
- return dist[pABegin][pBBegin];
- if (pABegin > pAEnd)
- {
- if(pBBegin > pBEnd)
- dist[pABegin][pBBegin] = 0;
- else
- dist[pABegin][pBBegin] = pBEnd - pBBegin + 1;
- return dist[pABegin][pBBegin];
- }
- if (pBBegin > pBEnd)
- {
- if(pABegin > pAEnd)
- dist[pABegin][pBBegin] = 0;
- else
- dist[pABegin][pBBegin] = pAEnd - pBBegin + 1;
- return dist[pABegin][pBBegin];
- }
- if (strA[pABegin]==strB[pBBegin])
- {
- dist[pABegin][pBBegin] = distance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
- return dist[pABegin][pBBegin];
- }
- else
- {
- int t1 = distance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);
- int t2 = distance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);
- int t3 = distance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
- dist[pABegin][pBBegin] = minValue(t1, t2, t3)+1;
- return dist[pABegin][pBBegin];
- }
- }
- int main()
- {
- string A;
- string B;
- cin>>A;
- cin>>B;
- for (int i=0; i<N; i++)
- for(int j=0; j<N; j++)
- dist[i][j] = -1;
- cout<<distance(A, 0, A.length()-1, B, 0, B.length()-1);
- return 0;
- }
当n=0时,f(n) = 0
当n=1时,f(n) = 1
当n>1时,f(n) = f(n-1) + f(n-2)
递归算法:
- int fun(int n)
- {
- if(n <= 0)
- return 0;
- if(n == 1)
- return 1;
- return fun(n-1)+fun(n-2);
- }
备忘录方法:
- #include <iostream>
- using namespace std;
- const int N = 100;
- int f[N];
- int fun(int n)
- {
- if(f[n]>=0)
- return f[n];
- if(n == 0)
- {
- f[0] = 0;
- cout<<"0"<<endl;
- return f[0];
- }
- if(n == 1)
- {
- f[1] = 1;
- cout<<"1"<<endl;
- return f[1];
- }
- cout<<n<<endl;
- f[n] = fun(n-1) + fun(n-2);
- return f[n];
- }
- int main()
- {
- for (int i=0; i<N; i++)
- f[i] = -1;
- cout<<fun(4);
- return 0;
- }
由于计算的时候只需要前两个数即可,所以代码还可以继续优化。但是对于上述的备忘录方法貌似不能继续进行空间优化了(不知道对否,如果理解的不对请不吝赐教~)。
但是对于下面的方法(就称为遍历方法吧),还是可以继续优化的。
- #include <iostream>
- using namespace std;
- const int N = 100;
- int f[N];
- int main()
- {
- int n;
- cin>>n;
- for (int i=0; i<=n; i++)
- {
- if(i==0)
- f[i] = 0;
- else if(i==1)
- f[i] = 1;
- else
- f[i] = f[i-1] + f[i-2];
- }
- cout<<f[n];
- return 0;
- }
- #include <iostream>
- using namespace std;
- int main()
- {
- int n;
- cin>>n;
- int temp1, temp2, temp;
- for (int i=0; i<=n; i++)
- {
- if(i==0)
- temp1 = 0;
- else if(i==1)
- temp2 = 1;
- else
- {
- temp = temp1 + temp2;
- temp1 = temp2;
- temp2 = temp;
- }
- }
- cout<<temp;
- return 0;
- }
总结:从代码中可以看出来,遍历方法实际上是一种自底向上的方法,而备忘录方法是一种自顶向下的方法,也许正由于这个原因造成了备忘录方法无法进行空间优化。(待证)
动态规划算法的基本要素:
1 最优子结构性质
当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2 重叠子问题性质
动态规划算法对每个问题只解一次,将其解保存在一个表格中,当再次需要解此问题时,用常数时间查看一下结果。因此,用动态规划算法通常只需要多项式时间。
备忘录方法:
•用一个表格来保存已解决的子问题的答案,用的时候查表即可。
•采用的递归方式是自顶向下。
•控制结构与直接递归相同,区别在于备忘录方式为每个解过的子问题建立备忘录。
•初始化为每个子问题的记录存入一个特殊的值,表示并未求解。在求解过程中,查看相应记录如果是特殊值,表示未求解,否则只要取出该子问题的解答即可。
备忘录方法与动态规划和递归的区别:
1、动态规划是自低向上 ,备忘录方法是自顶向下,递归是自顶向下
2、动态规划每个子问题都要解一次,但不会求解重复子问题;备忘录方法只解哪些确实需要解的子问题;递归方法每个子问题都要解一次,包括重复子问题• 。
动态规划解矩阵连乘问题
#include<iostream>
using namespace std;
void metrixchain(int n,int p[],int **s,int **m)
{
for(int i=0;i<n;i++)
m[i][i]=0;
for(i=2;i<=n;i++) //小于等于n
{
for(int j=0;j<n-i+1;j++) //横坐标
{ int k=j+i-1; //纵坐标
m[j][k]=m[j+1][k]+p[j]*p[k+1]*p[j+1];s[j][k]=j;
for(int t=j+1;t<k;t++)
{
int u=m[j][t]+m[t+1][k]+p[j]*p[t+1]*p[k+1];
if(u<m[j][k])
{
m[j][k]=u;s[j][k]=t;
}
}
}
}
}
void Traceback(int i, int j, int ** s)
{
if (i==j||i==j-1) return;
cout<<"divide after metrix "<<s[i][j]<<endl;
Traceback(i, s[i][j], s);
Traceback(s[i][j]+1,j , s);
}
int main()
{
int p[]={30,35,15,5,10,20,25};
int **s=new int*[6];
for(int i=0;i<6;i++)
s[i]=new int[6];
int **m=new int*[6];
for( i=0;i<6;i++)
m[i]=new int[6];
metrixchain(6,p,s,m);
for( i=0;i<6;i++)
{ for( int j=i;j<6;j++)
cout<<m[i][j]<<" ";
cout<<endl;
}
Traceback(0,5,s);
return 0;
}