四、递归&记忆化

递归

一个函数反复调用自己的过程叫递归。

一个例子:
你有事找A老师,但是A老师说这事不归他管,你得找B老师,	
然后你去找了B老师,B老师说这事不归她管,你得找A老师。
这时候就形成了一个递归过程,你反复使用了“找老师”的操作,但是显而易见,这个例子的是无法解决的,因为这形成了一个递归的死循环。
另一个例子:
怎么样判断一个数是正整数?
或许我们不知道怎么定义一个正整数,但是我们知道以下两个定论:
(1) 1 是正整数
(2) 如果 n 是正整数,那 n+1 也是正整数
基于此,我们就可以判断一个数是不是正整数了。
又一个例子:

数学函数也能定义递归过程,如下是阶乘的数学表达式:
n ! = { f ( 0 ) = 1 f ( x ) = f ( x − 1 ) ∗ x       ( 1 ≤ x ≤ n ) n!= \begin{cases} f(0) = 1 \\ f(x) = f(x - 1)*x \ \ \ \ \ (1 \le x \le n) \end{cases} n!={f(0)=1f(x)=f(x1)x     (1xn)
将此公式改写成代码则如下:

	int f(int n){
		if(n == 0)return 1;
		else return f(n - 1) * n;
	}

假如我们现在调用 f (3) ,则可以抽象比喻成下面的例子:

皇上:大臣帮我算一下f(3);
大臣:知县帮我算一下f(2);
知县:师爷帮我算一下f(1);
师爷翻书一看f(0) = 1;
然后开始上报
师爷上报知县说:f(1) = 1 //f(1) = f(0) * 1
然后知县上报大臣说:f(2) = 2 //f(2) = f(1) * 2
最后大臣上报皇上说:f(3) = 6 //f(3) = f(2) * 3

可以看出递归时,上级函数处于等待下级函数“回话”的过程。虽然浅显易懂,但相对会有很多弊端。弊端将在下一个例子讲解。

最后一个例子:

众所周知斐波那契数的递推式为:
f i b ( n ) = { f i b ( 1 ) = 1 f i b ( 2 ) = 1 f i b ( n ) = f i b ( n − 1 ) + f ( n − 1 )       ( n ≥ 3 ) fib(n)= \begin{cases} fib(1) = 1\\ fib(2) = 1\\ fib(n) = fib(n - 1) + f(n - 1)\ \ \ \ \ (n \ge 3) \end{cases} fib(n)= fib(1)=1fib(2)=1fib(n)=fib(n1)+f(n1)     (n3)
转化为程序则如下:

int fib(int n){
	if(n == 1 || n == 0)return 1;
	else return fib(n - 1) + fib(n - 2);
}

过程如图,弊端十分明显:fib(3)被反复调用了两次,做了大量的无用功。

记忆化搜索

接上个例子,我们发现fib(3)被反复调用,并且fib的每一项都是固定不变的,那这样的话我们就可以把前面出现过的所有fib都记录下来,如果某一项有记录,那就直接返回有记录的一项即可,这样就可以避免反复调用同一个函数而浪费大量时间了。
我们用dp[ i ]来记录第i项的斐波那契数,dp初始赋值为-1,如果dp[ i ]有值,就直接返回dp[ i ],否则就计算并保存dp[ i ]。
如果用上一个程序,在n = 50 时就会花费大量时间,但是下面的程序计算几千几万都会很快。

#include<bits/stdc++.h>  
using namespace std;  
const int p = 1000005;  
long long dp[1000005];  
long long fib(long long n){  
	if(dp[n] != -1)return dp[n];  
	if(n == 1 || n == 0) return dp[n] = 1;  
	//这里借用了C语言的特性:赋值具有返回值  
	else {  
		return dp[n] = (fib(n - 1) + fib(n - 2))% p;  
	}  
}  
int main(){  
	int n;  
	memset(dp, -1, sizeof dp);  
	while(cin >> n){  
		cout << fib(n) << endl;  
	}  
	return 0;  
}

1、计算f(n, x)

已知 f ( x , n ) = n + ( n − 1 ) + ( n − 2 ) + . . . + 2 + 1 + x f(x,n)=\sqrt{n+\sqrt{(n-1)+\sqrt{(n-2)+\sqrt{...+2+\sqrt{1+x}}}}} f(x,n)=n+(n1)+(n2)+...+2+1+x
计算 f ( x , n ) f(x, n) f(x,n) 的值。

输入格式

输入 x x x n n n

输出格式

函数值,保留两位小数。

样例输入 #1
4.2 10
样例输出 #1
3.68

你能写出数学函数式吗?
f ( x , n ) = { 1 + x       , n = 1 n + f ( x , n − 1 )     , n ≥ 2 f(x,n) = \begin{cases} \sqrt{1 + x}\ \ \ \ \ ,n = 1\\ \sqrt{n + f(x,n -1)} \ \ \ ,n \ge2 \end{cases} f(x,n)={1+x      ,n=1n+f(x,n1)    ,n2
answer:

double func(double n, double x){
	if(n == 1){
		return sqrt(1+x);
	}
	return (sqrt(n + func(n-1, x)));
}

2、再求f(n,x)

再求 f(x,n)

已知 f ( x , n ) = x n + x ( n − 1 ) + x ( n − 2 ) + ⋮ ⋯ + x 1 + x f(x,n)=\dfrac{x}{n+\dfrac{x}{(n-1)+\dfrac{x}{(n-2)+\dfrac{\vdots}{\cdots+\dfrac{x}{1+x}}}}} f(x,n)=n+(n1)+(n2)++1+xxxxx

输入格式

第一个数是 x x x 的值,第二个数是 n n n 的值。( x x x实数 n n n 为整数)

输出格式

函数值,保留两位小数。

样例输入 #1
1 2
样例输出 #1
0.40

你能写出来吗?
$$
f(n,x) =
\begin{cases}
\dfrac{x}{1 + x}\ \ \ \ (n = 1)\
\dfrac{x}{n + f(n - 1, x)}\ \ \ \ (n \ge2)

\end{cases}
$$

double func(int n, double x){
	if(n == 1){
		return x/(1+x);
	}
	return x/(n+func(n-1, x));
}

3、小猴吃桃

一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半,又贪嘴多吃了一个;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n n n 天早上起来一看,只剩下 1 1 1 个桃子了。请问小猴买了几个桃子?

输入格式

输入一个正整数 n n n,表示天数。 n ≤ 30 n \le 30 n30

输出格式

输出小猴买了多少个桃子。

样例输入 #1
4
样例输出 #1
22

你能写出来吗?
d a y ( n ) = { 1 , n = 1 2 ∗ [   d a y ( n − 1 ) + 1   ] , n ≥ 2 day(n) = \begin{cases} 1,n =1\\ 2*[\ day(n - 1) + 1\ ],n \ge 2 \end{cases} day(n)={1n=12[ day(n1)+1 ]n2

long long func(int n){
	if(n == 1)return 1;
	else return 2*(func(n - 1) + 1);
}

4、Function

对于一个递归函数 w ( a , b , c ) w(a,b,c) w(a,b,c)

  • 如果 a ≤ 0 a \le 0 a0 b ≤ 0 b \le 0 b0 c ≤ 0 c \le 0 c0 就返回值 1 1 1
  • 如果 a > 20 a>20 a>20 b > 20 b>20 b>20 c > 20 c>20 c>20 就返回 w ( 20 , 20 , 20 ) w(20,20,20) w(20,20,20)
  • 如果 a < b a<b a<b 并且 b < c b<c b<c 就返回 w ( a , b , c − 1 ) + w ( a , b − 1 , c − 1 ) − w ( a , b − 1 , c ) w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c) w(a,b,c1)+w(a,b1,c1)w(a,b1,c)
  • 其它的情况就返回 w ( a − 1 , b , c ) + w ( a − 1 , b − 1 , c ) + w ( a − 1 , b , c − 1 ) − w ( a − 1 , b − 1 , c − 1 ) w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1) w(a1,b,c)+w(a1,b1,c)+w(a1,b,c1)w(a1,b1,c1)

这是个简单的递归函数,但实现起来可能会有些问题。当 a , b , c a,b,c a,b,c 均为 15 15 15 时,调用的次数将非常的多。你要想个办法才行。
注意:例如 w ( 30 , − 1 , 0 ) w(30,-1,0) w(30,1,0) 又满足条件 1 1 1 又满足条件 2 2 2,请按照最上面的条件来算,答案为 1 1 1

输入格式

会有若干行,每行三个数:a,b,c,
a , b , c ∈ [ − 9223372036854775808 , 9223372036854775807 ] a,b,c \in [-9223372036854775808,9223372036854775807] a,b,c[9223372036854775808,9223372036854775807] (long long)
并以 − 1 , − 1 , − 1 -1,-1,-1 1,1,1 结束。

输出格式

输出若干行,每一行格式:
w(a, b, c) = ans

样例输入 #1
1 1 1
2 2 2
-1 -1 -1
样例输出 #1
w(1, 1, 1) = 2
w(2, 2, 2) = 4

你能写出来吗?
f ( a , b , c ) = { 1 ,    i f   a   o r   b   o r   c   = 0 ; f ( 20 , 20 , 20 ) ,    i f   a   o r   b   o r   c ≥ 20 ; f ( a , b , c − 1 )   +   f ( a , b − 1 , c − 1 )   −   f ( a , b − 1 , c ) ,    i f   a < b   a n d   b < c ; f ( a − 1 , b , c )   +   f ( a − 1 , b − 1 , c )   +   f ( a − 1 , b , c − 1 )   −   f ( a − 1 , b − 1 , c − 1 ) ,     e l s e ; f(a,b,c) = \begin{cases} 1,\ \ \ if\ a \ or\ b\ or\ c\ = 0 ;\\ \\ f(20,20,20),\ \ \ if\ a \ or\ b\ or\ c\ge 20 ;\\ \\ f(a,b,c-1)\ +\ f(a,b-1,c-1)\ -\ f(a,b-1,c),\ \ \ if\ a <b\ and\ b < c ;\\ \\ f(a-1,b,c)\ +\ f(a-1,b-1,c)\ +\ f(a-1,b,c-1)\ -\ f(a-1,b-1,c-1),\ \ \ \ else; \end{cases} f(a,b,c)= 1   if a or b or c =0;f(20,20,20)   if a or b or c20;f(a,b,c1) + f(a,b1,c1)  f(a,b1,c)   if a<b and b<c;f(a1,b,c) + f(a1,b1,c) + f(a1,b,c1)  f(a1,b1,c1)    else;
以下给出部分代码:

long long func(a,b,c){
	if(a < 0 || b < 0 || c < 0)return 1;
	if(a > 20 || b > 20 || c > 20)return func(20, 20, 20);
	if(a < b && b < c)
		return func(a, b, c - 1) + func(a, b - 1, c - 1) - func(a, b - 1, c);
	else 
		return func(a-1,b,c)+func(a-1,b-1,c)+func(a-1,b,c-1)-func(a-1,b-1,c-1);
}

但是显然这个的速度是不够用的,尝试用记忆化搜索改写。

tips:可以用dp{i, j, k}记录,i, j, k,分别表示a, b, c 的状态。
  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanxinfx2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值