2020年4月7日 ACM公选课 递归
递归(Recursion)就是子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,
是一种描述问题和解决问题的基本方法。
递归有两个基本要素:
⑴ 边界条件:确定递归到何时终止;
⑵ 递归模式:大问题是如何分解为小问题的。
递归算法的关键是根据递归过程建立递推关系式,然后求解这个递推关系式。
猜测技术:对递推关系式估计一个上限,然后(用数学归纳法)证明它正确。
递推算法的时间复杂度:
林大OJ 86 计算N!(简单版)
n的阶乘在n比较小时可以直接按最简单的思路写,但这里我们用递归的写法。
#include <bits/stdc++.h>
using namespace std;
long long f(int n)
{
return n==0?1:f(n-1)*n;
}
int main()
{
int n;
while(cin>>n)
printf("%lld\n",f(n));
return 0;
}
林大OJ 14 蟠桃记
分析:设A0代表第1天的桃子总数,Ai代表吃完后剩下的桃子数,则:
A0=2*(A1+1)
A1=2*(A2+1)
…………….
An-1=2*(An+1)
An=1
写代码的时候倒推回去:
#include <bits/stdc++.h>
using namespace std;
int f(int n)
{
if(n==1) return 1;
else return 2*(f(n-1)+1);
}
int main()
{
int n;
while(cin>>n){
if(n==0) break;
else cout<<f(n)<<endl;
}
return 0;
}
林大OJ 69 / 林大OJ 1762 Fibonacci
斐波那契数列在要求的项数较小时也有很多种方法,但直接用最普通的递推
F(n)=F(n-1)+F(n-2);
很容易造成超时,因为会有很多重复的计算(跑到n=20左右就跑不动了TAT),所以我们可以先用递推打表,然后再输出,省去了很多重复的计算。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
long long data[100];
data[1]=data[2]=1;
for(int i=3;i<=100;i++)
data[i]=data[i-1]+data[i-2];
while(cin>>n)
cout<<data[n]<<endl;
return 0;
}
林大OJ 1755 数的计算-递归
体现递归算法的优势的题,只要每次加上比这个数的一半小的数字之和即可,因为本题的n<=500,所以可以直接递归。
#include <bits/stdc++.h>
using namespace std;
int num=0;
int dfs(int n)
{
for(int i=1;i<=n/2;i++){
num++;
dfs(i);
}
return 0;
}
int main()
{
int n;
cin>>n;
dfs(n);
cout<<num+1<<endl;
return 0;
}
林大OJ 1756 数的计算加强版-递推
当上一题的n从500增到10000,普通的递归就不行了,所以可以先列举出几项,然后找其中的规律。
可以发现,当n为奇数时,F(n)=F(n-1);
当n为偶数时,F(n)=F(n-1)+F(n/2);
故直接写出代码:
#include <bits/stdc++.h>
using namespace std;
long long a[10001];
int main()
{
int n;
cin>>n;
a[0]=a[1]=1;
for(int i=1;i<=n;i++){
if(i&1)
a[i]=a[i-1];
else
a[i]=a[i-1]+a[i/2];
}
cout<<a[n]<<endl;
return 0;
}
林大OJ 1759 数字分段-递归
比较经典的简单递归题,主要学习递归的方法和思路。
本题是从最后往前循环,需要注意第一组的处理。
递归写法:
#include <bits/stdc++.h>
using namespace std;
int a[101];
int n,k;
int dfs(int r,int l)
{
int s=0;
for(int i=r;i>=l;i--){
s+=a[i];
if(s>k){
dfs(i,l);
cout<<i+1<<" "<<r<<endl;
return 0;
}
}
cout<<1<<" "<<r<<endl;//处理第一个区间
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
dfs(n,1);
return 0;
}
非递归数组写法:
#include <bits/stdc++.h>
using namespace std;
int b1[1001],b2[1001];
int a[1001];
int main()
{
int n,k,s=0,l,r,p=0;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
r=n;
for(int i=n;i>=1;i--){
s+=a[i];
if(s>k){
s=a[i];
l=i+1;
b1[++p]=l;
b2[p]=r;
r=i;
}
}
l=1;
b1[++p]=l;
b2[p]=r;
for(int i=p;i>=1;i--)
cout<<b1[i]<<" "<<b2[i]<<endl;
return 0;
}
林大OJ 784 白与黑-搜索
关于搜索问题以后还会单独研究,这里参考了大佬的博客。
本题思路:
- 这个题目可以描述成给定一点,计算它所在的连通区域的面积。需要考虑的问题包括矩阵的大小,以及从某一点出发向上下左右行走时,可能遇到的三种情况:出了矩阵边界、遇到‘.’、遇到‘#’。
- 设f(x, y)为从点(x,y)出发能够走过的黑瓷砖总数,则f(x,y)=1+f(x-1,y)+f(x+1,y)+f(x,y-1)+f(x,y+1).
- 这里需要注意,凡是走过的瓷砖不能够被重复走过。可以通过每走过一块瓷砖就将它作标记的方法保证不重复计算任何瓷砖。
#include <bits/stdc++.h>
using namespace std;
int w,h;
char z[21][21];
int f(int i,int j)
{
if(i<1||i>h||j<1||j>w)
return 0;
if(z[i][j]!='#'){
z[i][j]='#';
return 1+f(i,j-1)+f(i,j+1)+f(i-1,j)+f(i+1,j);
}
else
return 0;
}
int main()
{
while(cin>>w>>h){
if(w==0 && h==0) break;
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
cin>>z[i][j];
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
if(z[i][j]=='@')
cout<<f(i,j)<<endl;
}
return 0;
}