一、(what?)
二、(why?)
三、(how?)
四、典型例题分析:
例题1:神奇的兔子序列
输入:月数
输出:兔子数
思路:
代码1(函数递归):
#include<iostream>
using namespace std;int fib(int n)
{if(n < 1)return -1;else if(n == 1|| n == 2)return 1;elsereturn fib(n-1)+fib(n-2);
}int main()
{int n;cin>>n;cout<<fib(n);return 0;
}
代码2(数组递归):
#include<iostream>
using namespace std;int fib(int n)
{if(n < 1) return -1;int F[n+1];F[1] = 1, F[2] = 1;for(int i = 3; i <= n; i++)F[i] = F[i-1]+F[i-2];return F[n];
}int main()
{int n;cin>>n;cout<<fib(n);return 0;
}
例题2:孩子有多像爸爸——最长公共子序列
暴力搜索
举个简单的暴力搜索的
#include<iostream>
using namespace std;
int main()
{char s[7]={'A','B','C','B','A','D','B'};for(int k=0;k<=7;k++){for(int i=k;i<=7;i++){for(int j=k;j<i;j++){cout<<s[j];}cout<<endl;} }return 0;}
显示所有子串:
求其中一个子串的数组:
#include<iostream>
using namespace std;
int main()
{char s[7] = {'A','B','C','B','A','D','B'};//char ss[6] = {'B','C','B','A','A','C'};string str1[100],str2[100];int len1=0,len2=0;for(int k=0;k<=7;k++){for(int i=k;i<=7;i++){for(int q=k;q<i;q++){str1[len1]+=s[q];}len1++;} } for(int k=0;k<len1;k++){cout<<str1[k]<<endl;} return 0;}
输出:
题解代码:
#include<iostream>
#include<string>
using namespace std;const int MAX=1000;char s[MAX],ss[MAX];string str1[MAX],str2[MAX];int len1=0,len2=0;int main()
{//char s[7] = {'A','B','C','B','A','D','B'};//char ss[6] = {'B','C','B','A','A','C'};//string str1[100],str2[100];//int len1=0,len2=0;string st1,st2;cin>>st1>>st2;int i=0,j=0;while(i<st1.length()){s[i]=st1[i];i++;}while(j<st2.length()){ss[j]=st2[j];j++;}//存入第一个子串数组 for(int k=0;k<=7;k++){for(int i=k;i<=7;i++){for(int q=k;q<i;q++){str1[len1]+=s[q];}len1++;} }/* for(int k=0;k<len1;k++){cout<<str1[k]<<endl;} *///存入第二个子串数组for(int k=0;k<=6;k++){for(int i=k;i<=6;i++){for(int q=k;q<i;q++){str2[len2]+=ss[q];}len2++;} } /* for(int k=0;k<len2;k++){cout<<str2[k]<<endl;} */int temp=0,max=-1000;string answer;for(int i=0;i<len1;i++){string strr1=str1[i];for(int j=0;j<len2;j++){if(str2[j]==strr1){temp=strr1.length();if(temp>=max){max=temp;answer=strr1;}}} } cout<<answer<<endl<<max; return 0;}
结果:
*注:这并不是例题的解法,只是对暴力搜索举个例子,二者并无关联!
原理题的条件子串是从父亲的基因中取一些值并非一定连续!!!
下面,用动态规划算法解决此问题
算法设计:
图解算法:
伪代码:
Void LCSL()
{int I,j;for(I = 1;I <= len1;i++) //控制s1序列for(j = 1;j <= len2;j++) //控制s2序列{if(s1[i-1]==s2[j-1]) //字符下标从0开始{ //如果当前字符相同,则公共子序列的长度为该字符前的最长公共序列+1c[i][j] = c[i-1][j-1]+1;b[i][j] = 1;}else{if(c[i][j-1]>=c[i-1][j]) //两者找最大值,并记录最优策略来源{c[i][j] = c[i][j-1];b[i][j] = 2;}else{c[i][j] = c[i-1][j];b[i][j] = 3;}}}
}
Void print(int I, int j)//根据记录下来的信息构造最长公共子序列(从b[i][j]开始递推)
{if(i==0 || j==0) return;if(b[i][j]==1){print(i-1,j-1);cout<<s1[i-1];}else if(b[i][j]==2)print(I,j-1);else print(i-1,j);
}
代码:
#include<iostream>
#include<string>
using namespace std;
const int N = 1002;//数组最大长度
int c[N][N], b[N][N];//c:公共子序列长度。b:答案路径
char s1[N], s2[N];
int len1, len2;//动态规划查询最大子序列函数
void LCSL()
{int i, j;for (i = 1; i <= len1; i++)//控制s1序列{for (j = 1; j <= len2; j++)//控制s2序列{//动态规划开始if (s1[i - 1] == s2[j - 1])//字符下标从0开始 {//如果字符相同,公共子序列的长度为该字符前的最长公共子序列(左上角)+1 c[i][j] = c[i - 1][j - 1] + 1;b[i][j] = 1;//此情况标记为1 }else{//如果字符不相等的子序列长度 if (c[i][j - 1] >= c[i - 1][j]){//如果上面的大于左面,子序列长度为上值 c[i][j] = c[i][j - 1];b[i][j] = 2;//取上值为2 }else{//如果左大于上,取左值 c[i][j] = c[i - 1][j];b[i][j] = 3;//取左值为3 }}}}for (i = 0; i <= len1; i++){for(j = 0; j <= len2; j++){cout << c[i][j];}cout << endl;}
}//输出最优路径的函数(因为是函数递归,所以经过倒退能得到正序路径)
void print(int i, int j)//从b[i][j]开始递推
{if (i == 0 || j == 0) return;//如果有一个序列递归完了就结束递归if (b[i][j] == 1){//说明此时s1[i-1]=s2[j-1],b[i][j]的值来自c左上角 print(i - 1, j - 1);//递归去左上角 cout << s1[i - 1];}else if (b[i][j] == 2){//s1[i-1]与s2[j-1]不等,b[i][j]值来自c上 print(i, j - 1);}else{//字符不等取值来自左 print(i - 1, j);}
}int main()
{int i, j;cout << "输入字符串s1:" << endl;cin >> s1;cout << "输入字符串s2:" << endl;cin >> s2;len1 = strlen(s1);//求char型数组的长度len2 = strlen(s2);for (i = 0; i <= len1; i++){c[i][0] = 0;//初始化第一行 }for (j = 0; j <= len2; j++){c[0][j] = 0;//初始化第一列 }LCSL();//求最长子序列cout << "最长子序列长度为:" << c[len1][len2] << endl;cout << "最长公共子序列是:";print(len1, len2);return 0;
}
输出:
例题3:DNA基因鉴定——编辑距离
算法设计:
算法图解:
伪代码:
int editdistance(char *str1, char *str2)
{int len1 = strlen(str1); //计算字符串长度int len2 = strlen(str2); for(int i=0;i<=len1;i++) //当第二个串长度为0,编辑距离初始化为id[i][0]= i;for(int j=0;j<=len2;j++) //当第一个串长度为0,编辑距离初始化为jd[0][j]=j;for(int i=1;i <=len1;i++) //遍历两个字符串{for(int j=1;j<=len2;j++){int diff;//判断str[i]是否等于str2[j],相等为0,不相等为1if(str1[i-1] == str2[j-1]) //相等diff = 0 ;elsediff = 1 ;int temp = min(d[i-1][j] + 1, d[i][j-1] + 1);//先两者取最小值d[i][j] = min(temp, d[i-1][j-1] + diff);//再取最小值,//相当于三者取最小值d[i-1][j] + 1, d[i][j-1] + 1,d[i-1][j-1] + diff}}return d[len1][len2];
}
完整代码:
#include<iostream>
#include<string>
using namespace std;
const int N = 100;
char str1[N],str2[N];
int d[N][N];//d[i][j]表示的str1前i个字符的str2前j个字符的编辑距离int StrLen(char *s)//求字符串长度
{int i=0;while(s[i]!='\0'){i++; } return i;
} int min(int a,int b)
{return a<b?a:b; //返回较小值
}int editdistance(char *str1,char *str2)
{int len1=StrLen(str1);int len2=StrLen(str2); //初始化for(int i=0;i<=len1;i++){//第二个串长度为0,编辑距离初始化为i d[i][0] = i;} for(int j=0;j<=len2;j++){//第二个串长度为0,编辑距离初始化为jd[0][j] = j;}//遍历两个字符串 for(int i=1;i<=len1;i++){for(int j=1; j<=len2;j++){int diff;//判断字符是否相等,相等不需要编辑+0,不相等+1if(str1[i-1] == str2[j-1]){diff=0;}else{diff=1; } int temp=min(d[i-1][j]+1,d[i][j-1]+1);d[i][j] = min(temp,d[i-1][j-1]+diff);//连去两次两个求最小值等价于三个求最小值 }}for(int i=0;i<=len1;i++){for(int j=0;j<=len2;j++){cout<<d[i][j];}cout<<endl;}return d[len1][len2];
}
int main()
{cin>>str1>>str2;cout<<editdistance(str1,str2);return 0;
}
输入输出:
例题4:长江一日游——游艇租赁
算法设计:
算法图解:
伪代码:
void rent()
{int i,j,k,d;for(d=3;d<=n;d++) //将问题分为小规模d{for(i=1;i<=n-d+1;i++){j=i+d-1;for(k=i+1;k<j;k++) //记录每一个小规模内的最优解{int temp;temp=m[i][k]+m[k][j];if(temp<m[i][j]){m[i][j]=temp;s[i][j]=k;}}}}
}
void print(int i,int j)
{if(s[i][j]==0 ){cout << "--"<<j;return ;}print(i,s[i][j]);print(s[i][j],j);
}
完整代码:
//program 4-3
#include<iostream>
using namespace std;
const int ms = 1000;
int r[ms][ms],m[ms][ms],s[ms][ms]; //i到j站的租金
int n; //共有n个站点
void rent()
{int i,j,k,d;for(d=3;d<=n;d++) //将问题分为小规模为d{for(i=1;i<=n-d+1;i++){j=i+d-1;for(k=i+1;k<j;k++) //记录每一个小规模内的最优解{int temp;temp=m[i][k]+m[k][j];if(temp<m[i][j]){m[i][j]=temp;s[i][j]=k;}}}}
}
void print(int i,int j)
{if(s[i][j]==0 ){cout << "--"<<j;return ;}print(i,s[i][j]);print(s[i][j],j);
}
int main()
{int i,j;cout << "请输入站点的个数 n:";cin >> n;cout << "请依次输入各站点之间的租金:";for(i=1;i<=n;i++)for(j=i+1;j<=n;++j){cin>>r[i][j];m[i][j]=r[i][j];}rent();cout << "花费的最少租金为:" <<m[1][n] << endl;cout <<"最少租金经过的站点:"<<1;print(1,n);return 0;
}
输入输出:
例题5:快速计算——矩阵连乘
算法设计:
图解算法:
伪代码:
void print(int i,int j)
{if( i == j ){cout <<"A[" << i << "]";return ;}cout << "(";print(i,s[i][j]);print(s[i][j]+1,j);cout << ")";
}
完整代码:
//program 4-4
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int msize = 100;
int p[msize];
int m[msize][msize],s[msize][msize];
int n;
void matrixchain()
{int i,j,r,k;memset(m,0,sizeof(m));memset(s,0,sizeof(s));for(r = 2; r <= n; r++) //不同规模的子问题{for(i = 1; i <= n-r+1; i++){j = i + r - 1;m[i][j] = m[i+1][j] + p[i-1] * p[i] * p[j]; //决策为k=i的乘法次数s[i][j] = i; //子问题的最优策略是i;for(k = i+1; k < j; k++) //对从i到j的所有决策,求最优值,记录最优策略{int t = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];if(t < m[i][j]){m[i][j] = t;s[i][j] = k;}}}}
}
void print(int i,int j)
{if( i == j ){cout <<"A[" << i << "]";return ;}cout << "(";print(i,s[i][j]);print(s[i][j]+1,j);cout << ")";
}
int main()
{cout << "请输入矩阵的个数 n:";cin >> n;int i ,j;cout << "请依次输入每个矩阵的行数和最后一个矩阵的列数:";for (i = 0; i <= n; i++ )cin >> p[i];matrixchain();print(1,n);cout << endl;cout << "最小计算量的值为:" << m[1][n] << endl;
}
输入输出:
例题6:切呀切披萨——最优三角剖分
例题7:小石子游戏——石子合并
例题8:大卖场购物车——0-1背包问题
算法设计:
图解算法:
伪代码:
for(i=1;i<= n;i++) //计算c[i][j]for(j=1;j<=W;j++)if(j<w[i]) //当物品的重量大于购物车的容量,则不放此物品c[i][j] = c[i-1][j];else //否则比较此物品放与不放是否能使得购物车内的价值最大c[i][j] = max(c[i-1][j],c[i-1][j-w[i]] + v[i]);cout<<"装入购物车的最大价值为:"<<c[n][W]<<endl;
//逆向构造最优解
j=W;
for(i=n;i>0;i--)if(c[i][j]>c[i-1][j]){x[i]=1;j-=w[i];}elsex[i]=0;
cout<<"装入购物车的物品为:";
for(i=1;i<=n;i++)if(x[i]==1)cout<<i<<" ";
完整代码:
//program 4-7
#include <iostream>
#include<cstring>
using namespace std;
#define maxn 10005
#define M 105
int c[M][maxn]; //c[i][j] 表示前i个物品放入容量为j购物车获得的最大价值
int w[M],v[M]; //w[i] 表示第i个物品的重量,v[i] 表示第i个物品的价值
int x[M]; //x[i]表示第i个物品是否放入购物车
int main(){int i,j,n,W; //n表示n个物品,W表示购物车的容量cout << "请输入物品的个数n:";cin >> n;cout << "请输入购物车的容量W:";cin >> W;cout << "请依次输入每个物品的重量w和价值v,用空格分开:";for(i=1;i<=n;i++)cin>>w[i]>>v[i];for(i=0;i<=n;i++) //初始化第0列为0c[i][0]=0;for(j=0;j<=W;j++) //初始化第0行为0c[0][j]=0;for(i=1;i<= n;i++) //计算c[i][j]for(j=1;j<=W;j++)if(j<w[i]) //当物品的重量大于购物车的容量,则不放此物品c[i][j] = c[i-1][j];else //否则比较此物品放与不放是否能使得购物车内的价值最大c[i][j] = max(c[i-1][j],c[i-1][j-w[i]] + v[i]);cout<<"装入购物车的最大价值为:"<<c[n][W]<<endl;//逆向构造最优解j=W;for(i=n;i>0;i--)if(c[i][j]>c[i-1][j]){x[i]=1;j-=w[i];}elsex[i]=0;cout<<"装入购物车的物品为:";for(i=1;i<=n;i++)if(x[i]==1)cout<<i<<" ";return 0;
}
输入输出:
例题9:快速定位——最优二叉搜索