递归

递归是一种不断调用自身函数的一个过程,就像是套娃一样,一层套着一层,很多人初学时都会被绕晕,但其实只要抓住递归的两个重要概念—递归边界和递归式,学起来就会轻松许多,下面我们由浅入深来讲讲递归。

一、n!

首先从最简单的例子 n ! n! n! 讲起, n ! n! n!可以用一个循环来解决,为了让大家更好理解递归,我们用递归来实现 n ! n! n! n ! n! n! 的计算是 n × ( n − 1 ) × ⋯ × 1 n\times(n-1)\times\cdots\times1 n×(n1)××1, 它写成递推式是 n ! = n × ( n − 1 ) ! n! = n\times(n-1)! n!=n×(n1)! ,这样就把规模为 n n n的问题转化为规模为 n − 1 n-1 n1 的问题了,如果 F ( n ) F(n) F(n) n ! n! n!的话,那么递归式就是 F ( n ) = F ( n − 1 ) × n F(n)=F(n-1)\times n F(n)=F(n1)×n了,如果 n n n的规模一直缩小,最终会到 F ( 0 ) F(0) F(0),即 0 ! = 1 0!=1 0!=1,这样我们就可以将 F ( 0 ) = 1 F(0) = 1 F(0)=1作为递归边界,那么用递归实现 n ! n! n!的整体思路就是在函数 F ( n ) F(n) F(n)里面调用 F ( n − 1 ) F(n-1) F(n1),直到 F ( 0 ) F(0) F(0),然后再一层一层返回答案,用代码实现如下(以 3 ! 3! 3!为例)

#include <cstdio>
using namespace std;

int F(int n){
	if(n == 0) return 1; //递归边界 
	return n*F(n-1); //递归式 
} 

int main(){
	printf("%d", F(3));
	return 0;
}

我们简单讲一下代码,首先主函数调用函数 F ( 3 ) F(3) F(3),判断 n ≠ 0 n\neq0 n=0,执行下一条语句,返回 3 × F ( 2 ) 3\times F(2) 3×F(2),由于 F ( 2 ) 是 未 知 的 F(2)是未知的 F(2),这时就会继续调用 F ( 2 ) F(2) F(2),以此类推,直到 1 × F ( 0 ) 1\times F(0) 1×F(0),此时达到递归边界 n = 0 n=0 n=0,条件成立,返回 F ( 0 ) = 1 F(0)=1 F(0)=1,然后返回 F ( 1 ) = 1 × F ( 0 ) = 1 F(1)=1\times F(0)=1 F(1)=1×F(0)=1,依次向上返回结果,用一个恒等式表示就是 F ( 3 ) = 3 × F ( 2 ) = 3 × 2 × F ( 1 ) = 3 × 2 × 1 × F ( 0 ) = 3 × 2 × 1 × 1 F(3)=3\times F(2)=3\times2\times F(1)=3\times 2\times 1\times F(0)=3\times 2\times 1\times 1 F(3)=3×F(2)=3×2×F(1)=3×2×1×F(0)=3×2×1×1正向就是函数的调用过程,逆向就是结果的返回过程。

二、Fibonacci数列的第n项

第二个经典的例子就是Fibonacci数列的第 n n n项,Fibonacci是指0,1,1,2,3,5,8 ⋯ \cdots 这样的自然数列,他的递推定义式是 F ( 0 ) = 0 , F ( 1 ) = 1 , F ( n ) = F ( n − 1 ) + F ( n − 2 ) ( n ≥ 2 , n ∈ N ∗ ) F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N^*) F(0)=0F(1)=1,F(n)=F(n1)+F(n2)n2nN 从定义中我们可以看出递归边界是 F ( 0 ) = 0 , F ( 1 ) = 1 F(0)=0,F(1)=1 F(0)=0F(1)=1,递归式是 F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n)=F(n - 1)+F(n - 2) F(n)=F(n1)+F(n2),根据上面 n ! n! n!我们可以写出代码

#include <cstdio>
using namespace std;

int F(int n){
	if(n == 0) return 0; //递归边界 
	if(n == 1) return 1; //递归边界
	return F(n-1)+F(n-2); //递归式 
} 

int main(){
	printf("%d", F(3));
	return 0;
}

用恒等式来表示就是 F ( 3 ) = F ( 2 ) + F ( 1 ) = ( 1 + 0 ) + F ( 1 ) = 1 + 1 F(3)=F(2)+F(1)=(1+0)+F(1)=1+1 F(3)=F(2)+F(1)=(1+0)+F(1)=1+1当然用递归实现Fibonacci数列会伴随者大量重复的计算,比如计算完 F ( 4 ) F(4) F(4)时,会计算 F ( 3 ) F(3) F(3),而 F ( 3 ) F(3) F(3)本身也要再计算一次,我们可以用数组把已经计算好的用数组记录下来,这样可以避免很多重复计算,动态规划可以更好解决这个问题。

递归通用模板

通过以上两个例子,我们可以总结出递归的模板,如下

int fuction(所需参数){
	-----------------
	|    递归边界    |
	-----------------
	|     递归式     |
	-----------------
}

通过上面两个递归的总结,我们只需要找出所需参数、递归边界和递归式就可以了,而所需参数往往与递归边界有着密切关联,一般是作为条件或者满足条件后执行代码用到的参数。

三、Hanoi

一根柱子称原柱上,套有n个盘子,依次从小到大地从上往下地排序着,需要将这n个盘子移动一个目标柱上,要求在移的过程中,大的盘子不可以在小的盘子上面。可以使用一根辅助柱子;

这题的递归边界显然就是当只有一个盘子的时候,直接从原柱移动到目标柱,接下去就是寻找递归式了,当有n个盘子时,需要将n-1个盘子移动到临时柱,然后将原柱的盘子移动到目标柱,最后将临时柱的n-1个盘子移动到目标柱,而想将n-1个盘子移动到目标柱,就要重复以上步骤,所以递归式有三个步骤:

  1. 把n-1个盘从源柱移动到临时柱上;
  2. 把原柱上剩余的1个盘移动到目标柱上;
  3. 把临时柱上的n-1个盘移到目标柱上。

根据上述可知所需参数就是一个整型来记录盘子的个数,还有三个字符型来记录三根柱子的移动

#include <cstdio>
using namespace std;

void Hanoi(int n, char a, char b, char c){
	//递归边界 
	if(n == 1){
		printf("%c-->%c\n",a,c);
		return;
	}
	Hanoi(n-1,a,c,b);//将n-1个盘子从A柱(原柱)移动到B柱(临时柱) 
	printf("%c-->%c\n",a,c);//A柱(原柱)只剩下最后一个盘子,直接移动到C柱(目标柱) 
	Hanoi(n-1,b,a,c);//将B柱(临时柱)上的盘子移动到C柱(目标柱) 
}
int main(){
	Hanoi(3,'A','B','C');
	return 0;
}

四、全排列问题

将1~n进行排列,一共有几种排列?

这个排列组合的问题我们高中就学过了,其中一种方法就是填充法,从n个数选一个出来填充在第一个位置,然后从剩下的数中再选一个出来填充在第二位,以此类推,直至将数全部填充完,那如果用代码呢,那就要用到循环了,从1开始依次填充数字,直至所有数字都填充完毕,用index表示当前填充的位数,那么递归边界就是当index等于n+1的时候。那递归式呢,我们用一个数组来存放填充的数,那我们怎么知道这个数是否已经填充过了呢,这时候就需要有一个数组来记录这个数是否已经填充过了。然后在填充过程中判断这个数是否已经被填充了,有就跳过,没有就填充,然后填充下一位,直到所有的数都被填充完,所需的参数就是下标index。代码如下:

#include <cstdio>
using namespace std;

const int maxn = 20;
int n, p[maxn], hashTable[maxn] = {};

void Permutation(int index){
	//递归边界 
	if(index == n+1){
		for(int i = 1; i <= n; i++){
			printf("%d", p[i]);
		}
		printf("\n");
		return;
	}
	//递推式,有就跳过,没有就填充,然后填充下一位
	for(int i = 1; i <= n; i++){
		if(!hashTable[i]){
			p[index] = i;
			hashTable[i] = 1;
			Permutation(index+1);
			hashTable[i] = 0;
		}
	}
}

int main(){
	n = 3;
	Permutation(1); //从1开始填充
	return 0;
}

五、八皇后

在8×8格的 国际象棋 上摆放8个 皇后 ,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

这一题与全排列的思想是一样的,可以使用二维的数组来记录皇后是否放置,这样我们枚举的时候就需要 C n 2 n C_{n^2}^n Cn2n,仅仅八皇后就需要4426165368次了,根据题目任意两个皇后都不能处于同一行、同一列,也就是说一行只能放一个皇后,我们不妨用数组的下标表示皇后所在的行数,数组内容表示皇后所在的列数,然后用一个数组记录这一列是否放置了皇后,与上题类似,递归的边界就是index等于n+1,递归式也是一样的,唯一的差别就是同一斜线不能放置两个皇后,所以现在我们只需要解决皇后不在同一斜线上就可以了。
4 3 0 2 0 0 1 ∗ 1 2 3 4 \begin{array}{r|r|r|r|r|}\hline 4&&&&\\ \hline 3&&&&0\\ \hline 2&0&&0&\\ \hline 1&&*&&\\ \hline &1&2&3&4\\ \end{array} 43210120304
我们在(1,2)放了一个皇后*,那么0的位置就不能放皇后了,观察*和0,我们可以发现两个所在行坐标相减会等于所在的列坐标相减,我们找出这个条件就可以开始写代码了。

#include <cstdio>
#include <cmath>
using namespace std;

int hashTable[10] = {}, queen[10], count = 0;

void EightQueen(int index){
	//递归边界,n=8
	if(index == 9){
		//打印棋盘
		for(int i = 1; i <= 8; i++){
			for(int j = 1; j <= 8; j++){
				if(queen[i] == j) printf("1 ");
				else printf("0 ");
			}
			printf("\n");
		}
		printf("\n");
		count++;
		return;
	}
	//递归式
	for(int i = 1; i <= 8; i++){
		//遍历以放置的皇后,判断要放置的皇后与之前的皇后是否在同一斜线上
		int flag = 1;
		for(int j = 1; j < index; j++){
			if(abs(queen[j]-i) == index-j){
				flag = 0;
			}
		}
		if(!hashTable[i] && flag){
			queen[index] = i;
			hashTable[i] = 1;
			EightQueen(index+1);
			hashTable[i] = 0;
		}
	}
}

int main(){
	EightQueen(1);
	printf("%d", count);
	return 0;
}

如果有错误或者有更好的建议欢迎私信我!

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Leslie_Hollow

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

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

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

打赏作者

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

抵扣说明:

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

余额充值