王晓东著《计算机算法设计与分析》第五版章节代码整理,加入了自己的理解(持续更新中)
第2章 递归与分治策略
P11 例2-1阶乘函数
#include<stdio.h>
#include<stdlib.h>
int factorial(int n){
if(n>1)
return n*factorial(n-1);
else
return 1;
}
int main(){
printf("输入数:");
int n=0;
scanf("%d",&n);
printf("阶乘为:%d",factorial(n));
}
P12 例2-2fibonacci函数
#include<stdio.h>
#include<stdlib.h>
int fibonacci(int n){
if(n<=1)
return 1;
else
return fibonacci(n-1)+fibonacci(n-2);
}
int main(){
printf("输入数:");
int n=0;
scanf("%d",&n);
printf("fibonacci数为:%d\n",fibonacci(n));
}
P12 例2-3Ackerman函数
#include<stdio.h>
#include<stdlib.h>
int Ackerman(int n,int m){
if(n==1 && m==0)
return 2;
else if(n==0 && m>1)
return 1;
else if(n>=2 && m==0)
return n+2;
else if(n>=1 && m>=1)
return Ackerman(Ackerman(n-1,m),m-1);
}
int main(){
printf("输入两个数:");
int n=0,m=0;
scanf("%d %d",&n,&m);
printf("Ackerman函数为:%d",Ackerman(n,m));
}
第3章 动态规划
P47 3.1矩阵连乘问题
void matrixMultiply(int **a,int **b,int **c,int ra,int ca,int rb,int cb){
if(ca!=rb)//前面的A矩阵的行数不等于后面的B矩阵的列数
error("矩阵不可乘");
for(int i=0;i<ra;i++){//i<行数ra
for(int j=0;j<cb;j++){//j<列数cb
int sum=a[i][0]*b[0][j];//先求新数组的第一个元素
for(int k=1;k<ca;k++)
sum+=a[i][k]*b[k][j];//C矩阵的第i行第j列的元素等于A矩阵第i行中所有元素与B矩阵第j列中对应元素的乘积之和
c[i][j]=sum;//将sum赋值给相乘后的数组C的元素
}
}
}
P49 3.1.3计算最优值
//用动态规划算法解决,可依据其递推式以自底向上的方式进行计算
//在计算过程中,保存已解决的子问题答案,而在后面需要是只要简单查一下
//从而避免大量的重复计算,最终得到多项式时间的算法(时间复杂度小于等于n^2)
void MatrixChain(int *p,int n,int **m,int **s){//输入参数存储在p数组,最优值数组m
//s为一个n*n的二维整型数组,表示断开的位置(分为两个矩阵的断点处)
for(int i=1;i<=n;i++)
m[i][i]=0;//m为n*n的二维整型数组,表示每个子问题的最优值,即m[i][j]表示矩阵链Ai...j的最小乘次数。
for(int r=2;r<=n;r++){
for(int i=1;i<n-r+1;i++){
int j=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
//计算矩阵链m[i][j]的最小乘次数,同时记录断点位置s[i][j]
for(int k=i+1;k<j;k++){//s[i][j]表示使得矩阵链Ai...j分为两个最小矩阵链之和,断开的位置k
//当长度为k的左右两个矩阵链分别对应m[i][k]和m[k+1][j]时,计算得到当前断点下的乘运算次数t
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j]){//如果t比已知的最小值m[i][j]还小,则更新m[i][j](最优解)和s[i][j](最佳断点位置)的值
s[i][j]=k;
}
}
}
}
}
P50 3.1.4构造最优解
void Traceback(int i,int j,int **s){//按算法MatrixChain计算出的断点矩阵s指示的加括号方式输出计算A[i:j]的最佳计算次序
if(i==j)
return;
Traceback(i,s[i][j],s);
Traceback(s[i][j]+1,j,s);
cout<<"Multiply A "<<i<<","<<s[i][j];
cout<<"and A"<<(s[i][j]+1)<<","<<j<<endl;
}//要输出A[1:n]的最优计算次序只要调用Traceback(1,n,s)即可
P51 3.2.2重叠子问题
//在用递归算法自顶向下解决问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算
//动态规划算法对每个子问题只解一次,然后将其解保存在一个表格中
//当再次需要解该问题时,只是简单地用常数时间查看一下结果
int RecurMatrixChain(int i,int j){
if(i==j)//检查是否只有一个矩阵
return 0;
int u=RecurMatrixChain(i,j)+p[i-1]*p[i]*p[j];//计算当前的矩阵链的最小乘法运算次数
s[i][j]=i;
for(int k=i+1;k<j;k++){
int t=RecurMatrixChain(i,k)+RecurMatrixChain(K+1,j)+p[i-1]*p[k]*p[j];
if(t<u){
u=t;
s[i][j]=k;
}
}
return u;//返回当前矩阵链的最小乘法运算次数u
}
P53 3.2.3备忘录方法
//记忆化搜索优化的矩阵连乘动态规划算法
int MemoizedMatrixChain(int n,int **m,int **s){//m记录子问题的最优值,s为最优解的矩阵链
for(int i=1;i<=n;i++){
for(int j=i;i<=n;j++)
m[i][j]=0;//对m[][]数组初始化
return LookupChain(1,n);//调用LookupChain函数,将1到n的矩阵链分解为子问题求解
}
}
int LookupChain(int i,int j){
if(m[i][j]>0)//判断是否已经求过子问题
return m[i][j];//如果是,则直接返回最优解
if(i==j)//只有一个矩阵
return 0;
int u=LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++){//枚举中间断点k,将其分解为i到k和k+1到j两个子问题
int u=Lookup(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];//p[]数组存储了子矩阵的维度
if(t<u){
u=t;//记录最优值
s[i][j]=k;//记录最优值对应的断点
}
}
m[i][j]=u;
return u;
}
P55 3.3.2计算最优值
//求解最长公共子序列(LCS)的动态规划算法
void LCSLength(int m,int n,char *x,char *y,int **c,int **b){//m和n是两个字符串的长度
//c[][]是最长公共子序列长度,b[][]是最长公共子序列长度和两个字符串的匹配情况(1表示匹配,2表示取x字符,3表示取y字符)
int i,j;
for(i=1;i<=m;i++)
c[i][0]=0;
for(i=1;i<=n;i++)
c[0][i]=0;
//将第0行和第0列全部赋值为0,因为长度为0的字符串没有公共子序列
for(i=1;i<=m;i++){
for(k=1;j<=n;j++){
if(x[i-1]==y[i-1]){
c[i][j]=c[i-1][j-1]+1;
b[i][j]=1;//最长公共子序列包含了这个字符
}//如果不相同,说明这两个字符不可能同时存在于最长公共子序列中,需要在x[0:i-2]和y[0:j-2]中寻找最长的公共子序列
//这时,可以比较c[i-1][j]和c[i][j-1],将其较大值作为c[i][j]的值,并将对应的情况记录在b[i][j]中
else if(c[i-1][j]>=c[i][j-1]){
c[i][j]=c[i-1][j];
b[i][j]=2;
}//如果是c[i-1][j]大,则说明最长公共子序列不包含x[i-1],因此b[i][j]为2,表示取x[i-1]字符
else{
c[i][j]=c[i][j-1];
b[i][j]=3;
}//否则,最长公共子序列不包含y[j-1],b[i][j]为3,表示取y[j-1]字符
}
}
}
P56 3.3.4构造最长公共子序列
//通过回溯记录的LCS最优路径,构造出最长公共子序列的函数
void LCS(int i,int j,char *x,int **b){//i和j分别表示两个字符串的末尾位置
if(i==0||j==0)
return;
if(b[i][j]==1){//当前字符匹配,属于最长公共子序列的一部分
LCS(i-1,j-1,x,b);//递归到b[i-1][j-1],输出x[i-1],即当前匹配的字符
cout<<x[i-1];
}
else if(b[i][j]==2)//说明当前字符没有匹配在公共子序列中,需要递归到b[i-1][j]
LCS(i-1,j,x,b);//即在第一个字符串中向前找到一个字符,与第二个字符串中的当前字符匹配
else//说明当前字符也没有匹配在公共子序列中,需要递归到b[i][j-1]
LCS(i,j-1,x,b);//即在第二个字符串中向前找到一个字符,与第一个字符串中的当前字符匹配
}
P56 3.4.3最大子段和问题的动态规划算法
int MaxSum(int n,int *a){//利用Kadane算法求解最大子段和问题
int sum=0,b=0;//sum记录最大子段和
for(int i=1;i<=n;i++){
if(b>0)
b+=a[i];
else
b=a[i];
if(b>sum)
sum=b;
}
return sum;
}
P57 3.4.1最大子段和问题的改进算法
int MaxSum(int n,int *a,int& besti,int& bestj){//Kadane算法,将时间复杂度降到O(n^2)
int sum=0;
for(int i=1;i<=n;i++){
int thissum=0;
//从第0个元素开始逐一遍历数组a,记录当前的最大子段和sum和当前的子段和thissum
for(int j=i;j<=n;j++){
thissum+=a[j];
if(thissum>sum){//如果thissum比sum大
sum=thissum;//更新sum
besti=i;//记录对应的起点besti(即i)
bestj=j;//记录终点bestj(即j)
}
}
}
return sum;
}
P57 3.4.1最大子段和问题的简单算法
int MaxSum(int n,int *a,int& besti,int& bestj){//暴力枚举,用于求解数组a中的最大子段和,算法的时间复杂度为O(n^3)
int sum=0;
for(int i=1;i<=n;i++){//遍历所有可能的起点i
for(int j=i;j<=n;j++){//便利所有可能的终点j
int thissum=0;
for(int k=i;k<=j;k++)//遍历了i到j范围内所有元素,以求出i到j的子段和thissum
thissum+=a[k];
if(thissum>sum){//如果thissum大于目前已经求出的最大子段和sum
sum=thissum;//更新sum
besti=j;
bestj=j;
//记录下对应的起点besti和终点bestj
}
}
}
return sum;
}
P59 3.4.2最大子段和问题的分治算法
int MaxSubSum(int *a,int left,int right){//分治算法的实现求解最大子段和问题
int sum=0;
if(left==right)//左left和右边界right相等
sum=a[left]>0?a[left]:0;
else{
int center=(left+right)/2;//通过分治的方式不断缩小问题规模,最终求得区间[left,right]的最大子段和
int leftsum=MaxSubSum(a,left,center);//左子段
int rightsum=MaxSubSum(a,center+1,right);//右子段
int s1=0;
int lefts=0;
for(int i=center;i>=left;i--){
lefts+=a[i];
if(lefts>s1)
s1=lefts;
}
int s2=0;
int rights=0;
for(int i=center+1;i<=right;i++){
rights+=a[i];
if(rights>s2)
s2=rights;
}
sum=s1+s2;
if(sum<leftsum)
sum=leftsum;
if(sum<rightsum)
sum=rightsum;
}
return sum;
}
int MaxSum(int n,int* a){//接受数组a的长度n和指向数组a的指针
return MaxSubSum(a,1,n);//调用了MaxSubSum函数求解最大子段和问题。
}
P60 3.4.3最大子段和问题与动态规划算法的推广
int MaxSum2(int m,int n,int **a){//MaxSum2函数的总时间复杂度为O(n^3)
int sum=0;
int *b=new int[n+1];//定义一维数组b,用于计算二维数组中从上到下某些行的元素和。
for(int i=1;i<=m;i++){
for(int k=1;k<=n;k++)
b[k]=0;
for(int j=i;j<=m;j++){
for(int k=i;k<=n;k++)
b[k]+=a[j][k];
int max=MaxSum(n,b);//调用一个MaxSum函数求出b数组的最大子段和
if(max>sum)
sum=max;
}
}
return sum;
}
P70 3.8.2递归计算最优值
void MNS(int C[],int n,int **size){//二维数组size用来表示函数Size(i,j)的值
for(int j=0;i<C[1];j++)//第一上端点在[1][j]之前都是0
size[1][j]=0;
for(int j=C[1];j<=n;j++)//第一上端点在[1][j]后都为1
size[1][j]=1;
for(int i=2;i<n;i++){
for(int j=0;j<C[i];j++)//j<C[i-1],出现交叉打架,不能选择当前线
size[i][j]=size[i-1][j];//size[i][j]值等于正上方的值
for(int j=C[i];j<=n;j++)//j>C[i-1],没有交叉了,可以选择当前线
size[i][j]=max(size[i-1][j],size[i-1][C[i]-1]+1);
//这时需要选择,是选择当前线还是不选择,选择标准是选择的线路数量要求最大
//前为选用当前线,后为不选用
}
size[n][n]=max(size[n-1][n],size[n-1][C[n]-1]+1);
//数组的最后一个值,即为整个线路中可选择的线路的最大数量
}
P71 3.8.3构造最优解
void Traceback(int C[],int **size,int n,int Next[],int& m){
int j=n;
m=0;
for(int i=n;i>1;i--){
if(size[i][j]!=size[i-1][j]){
Next[m++]=i;//上端点m向后进一
j=C[i]-1;//下端点j向前进一
}
if(j>=C[1])
Net[m++]=1;
}
}
第4章 贪心算法
P96 4.1活动安排问题
void GreedySelector(int n,int s[],int f[],bool A[]){
A[1]=true;//用集合A来存储所选择的活动,A[1]为已选项
int j=1;
for(int i=2;i<=n;i++){//从A[2]开始挑选活动
if(s[i]>=f[j]){//待选择活动的起始时间s[i],目前已选择的最后一个活动的结束时间f[j],作比较
j=i;//将活动i加入选择队列中
}
else
A[i]=false;//表示不选择活动i
}
}
P99 4.1背包问题
void Knapsack(int n,float m,float v[],float w[],float x[]){//贪心算法解决背包问题(非0-1背包)
Sort(n,v,w);//Sort函数,排序各个物体的单位体积下的价值(价值v/质量w)
int i;
for(i=1;i<=n;i++)//n为所有物品的总大小,用x[i]=0表示没被装入背包,x[i]=1表示被装入背包了
x[i]=0;
float c=M;//c被赋初值为原始背包大小
for(i=1;i<=n;i++){
if(w[i]>c)//背包满了
break;
x[i]=1;//表示这个单位体积的物品被全部装入背包
c-=w[i];//现在背包的大小c要被减去装入物品占据的大小
}
if(i<=n)
x[i]=c/w[i];//x[i]表示第i个物品的所占体积在当前规定背包最大容量M的条件下被装入的比例
//比如x[i]=0.5,表示第i个物品的一半被放入背包,另一半没有放入
}