【入门】算法初步2 递归

目录

1、分治

2、递归

使用递归求解n的阶乘

求Fibonacci 数列的第n项。

n皇后问题。


1、分治

分治,就是把原问题划分成若干个规模较小的小问题,然后一个一个解决这些小问题,最后再合并这些小问题的解,就可以解决了这个大问题啦。下面就是官方的解释啦

①分解:将原问题分解为若干和原问题拥有相同或相似结构的子问题。

②解决:递归求解所有子问题。如果存在子问题的规模小到可以直接解决,就直接解决

③合并:将子问题的解合并为原问题的解。

我们需要注意的是:分治法分解出的子问题应当是相互独立、没有交叉的。如果存在两个子问题有相交部分,那么不应当使用分治法解决。

从广义上米说,分治法分解成的子问题个数只要大于0即可。但是从严格的定义上讲,一般把子问题个数为1的情况称为减治,而把子问题个数大于1的情况称为分治,不过通常情况下不必在意这种区别。另外,分治法作为一种算法思想,既可以使用递归的手段去实现,也可以通过非递归的手段去实现,可以视具体情况而定,一般我们都用递归啦!

下面我们用n的阶乘来介绍递归思想。

2、递归

我觉得递归就是反复调用自身函数嘛,但是每次把问题的范围缩小,直到范围缩小到可以直接得到边界数据的结果,然后再在返回路上求出对应的解,是不是看出来了递归很适合用来实现分治思想。

递归逻辑中有二个重要概念:

①递归边界

②递归调用

那就来个经典的例题吧!

题目:

使用递归求解n的阶乘

代码如下:

#include<stdio.h>
int	F(int n)
{
	if (n == 0) return 1;   //当达到递归边界F(0)时,返回F(0)==1
	else return F(n - 1) * n; //没有达到递归边界使用递归式传递下去
}
int main() {
	int n;
	scanf("%d", &n);
	printf("%d\n", F(n));
	return 0;
}

思路如下: 

①无妨令输入的 n 为3,然后调用 F(3),即求解3的阶乘。

②进入F(3)函数(第一层),判断 n ==0是否成立:不成立,因此返回 F(2)*3。注意,此时 F (2)还是未知的,因此程序会调用 F (2),等待得到 F (2)的结果后再计算 F (2)+3。

③进入 F(2)函数(第二层),判断 n ==0是否成立:不成立,因此返回F(1)*2。注意,注意此时 F (1)还是未知的,因此程序会调用 F (1),等待得到 F (1)的结果后再计算F(1)*2。
④进入 F (1)函数(第三层),判断 n ==0是否成立:不成立,因此返回 F (0)*1。注意,此时 F (0)还是未知的,因此程序会调用 F (0),等待得到 F (0)的结果后再计算F (0)*1。
⑤进入 F (0)函数(第四层),判断 n ==0是否成立:成立,因此返回1。到这里为止,得到了 F (0)=1,且不用再向更小范围进行递归,因此向上一层(第三层)返回 F(0)的结果。
⑤步骤④(第三层)由于等到了从第四层返回的 F (0),计算出了 F (1)= F (0)*1=1,并将 F (1)的结果返回上一层(第二层)。
⑥面步骤③(第二层)由于等到了从第三层返回的 F ( I ),计算出了 F (2)= F (1)*2=2,并将 F (2)的结果返回上一层(第一层)。
⑦步骤②(第一层)由于等到了从第二层返回的 F (2),计算出了 F (3)= F (2)*3=6,并将 F (3)的结果返回给了 main 函数。
至此,F(3)的递归过程结束,得到了F(3)=6并输出。在图4-2中,从进入第一层 F (3)开始,不断向下递归,到达递归边界后返回,在返回的过程中依次计算出F(1)、F(2)及 F (3),并把 F (3)返回给 main 函数调用 F (3)的地方。

(有点长,不过很好理解)

那再来看一道例题吧!(进阶一下)

题目:

求Fibonacci 数列的第n项。

Fibonacci 数列(即斐波那契数列)是足 F (0)=1, F ( I )=1. F(n)=F(n-1)+ F(n-2) ( n ≥2)的数列,数列的前几项为1,1,2,3,5,8,13,21,…。由于从定义中已经可以获知递归边界为F(0)=1和 F (1)=1,且递归式为F(n)=F(n-1)+ F(n-2) ( n ≥2) ,因此可以仿照求解 n 的阶乘的写法,

代码如下:

#include<stdio.h>
int	F(int n)
{
	if (n == 0||n==1) return 1;   //当达到递归边界
	else return F(n - 1)+ F(n - 2); //递归式
}
int main() {
	int n;
	scanf("%d", &n);
	printf("%d\n", F(n));
	return 0;
}

我们来看一下求解Fibonacci 数列的过程中会发现,这不就是使用递归实现分治法的一个简单例子吗,对给定的正整数n来说,把求解F(n)的问题分解为求解F(n-1)和F(n-2)这二个子问题,而F(0)=F(1)=1是当n很小时问题的直接解决,递归式F(n)=F(n-1)+F(n-2)则是问题的合并。

 有上面二个例子可以知道,如果实现一个递归函数,那么就要有两样东西:递归边界和递归式。其中递归边界用来返回最简单底层的结果,递归式用来减少数据规模并向下一层递归。我们一开始学我觉得画图理解比较好,慢慢的找到规律。

再来一道例题

n皇后问题。

n皇后问题是指在一个n*n的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行、同一列、同一条对角线上

如果我们用组合数的方式来枚举每一种情况(即从n平方个位置中选择n个位置),那n如果大,那我们根本吃不消啊!那我们换个思路,考虑到每行只能放置一个皇后、每列也只能放置一个皇后,那么如果把n列皇后所在的行号依次写出,那么就会是1~n的一个排序。那么我们就可以只枚举1~n的所有排列,查看每个是否合理进行了,通过计算可以发现比上一个方法好太多。

于是可以在全排列的代码基础上求解。由于当到达递归边界是生成了一个排列,所以需要在其内部判断是否为合法方案,即遍历每两个皇后,判断它们是否再同一条对角线上(不在同一行和同一列是显然的),若不是,则累计计数变量count即可。

代码如下:

#include <cstdio>
#include <cstdlib>

#define MAXN 12

int legal_rank_num = 0;

int N;		// 棋盘边长,位数
int P[MAXN];		// 排列结果
bool hashTable[MAXN];	// 散列记录

void generateP(int index) {
	// 递归边界
	if (index == N + 1) {
		// 内部判断是否满足要求
		bool flag = true;
		for (int i = 1; i <= N; i++) {
			for (int j = i + 1; j <= N; j++) {
				if (abs(i - j) == abs(P[i] - P[j])) {	// 在同一条对角线上
					flag = false;
				}
			}
		}
		if (flag == true) {
			legal_rank_num += 1;
			for (int i = 1; i <= N; i++) {
				printf("%d", P[i]);
			}
			printf("\n");
		}
		return;
	}
	// 递归式
	for (int j = 1; j <= N; j++) {
		if (hashTable[j] == false) {
			P[index] = j;
			hashTable[j] = true;

			generateP(index + 1);

			hashTable[j] = false;
		}
	}
}

int main() {

	N = 8;

	generateP(1);

	printf("\n解决方案有: %d\n", legal_rank_num);

	return 0;
}

在前述的方法中,我们先枚举出所有的情况,然后再判断每一种情况是否合法,这种方法明显不是最优化的。其实当我们在已经放置了部分的皇后之后,该部分皇后已经出现呈对角线的局部排列时,后面再怎么摆放其它棋子都是徒劳。这样,我们也没有必要再往下继续递归了,直接返回上层即可,还可以减少很多的计算量。
一般我们把这种还没有达到递归边界就直接返回上层,不再进行继续递归的做法叫做回溯法。

代码如下:

#include <cstdio>
#include <cstdlib>
#define MAXN 12
int legal_rank_num = 0;
int N;		// 棋盘边长,位数
int P[MAXN];		// 排列结果
bool hashTable[MAXN];	// 散列记录

void generateP(int index) {
	// 递归边界
	if (index == N + 1) {
		// 回溯法中能够到达递归边界的都是满足条件的
		legal_rank_num += 1;
		for (int i = 1; i <= N; i++) {
			printf("%d", P[i]);
		}
		printf("\n");
		return;
	}
	// 递归式
	for (int j = 1; j <= N; j++) {
		if (hashTable[j] == false) {
			bool flag = true;// 遍历之前的皇后,确保新皇后不会与它们形成对角线
			for (int pre = 1; pre < index; pre++) {
				if (abs(pre - index) == abs(P[pre] - j)) {
					flag = false;
					break;
				}
			}

			if (flag == true) {
				P[index] = j;
				hashTable[j] = true;

				generateP(index + 1);
				// 递归完毕,还原第j行为未占用。
				hashTable[j] = false;
			}
		}
	}
}
int main() {

	N = 8;

	generateP(1);

	printf("\n解决方案有: %d\n", legal_rank_num);

	return 0;
}


 

 这个n皇后问题其实我也还没太搞懂,惭愧啊!还是练的少啊! 哎

大家浅看一下吧!

比特社区欢迎大家加入哈

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白小白,一定发财。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值