赞
踩
一个实际问题的各种可能的情况构成的集合常称为”状态空间“,而程序的运行则是对于状态空间的遍历,算法和数据结构则通过划分、归纳、提取、抽象来帮助提高程序遍历状态空间的效率。递推和递归就是遍历状态空间的两种基本方式。
对于一个待求解的问题,当它局限在某处边界、某个小范围或者某个特殊情况下时,其答案往往是已知的。如果能够将该解答的应用场景扩大到原问题的状态空间,并且扩展过程的每个步骤具有相似性,就可以考虑使用递推或者递归求解。
什么是递推?递归?以求阶乘n!为例。
递推:
factorial[0]=1;
for(int i=1;i<=n;i++)
factorial[i]=factorial[i-1]*i;
递归:
int factorial(int n){
if(n==0)return 1;
return factorial(n-1)*n;
}
像第一份代码,以已知的问题边界为起点向原问题正向推导的扩展方式就是递推。像第二份代码,函数自身定义自身,通过函数体实现循环,以自相似的方式重复进行的过程就被称为递归。
对于本问题来说,n!的推导路径我们已经知道了,所以直接for循环一遍就好了。但是很多时候,推导的路径难以确定,这时就需要用到递归,以原问题为起点尝试寻找把状态空间缩小到已知的问题边界的路线,再通过该路线反向回溯。
想要使用递推或者递归解决一个问题,该问题需要保证程序在每个步骤上应该面对相同种类的问题,这些问题都是原问题的一个子问题,可能仅在规模或者某些限制上有所区别,并且能够使用求解原问题的程序进行求解。
对于递归算法,有了上面的前提,可以在每次状态变化过程中执行三个操作。
每个问题可能有多个子问题,会向下递归多次,如果求解一个子问题失败,程序需要重新回到当前问题寻找其他的路线,因此把当前问题缩小为之前的子问题时所做的“对当前问题状态产生影响的事情”应该全部失效,这被称为“还原现场”。具体来说,就是再一次递归完成后,修改过的所有全局变量应该恢复为递归开始之前的状态。
枚举形式 | 状态空间规模 | 一般遍历方式 |
---|---|---|
多项式 | n k , k n^k,k nk,k为常数 | 循环(for),递推 |
指数 | k n , k k^n,k kn,k为常数 | 递归,位运算 |
排列 | n ! n! n! | 递归,C++next_permutation |
组合 | C ( n , m ) C(n,m) C(n,m) | 递归+剪枝 |
#include<iostream> #include<vector> using namespace std; vector<int>chosen; int n; void calc(int pos) { if(pos==n+1) { for(auto k:chosen) { printf("%d ",k); } printf("\n"); return ; } calc(pos+1); chosen.push_back(pos); calc(pos+1); chosen.pop_back(); return ; } int main() { cin>>n; calc(1); return 0; }
#include<iostream> #include<vector> using namespace std; vector<int>chosen; int n,m; void calc(int pos) { if(chosen.size()>m||chosen.size()+(n-pos+1)<m)return ; if(pos==n+1) { for(auto k:chosen) { printf("%d ",k); } printf("\n"); return ; } chosen.push_back(pos); calc(pos+1); chosen.pop_back(); calc(pos+1); return ; } int main() { cin>>n>>m; calc(1); return 0; }
#include<iostream> using namespace std; int n; int chosen[15]; int vis[15]; void calc(int x) { if(x==n+1) { for(int i=1;i<=n;i++) printf("%d ",chosen[i]); printf("\n"); return ; } for(int i=1;i<=n;i++) { if(vis[i])continue; chosen[x]=i; vis[i]=1; calc(x+1); vis[i]=0; } return ; } int main() { cin>>n; calc(1); return 0; }
易发现三个性质:
#include<iostream> #include<cstring> using namespace std; int t; int cnt=0,res=10; char g[10][10],dg[10][10]; int dx[5]={-1,0,0,0,1}; int dy[5]={0,-1,0,1,0}; void turn(int x,int y) { for(int i=0;i<5;i++) { int xx=x+dx[i],yy=y+dy[i]; if(xx<1||xx>5||yy<1||yy>5)continue; g[xx][yy]^=1; } return ; } int main() { cin>>t; while(t--) { res=10; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) cin>>dg[i][j]; for(int i=0;i<32;i++) { memcpy(g,dg,sizeof g); cnt=0; int r=i; for(int j=1;j<=5;j++) { if(r&1) { turn(1,j); cnt+=1; } r>>=1; } for(int j=2;j<=5;j++) { for(int k=1;k<=5;k++) { if(g[j-1][k]=='0') { turn(j,k); cnt+=1; } } } int flag=1; for(int j=1;j<=5;j++) if(g[5][j]=='0')flag=0; if(flag) { res=min(res,cnt); } } if(res>6)cout<<-1<<endl; else cout<<res<<endl; } return 0; }
acwing96.奇怪的汉诺塔
对于三塔模型:
d
[
n
]
=
2
∗
d
[
n
−
1
]
+
1
d[n]=2*d[n-1]+1
d[n]=2∗d[n−1]+1
对于本问题:
f
[
n
]
=
m
i
n
(
2
∗
f
[
i
]
+
d
[
n
−
i
]
)
f[n]=min(2*f[i]+d[n-i])
f[n]=min(2∗f[i]+d[n−i])
#include<iostream> #include<cstring> using namespace std; int dp1[20],dp2[20]; int main() { dp1[1]=1; for(int i=2;i<=12;i++) dp1[i]=2*dp1[i-1]+1; memset(dp2,0x3f,sizeof dp2); dp2[0]=0; for(int i=1;i<=12;i++) { for(int j=0;j<=i;j++) { dp2[i]=min(dp2[i],dp2[j]*2+dp1[i-j]); } cout<<dp2[i]<<endl; } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。