一文让你轻松写出递归(Recursion)

本文介绍了递归的基本概念,包括递归基例和递推关系,阐述了递归在简化代码和问题解决中的作用。通过计算幂的例子展示了如何寻找递推关系和构造递归函数。接着,详细解释了汉诺塔问题的递归解决方案,揭示了递归在解决复杂问题时的思维方式,并分析了递归的时间复杂度。递归在编程中是一种强大的工具,尤其在处理分治和动态规划问题时显得尤为重要。
摘要由CSDN通过智能技术生成

关于

递归是什么

简单说, 函数自己调用自己就叫递归

递归有什么用

简化代码: 只需少量代码就可描述出解题过程
简化问题: 把一个大型复杂问题转化为一个比原问题规模更小的子问题, 到子问题无需再次递归为止

递归构成

递归基例(递归出口)(bottom cases):最基本的情况, 递归到这种情况时, 不需要进一步的递归, 程序调用完成, 返回
递推关系(recurrentce relation):一个问题的结果与其子问题的结果之间的关系

递归的使用条件

1. 可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式
2. 存在一种简单情境,可以使递归在简单情境下退出

下面举一个简单的例子来演示:
假如我们要计算m的n(n>0)次幂, 即mn

显然, 下面的式子成立 (递推关系):
m n = m × m × m × . . . × m m ^ n = m\times m\times m \times ...\times m mn=m×m×m×...×m
m × m n − 1 = m × m × m × . . . × m m \times m ^ {n-1} =m\times m\times m\times ...\times m m×mn1=m×m×m×...×m
显然, 当n=1的时候 (递归出口)
m 1 = m m ^ 1=m m1=m

	/**
	 * 输入底数和指数计算幂
	 * 
	 * @param m 底数
	 * @param n 指数
	 * @return m^n
	 */
	public static int pow(int m, int n) {
		if (n == 1) // 递归出口
			return m;
		return m * pow(m, n - 1); // 递推关系
	}

如何使用递归

写出递归最关键的就是找出递推关系递归出口

递归出口往往是显而易见的, 就是那些最基本的情况, 比如n=0或n=1时的情况

比较复杂的是如何找出递推关系

如何找递推关系?

回顾上个计算幂的例子, 其中的递推关系是:
m × m n − 1 = m × m × m × . . . × m m \times m ^ {n-1} =m\times m\times m\times ...\times m m×mn1=m×m×m×...×m
所以,代码中:

 m * pow(m, n - 1); 

可见, 在写递归代码的时候, 我们不需要考虑 n-1或n+1需要做什么, 只需要指定当前的n需要做什么

下面举一个著名的递归例子:
汉诺塔(Hanoi),有A, B, C三根柱子和若干块大小不同的圆盘
现在, 把圆盘从下面开始按大小顺序重新摆放在另一根柱子上(从A移到C)。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘
在这里插入图片描述
这个问题该如何思考呢?

我们先想只有一个圆盘的情况(递归基例):

是不是直接把圆盘从A移到C就可以了😉

现在再想, A上有两个圆盘的情况:

1. 可以先把小的那个圆盘从A柱子移到B柱子上面:  A->B
2. 再把较大的那个圆盘移到从A移到C上面:  A->C
3. 然后把放在B上的那个圆盘放到C上:  B->C

再想, A上有3个圆盘的情况(从上到下依次增大):

1. 把最上面的1移到C   A->C
2. 把中间的2移到B     A->B
3. 把C上的移到B      C->B   此时我们已经把最后一个盘上面的所有盘移到了B	
4. 把最下层的3移到C   A->C	
5. 把B上的1移到A 		B->A 相当于把两个盘从B移到C
6. 把B上的2移到C 		B->C
7. 把A上的1移到C 		A->C 

在这里插入图片描述
我们观察上面两种情况, 不难发现步骤其实只有3步(假设有n个盘,从上到下编号1~n):

1. 把上面n-1个盘从A移到B
2. 把第n个盘从A移到C
3. 把n-1个盘从B移到C

也即

	/**
	 * 汉诺塔
	 * 
	 * @param a 起始柱
	 * @param b 辅助柱
	 * @param c 目标柱
	 * @param n 移动的次数
	 */
	public static void hannoi(char a, char b, char c, int n) {
		if (n == 1) {// 只有一个盘的情况
			System.out.println(a + " -> " + c);
			return;
		}
		hannoi(a, c, b, n - 1);// 把前n-1个从a->b
		System.out.println(a + " -> " + c);// 最后一个从a->c
		hannoi(b, a, c, n - 1);// 最后把前n-1个从b-c
	}

运行代码, 当n=10时, 需要移动 1023次

所以这种递归的问题, 在数据较大的时候, 用人脑去模拟势必会显得大脑不太够用

实际上在分析一个递归的问题时, 我们不需要跳进函数里面, 只需要指定这个函数的功能(如, 将圆盘从起始柱移到目标柱), 然后对当前的数据进行操作(如,System.out.println(a + " -> " + c); 第n个圆盘的起始柱和目标柱 )

应注意到, 递归调用函数的时候参数是在变化的:
起始柱, 目标柱, 辅助柱在变, 次数n也在变
找出递推关系的关键就是找到变化的量, 将它们作为参数传入函数

递归时间复杂度分析

就汉诺塔问题而言:

参数n代表问题的规模, 耗时用T(n)表示, 规模为n-1时的耗时为T(n-1)
当规模等于1时, 只需要移动一步即可, 耗时为O(1)
当规模大于1时, 先将n-1个圆盘从A移动到B, 耗时T(n-1)
将剩下的1个移动到C, 耗时O(1)
将B上的n-1个移动到C, 耗时T(n-1)
故:

T ( n ) = { O ( 1 ) , n = 1 2 T ( n − 1 ) + O ( 1 ) , n > 1 T(n)=\begin{cases}O(1) ,n=1 \\2T(n-1)+O(1) ,n>1 \end{cases} T(n)={O(1),n=12T(n1)+O(1),n>1

当n>1时: 令c=O(1)
T ( n ) = 2 T ( n − 1 ) + c T(n)=2T(n-1)+c T(n)=2T(n1)+c
T ( n ) = 2 ( 2 T ( n − 2 ) + c ) + c T(n)=2(2T(n-2)+c)+c T(n)=2(2T(n2)+c)+c
T ( n ) = 2 ( 2 ( 2 T ( n − 3 ) + c ) + c ) + c T(n)=2(2(2T(n-3)+c)+c)+c T(n)=2(2(2T(n3)+c)+c)+c
T ( n ) = 2 n − 1 c + 2 n − 1 c − c T(n)=2^{n-1}c+2^{n-1}c-c T(n)=2n1c+2n1cc
T ( n ) = 2 n c − c T(n)=2^nc-c T(n)=2ncc
O ( n ) = 2 n O(n)=2^n O(n)=2n

所以如果要计算移动的次数, n个圆盘就需要移动2n-1

设计递归下降语法分析器的过程可以分为以下几个步骤: 1. 定义文法:首先需要定义要分析的文法,通常使用 BNF(巴科斯-诺尔范式)或类似的形式来表示。例如,要分析算术表达式,可以使用以下 BNF 形式: ``` expression ::= term | expression ('+' | '-') term term ::= factor | term ('*' | '/') factor factor ::= '(' expression ')' | number number ::= digit+ ``` 2. 将文法转换为更易于处理的形式:一般来说,需要将文法转换为消除左递归和提取左公因式等形式,以便更容易编递归下降语法分析器。例如,将上述文法消除左递归和提取左公因式后,得到以下形式: ``` expression ::= term expression' expression'::= ('+' term | '-' term)* term ::= factor term' term' ::= ('*' factor | '/' factor)* factor ::= '(' expression ')' | number number ::= digit+ ``` 3. 编递归下降语法分析器:根据转换后的文法,编递归下降语法分析器。一般来说,递归下降语法分析器的每个非终结符对应一个函数,负责解析该非终结符所描述的语法结构,并返回解析的结果。在编函数时,需要考虑如何处理可能存在的语法错误、如何处理嵌套的语法结构等问题。 4. 测试和调试:编递归下降语法分析器后,需要进行测试和调试,以确保它能够正确地解析各种输入,并且能够正确地处理各种边界情况。 总的来说,设计递归下降语法分析器的过程需要理解文法、转换文法、编递归下降语法分析器以及测试和调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SVIP_Quanw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值