时间复杂度
为防止递归的无休止调用,在递归函数中要及时返回,这就是结束条件的作用。我们应当看到,在所有的递归函数中都有一个终止递归的条件判断
斐波纳契数列的递推式为:
F(n)= 1 n=1 || n=2
F(n-1)+F(n-2) n>2
<1>从数学上讲,上面式子与下面式子等价
F(n)= 1 n=1 || n=2
F(n+1) - F(n-1) n>2
但是我们能写成下面的递归函数吗?
int f(int n)
{
if(n==1 || n==2) return 1;
else return f(n+1)-f(n-1);
}//分析函数异常的原因。
简要分析,按上面递归式展开,有:
F(n)= F(n+1) - F(n-1)
=[F(n+2)-F(n)]-[F(n)-F(n-2)]
也就是说F(n)在递归展开后,又重新回到了F(n),这样导致永远无法递归退出。暂时我们可以定义其为“递归回路”,即a处结果建立在b处结果基础上,按递推式却发现b处结果又建立在a处结果基础之上。
这种特征的递归是比较容易碰到的,其参数在右侧递归中既有>n的也有<n的,即递归参数不是单调的往一个方向变化。这种情况我们要学会分析、识别【它不能直接使用后面介绍的dp算法,如果能改进成不是回路型的,则可以考虑dp】。
- 消除“递归回路”一个例子 例如下图所示:m*n方格中摆放着价值不等的宝贝(价值可正可负),从左上角到达右下角的所有可能路线中,能捡到宝贝的最大值是多少?注:每个格子只能走一遍,并且走到的格子宝贝一定会被捡起。
假设宝贝价值存放在数组a[M][N]中,用f(i,j)表示从a[i][j]出发到达右下角a[m-1][n-1]能捡到的最大价值和,调用f(0,0)即可,尝试着分析下面<1><2>的关于f(i,j)的递推关系式。
<1>如果只能从当前格子向右或向下走到相邻格。 //这种情况不存在“递归回路”,因为只要走出去了,永远都不会走回去。
<2>如果只能从当前格子向上、向下或向右走到相邻格。(选做)
//这种情况存在“递归回路”,但可以想办法增加递归个数来消除递归回路,有兴趣可以思考尝试,也可以文件夹中“百度之星”的一道题目解题思路。
<3>如果从当前格子能向上、向下、向左、向右走到相邻格。 //这种情况存在的“递归回路”无法消除,最终只能采用搜索型算法。
qsort/sort
动态规划
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int n,i,j,k,len[1005]={0},value[1005]={0},dp[1006]={0};
scanf("%d %d",&n,&k);
for(i=1;i<=k;i++)
{
scanf("%d",&len[i]);
}
for(i=1;i<=k;i++)
{
scanf("%d",&value[i]);
}
for(i=1;i<=n;i++)
{
for(j=len[i];j<=n;j++)
{
//此处用到了完全背包的公式;
dp[j]=max(dp[j],dp[j-len[i]]+value[i]);
}
}
printf("%d",dp[n]);
return 0;
}
原文链接:https://blog.csdn.net/qq_43788669/article/details/108985146
整数拆分
求解数字和为sum的方法数问题
long solve(){
for(int i = 0; i <= n; ++i)
{
dp[i][0] = 1;
}
for(int j = 1; j <= sum; ++j)
{
dp[0][j] = 0;
}
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= sum; ++j)
{
if(a[i] > j)
{
dp[i][j] = dp[i-1][j];
}
else
{
dp[i][j] = dp[i-1][j] + dp[i-1][j-a[i]];
}
}
}
return dp[n][sum];
}
求解资源分配问题
void Plan()
{
int maxf, maxj;
for (int j = 0; j <= n; j++)
dp[0][j] = 0;
for (int i = 1; i <= m; i++)
{
for (int s = 1; s <= n; s++)
{
maxf = 0;
maxj = 0;
for (int j = 0; j <= s; j++)
{
if ((v[i][j] + dp[i - 1][s - j] >= maxf))
{
maxf = v[i][j] + dp[i - 1][s - j];
maxj = j;
}
}
dp[i][s] = maxf;
pnum[i][s] = maxj;
}
}
}
求解编辑距离问题
void solve()
{
int i, j;
for (i = 1; i <= a.length(); i++)
dp[i][0] = i;
for (j = 1; j <= b.length(); j++)
dp[0][j] = j;
for (i = 1; i <= a.length(); i++)
{
for (j = 1; j <= b.length(); j++)
{
if (a[i - 1] == b[j - 1])
dp[i][j] = dp[i - 1][j - 1];
else
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
求解会议安排问题
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
#define MAX 101
struct NodeType
{
int b; //开始时间
int e; //结束时间
int length; //订单的执行时间
};
bool cmp(const NodeType &a,const NodeType &b)
{ //用于排序的运算符重载函数
return a.e<b.e; //按结束时间递增排序
}
int n; //订单个数
NodeType A[MAX]; //存放订单
int dp[MAX]; //动态规划数组
int pre[MAX]; //pre[i]存放前驱订单编号
void solve();
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>A[i].b>>A[i].e;
for (int i=0; i<n; i++)
A[i].length=A[i].e-A[i].b;
solve();
cout<<dp[n-1]; //结果在这个单元中,则递归模型调整成f(n)形式
return 0;
}
/* 请在这里填写答案 */
void solve() //从函数参数、返回值形式看,显然只能写成循环型写法
{//f(i)结果存入dp[i],按依赖关系,显然从左往右计算
sort(A,A+n-1,cmp);//A[0]~A[n-1]按结束时刻递增 排序
int i,k;
for(i=0; i<=n-1; i++) //从左往右
{
for(k=i-1;k>=0;k--)
if(A[k].e <= A[i].b) break; //a[k]是可以安排在A[i].b时刻之前的且序号最大的活动
if(k<0) dp[i] = max(dp[i-1] , A[i].length); //安排A[i]后,已经没有其它可以安排的活动了
else dp[i] = max(dp[i-1] , A[i].length + dp[k]);
}
}
兔子繁殖
int F1[MAX],G1[MAX];//对应f(n),g(n)的存储
int g1(int n)//根据g(n)等价的递推关系式而写
{
if(n<1) return 0;//异常
if(G1[n]) return G1[n];//如果G[n]已经计算过,直接返回结果
if(n==1) return G1[n]=1;//保存结果并返回
else return G1[n]=g1(n-5)+g1(n-4)+g1(n-3)+g1(n-2);
}
int f1(int n)//求f(n)
{
int k;
if(n<1) return 0;//异常
if(F1[n]) return F1[n];//如果F[n]已经计算过,直接返回结果
for(k=n-4;k<=n;k++)
F1[n] += g1(k); //对应f(n)=∑g(k) ,n-4<=k<=n
return F1[n];
}
int G2[MAX]={0};//
int g2(int n)//根据g(n)等价的递推关系式而写
{
int i;
if(n<1) return 0;//异常
for(i=1;i<=n;i++)
if(i==1) G2[i]=1;
else
{
for(int j=i-5;j<=i-2;j++)
if(j>0) G2[i] +=G2[j];//g[i]=g[i-5]+g[i-4]+g[i-3]+g[i-2]
//i,j都表示月份,i值可能为2,3,4,5,里面有j<1情况
}
return G2[n];
/* 按g(n)常规写法的代码如下
for(i=1;i<=n;i++)
if(i==1) G2[i]=1;
else if(i==2) G2[i]=0;
else if(i==3) G2[i]=1;
else if(i==4) G2[i]=1;
else if(i==5) G2[i]=2;
else
{//处理i>=6
G2[i]=G2[i-5]+G2[i-4]+G2[i-3]+G2[i-2];
//for(int j=i-5;j<=i-2;j++)
//if(j>0) G2[i] +=G2[j];//i>=6时,if(j>0)可以省
}
*/
}
int F2[MAX]={0};
int f2(int n)//求f(n)
{
int i;
if(n<1) return 0;//异常
for(i=n-4;i<=n;i++)
F2[n] += G2[i]; //对应f(n)=∑g(k) ,n-4<=k<=n
//此处G2[i]也可以改为调用了g2(i),因为g2算的结果已经放在全局G2[]中,所以可以直接引用已经算好的值
return F2[n];
}
//G3[6]为f3,g3共享,存放近6个月新生兔子对数
int G3[6];//压缩存储写法:只用存储g(n-5)~g(n)这6个值
int f3(int n)
{
int i,s=0;
if(n<1) return 0;//异常
if(n>5) n=5; //如果n>5,直接将G3[0]~G3[4]累加返回即可
for(i=0;i<n;i++)
s += G3[i]; //对应f(n)=∑g(k) ,n-4<=k<=n
return s;
}
int g3(int n)
{
int i,j;
memset(G3,0,6*sizeof(int));
G3[0]=1;
G3[1]=0;
G3[2]=1;
G3[3]=1;
G3[4]=2; //初始化前5个月新生兔子数,类似于斐波那契数列初始时f1=1;f2=1;
for(i=6;i<=n;i++)//求第i个月新生兔子数,存放在G3[5]中
{
G3[5]=G3[0]+G3[1]+G3[2]+G3[3];
for(j=0;j<5;j++) G3[j]=G3[j+1];//覆盖已经无用的G3[0],即第i个月~第i-4个月的新生兔子数依次存放在G3[4]~G3[0]中
}
//当for结束后,则第n个月~第n-4个月的新生兔子数依次存放在G3[4]~G3[0]中
if(n<5) return G3[n-1];
else return G3[4];
}
杨辉三角
递归型写法
int a[N][N]; //均初始化为0了
int f1(int n,int k)
{
If(k<0 || n<k) return 0; //异常语句在最前面
If(a[n][k]) return a[n][k]; //该语句不能在最前面的原因?
If(k==0 || k==n) return a[n][k]=1;
else return a[n][k]=f1(n-1,k)+f1(n-1,k-1);//注意这里是递归调用
}
可以在输入n后对n做出限制
if(n>=50) return 1; // n太大时组合数会超出int范围
非递归写法
二维数组中,只用到了主对角线的下半部分,所以函数为
Int f2(int n,int k)
{
Int I,j,b[N][N]={0}; //与f1函数的存储分开,互不干扰
for(i=0;i<=n;i++)
for(j=0;j<=i;j++)
if(j==0 || j==i) b[i][j]=1;
else b[i][j]= b[i-1][j]+b[i-1][j-1];
return b[n][k];
}
写法三://压缩存储写法,结果在b1[],b2[]数组中
//压缩版本1
Int f3(int n,int k)
{
Int I,j,b1[N],b2[N]; //与f1,f2函数的存储分开,互不干扰
Int *p1=b1, *p2=b2, *p; //p1是上一行,p2是下一行
for(i=0;i<=n;i++)
{
for(j=0;j<=i;j++)
{
if(j==0 || j==i) p2[j]=1;
else p2[j]=p1[j]+p1[j-1];
}
p=p1; p1=p2; p2=p; //两个指针交换
}
return p1[k]; //for结束后,p1是最后一行
}
//压缩版本2
Int f3(int n,int k)
{
Int I,j,b1[N],b2[N]; //与f1,f2函数的存储分开,互不干扰
for(i=0;i<=n;i=i+2)//一次算2行
{
for(j=0;j<=i;j++)//先算奇数行b1[ ]
{
if(j==0 || j==i) b1[j]=1;
else b1[j]=b2[j]+b2[j-1];
}
for(j=0;j<=i+1;j++)//后算偶数行b2[ ]
{
if(j==0 || j==i) b2[j]=1;
else b2[j]=b1[j]+b1[j-1];
}
}
If(n%2) return b2[k]; //b2[]存储了n=1,3,5,7…行的组合数
else return b1[k]; //b1[]存储了n=0,2,4,6…行的组合数
}
//压缩版本3
Int f3(int n,int k) //压缩成一个数组
{
Int i,j,a[N]; //
for(i=0;i<=n;i++)//循环体中计算C(i,j)
{
for(j=i;j>=0;j--)//
{
if(j==0 || j==i) a[j]=1;
else a[j]=a[j]+a[j-1];
}
}
return a[k]; //a[k]即为C(n,k)
}
一副扑克牌(两张鬼牌除外)任取k张牌,不计花色
整数2019分解成不同质数之和,有多少种方法
回溯、分枝限界
1~n的全排列
int X[100],used[100],n, cnt=0; //键盘输入的n小于100
int xianjie(int k, int i)//判断X[k]能否取i
{
if(used[i] >0) return 0; //used[i]>0时表示数字i已经被使用过
//if(cnt>=20) return 0; //此剪枝可选用:只输出前20个排列结果
return 1;
}
void f(int k)
{ int i;
if(k-1==n) { 输出++cnt: 输出X[1]~X[n]; }
else for(i=1;i<=n;i++)
if(xianjie(k,i))
{
X[k]=i;
used[i]++; //数字i已经被使用的次数+1,从而在递归f(k+1)里面used[i]都是增加1之后的值
f(k+1); //遍历 x[k]取i 处的一颗子树
used[i]--;//f(k+1)结束后,准备遍历旁边一棵子树(即x[k]取i+1 处的子树),x[1]~x[k-1]没有变化,
//仅X[k]换成 i+1了,所以在旁边这棵子树中数字i的使用次数应-1
}
}
void main(void)
{
cout<<"Enter n:";
cin>>n;
f(1);
}
n皇后的结果
int X[100],n; //键盘输入的n小于100
int a[100],b[200],c[200]; //初始值均为0
//b[]记录所有45度对角线上是否有皇后,c[]记录135度方向
int xianjie(int k, int i)//判断X[k]能否取i,即第k行皇后能否摆在i列
{ int j;
if(a[i] //i列已经有皇后了
|| b[k+i-1] //(k,i)位置45度方向已经有皇后了
|| c[k-i+n]) //(k,i)位置135度方向已经有皇后了
return 0;
return 1;
}
void f(int k)
{ int i;
if(k-1==n) 输出X[1]~X[n];//也可以改进成输出二维矩阵
else for(i=1;i<=n;i++)
if(xianjie(k,i)) //检验k行皇后能否摆在i列
{
X[k]=i;
a[i]=1; b[k+i-1]=1; c[k-i+n]=1;
f(k+1);
a[i]=0; b[k+i-1]=0; c[k-i+n]=0; //状态量还原
}
}
void main(void)
{
cout<<"Enter n:";
cin>>n;
f(1);
}
输出由数字0,1,3,4,6,7,9构成的所有能够被9整除、数字允许重复、十位数>=个位数的、 m位奇数(1<m<100,由键盘输入)、而且9的出现次数不少于m/3。
int X[100],m; //键盘输入的m小于100
int a[]={0,1,3,4,6,7,9};
int xianjie(int k, int i)//判断X[k]能否取a[i]
{ int j,sum=0,count9=0;
if(k==1 &&a[i]==0) return 0; //最高位数字X[1]不能等于0
if(k==m) //判断个位数字能否等于a[i]
{
if(X[m-1]<a[i]) return 0; //十位数比个位数小,不允许
if(a[i]%2==0) return 0; //a[i]不是奇数
if((sum+a[i])%9) return 0; //该数不能被9整除
}
if(a[i]!=9)
{
if(count9+ ( m-k) < m/3) return 0; //9的次数不可能达到m/3个
}
return 1;
}
void f(int k)
{ int i;
if(k-1==m) 输出X[1]~X[m];
else for(i=0;i<=6;i++)
if(xianjie(k,i)) //检验数字X[k]能否取用a[i]
{
X[k]=a[i];
sum=sum+a[i];
if(a[i]==9) count9++;
f(k+1);
sum=sum-a[i]; //递归调用结束后,状态量对称还原
if(a[i]==9) count9--;
}
}
void main(void)
{
cout<<"Enter m:";
cin>>m;
f(1);
}
子集的元素之和为S
int a[100]= {0,9,7,5,3,2,1}; //本程序中不用a[0],解其它集合时可以补写初始化函数
int X[100] , n=6,S=15;
int sumS=0, leftS=27; //leftS初值为集合元素之和。
int xianjie(int k, int t)//判断X[k]能否取t,即集合元素a[k]能否划分到子集t
{
if(t==1 && sumS+a[k]>S) return 0;//如果a[k]划分到子集1导致其和大于S
//上面语句是X[k]取1的约束逻辑,下面代码是X[k]取0的约束逻辑
if(t==0 && sumS+leftS-a[k]<S) return 0;//在X[k]待定时leftS中包含了a[k]
return 1;
}
void f(int k)
{ int i;
if(k-1==n) //到叶子节点的标志
{ if(sumS==S) //如果子集1的元素和为S,则输出该子集
{
cout<<endl<<"{";
for(i=1;i<=n;i++)
if(X[i]==1) cout<<a[i]<<", ";
cout<<"}";
}
}
else for(i=0;i<=1;i++)
if(xianjie(k,i))
{ X[k]=i;
if(i==1) sumS=sumS + a[k];
leftS =leftS - a[k];
f(k+1);
if(i==1) sumS=sumS - a[k]; //共享状态量必须还原
leftS =leftS + a[k];
}
}
void main(void)
{
f(1);
}
1-n表达式
#include<stdio.h>
char a[]=" +-*/";
int x[100],n=9,cnt=0,used[100];//used[0]~used[4]记录运算符次数
/*在1~n(=9)的数字之间任意加入加、减、乘、除或不加,
输出所有可能的表达式
还可以增加约束:
至少3个加,至少3个乘,不能用除,乘不能连续出现3个
分析.本质就是从0~4任取8个(允许重复取)进行全排列
所以回溯函数的框架如f(k)函数,上面4个约束由
jianzhi(k,i)函数控制实现。
*/
void output()
{
int i;
printf("\n%d:",++cnt);
for(i=1;i<n;i++)
printf("%d%c",i,a[x[i]]);
printf("%d",n);
}
int jianzhi(int k,int i)
{
int j;
//不允许连续出现3个乘
if(i==3 && k>2 && x[k-1]==3 && x[k-2]==3)
return 0;
//至少3个加
if(i!=1 && used[1]+8-k<3) return 0;
//至少3个乘
if(i!=3 && used[3]+8-k<3) return 0;
//隐含剪枝逻辑:加与乘 出现次数和 >= 6
if(i!=1 && i!=3 && used[1]+used[3]+8-k<6)
return 0;
//不能用除
if(i==4) return 0;
return 1;
}
void f(int k)//x[1]~x[k-1]
{
if(k>8) output();
else for(int i=0;i<=4;i++)
if(jianzhi(k,i))
{
x[k]=i;
used[i]++;
f(k+1);
used[i]--;
}
}
int main()
{
//scanf("%d",&n);
f(1);
return 0;
}
分枝限界
迷宫
回溯
#include<iostream>
using namespace std;
int mg[9][9]=
{
{1,1,1,1,1,1,1,1,1},
{1,0,0,1,0,0,1,0,1},
{1,0,0,1,1,0,0,0,1},
{1,0,1,0,1,1,0,1,1},
{1,0,0,0,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1}
};
int n,a,b,c,d,Min,step;
int offset[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int jianzhi(int x,int y,int i)
{
int x1=x+offset[i][0],y1= y+offset[i][1];
if(x1<0 || x1>8 || y1<0 || y1>8) return 0;
if(mg[x1][y1]) return 0;
if(step>Min) return 0;
return 1;
}
void f(int x,int y)
{//回溯最大的缺点,运算量难以控制
if(x==c && y==d)
{
if(step-1<Min) Min=step-1;
}
else for(int i=0;i<4;i++)
if(jianzhi(x,y,i))
{ int x1=x+offset[i][0],y1= y+offset[i][1];
mg[x1][y1] = step;
step++;
f(x1,y1);
mg[x1][y1] = 0;
step--;
}
}
int main()
{
cin>>n;
while(n--)
{
cin>>a>>b>>c>>d;
Min=100;
step=1;
f(a,b);
cout<<Min<<endl;
}
return 0;
}
广搜
#include<iostream>
#include<memory.h>
#include<queue>
using namespace std;
int mg[9][9]=
{
{1,1,1,1,1,1,1,1,1},
{1,0,0,1,0,0,1,0,1},
{1,0,0,1,1,0,0,0,1},
{1,0,1,0,1,1,0,1,1},
{1,0,0,0,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1}
};
int n,a,b,c,d,m[9][9];
int offset[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct node{ int x,y,step;};//起点到(x,y),已经走了step步骤
int jianzhi(node e,int i)
{ int x1,y1;
x1 = e.x+offset[i][0];
y1 = e.y+offset[i][1];
if(x1<0 || x1>8 || y1<0 || y1>8) return 0;
if(m[x1][y1] || mg[x1][y1]) return 0;//(x1,y1)为已跳过、障碍物,m[][],mg[][]
return 1;
}
int main()
{
int i;
node e,e1;
cin>>n;
queue<node>Q;
while(n--)
{
cin>>a>>b>>c>>d;
e.x = a; e.y = b; e.step = 0; //根节点
m[a][b]=1;
Q.push(e);
memset(m,0,sizeof(m));
while(!Q.empty())
{
e=Q.front();
Q.pop();
if(e.x==c && e.y==d) break;
for(i=0;i<4;i++)
if(jianzhi(e,i))
{
e1.x = e.x + offset[i][0];
e1.y = e.y + offset[i][1];
e1.step = e.step +1;
m[e1.x][e1.y]=1;
Q.push(e1);
}
}
if(e.x ==c && e.y ==d) cout<<e.step<<endl ;
else cout<<"无法到达"<<endl;
while(!Q.empty()) Q.pop();
}
return 0;
}
A星
#include<iostream>
#include<memory.h>
#include<queue>
#include<algorithm>
using namespace std;
int mg[9][9]=
{
{1,1,1,1,1,1,1,1,1},
{1,0,0,1,0,0,1,0,1},
{1,0,0,1,1,0,0,0,1},
{1,0,1,0,1,1,0,1,1},
{1,0,0,0,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,1,0,0,1},
{1,1,0,1,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1}
};
int n,a,b,c,d,m[9][9];
int offset[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct node{
int x,y,step,len;
bool operator < (const node &a) const
{
return step+len > a.step+a.len; //小的优先排队头
}
};
//起点(a,b)到(x,y),已经走了step步骤,到(c,d)至少还要走len步
int jianzhi(node e,int i)
{ int x1,y1;
x1 = e.x+offset[i][0];
y1 = e.y+offset[i][1];
if(x1<0 || x1>8 || y1<0 || y1>8) return 0;
if(m[x1][y1] || mg[x1][y1]) return 0;//(x1,y1)为已跳过、障碍物,m[][],mg[][]
return 1;
}
int main()
{
int i;
node e,e1;
cin>>n;
priority_queue<node>Q;
while(n--)
{
cin>>a>>b>>c>>d;
e.x = a; e.y = b; e.step = 0; e.len = abs(a-c)+abs(b-d);//根节点
m[a][b]=1;//(a,b)点已经进入队列
Q.push(e);
memset(m,0,sizeof(m));
while(!Q.empty())
{
e=Q.top();
Q.pop();
if(e.x==c && e.y==d) break;
for(i=0;i<4;i++)
if(jianzhi(e,i))
{
e1.x = e.x + offset[i][0];
e1.y = e.y + offset[i][1];
e1.step = e.step +1;
e1.len = abs(e1.x-c)+abs(e1.y-d);//评估代价
m[e1.x][e1.y]=1;//已经进入队列标志
Q.push(e1);
}
}
if(e.x ==c && e.y ==d) cout<<e.step<<endl ;
else cout<<"无法到达"<<endl;
while(!Q.empty()) Q.pop();
}
return 0;
}