递归4之递归的利弊

Posted zsQgqdsd1002

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归4之递归的利弊相关的知识,希望对你有一定的参考价值。

递归的利弊



前言

前面经过对递归的了解与学习,让我处理问题多了一种思考方式,递归确实是一种很不错的处理问题的方式,但是不是所有的问题都可以用递归来处理,也不是所有的问题都适合用递归处理,今天我们就探讨一下递归在处理一些问题方面的利弊。


一、递归是什么?

回顾一下递归到底是什么,递归就是让程序不断调用自身,最后达到我们预期的目标的一个技巧。在一些复杂的问题中,递归可能只需要少量的代码就可以描绘出解题过程,大大减少了计算的时间,绝对算得上是一个优点。

递归的主要思考方式就在于:大事化小,把一个复杂的问题简单化,找到其中的规律,然后用少量的代码来描绘这个问题的过程,最终达到解决问题的目的。

同时递归还有两个必要条件:
1.一定要有一个限制条件,当满足这个限制条件的时候递归就停止了,不再继续,因为如果没有限制条件,程序就会一直调用自身,不断的开辟新的空间和内存,最后会导致堆栈溢出或者程序崩溃。
2.每一次递归调用之后要越来越接近1里的限制条件,原因也和前面说的一样,如果离限制条件越来越远了那就等于没有限制条件。

那么递归在处理这一类问题时,就真的没有一点缺点吗?下面我将以斐波那契数列为例,分别用递归和非递归的方式来实现,然后进行对比。

二、递归与非递归处理斐波那契数列

1.递归

具体的思路和方法可以看我之前的文章,这里就不赘述了,直接贴代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Fib(int n) {
	if (n <= 0){
	return -1;
	}
	if (n == 1 || n == 2) {
		return 1;
	}
	return Fib(n - 1) + Fib(n - 2);
}
int main() {
	int n ;
	scanf("%d", &n);
	long long ret = Fib(n);
	printf("%lld", ret);
	return 0;
}

2.非递归

非递归实现斐波那契数列,首先我们创建一个函数Fib,然后当n == 1时和n == 2时返回1,当n <= 0时我们返回一个-1来表示输入有误,当n > 3 的时候我们可以创建三个变量来分别表示第 n - 2项,第n - 1项以及第n项。然后我们创建一个循环体,让在到达第n项之前不断地循环,令第n项=第n - 2项+第 n - 1项,随后把第n - 1项的值赋给第n - 2项,把第n项的值赋给第n - 1项即可。下面是代码的实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Fib(n) {
	long long f1 = 1;
	long long f2 = 1;
	long long f3 = 0;
	if (n <= 0) {
		return -1;
	}
	if (n == 1 || n == 2) {
		return 1;
	}
	for (int i = 3; i <= n; i++) {
		f3 = f1 + f2;
		f1 = f2;
		f2 = f3;
	}
	return f3;
}
int main() {
	int n;
	scanf("%d", &n);
	long long ret = Fib(n);
	printf("%lld", ret);
	return 0;
}

非递归的做法使用变量来记录中间的值,保存中间的运算结果然后再拿来使用,也是非常的方便。


3.对比

首先呢就是代码量,显然递归比非递归的代码量要少很多,看上去十分的方便,可是真的如此吗,我们来对比一下运行结果。

在开始的运行过程中,好像并没有什么差异,递归和非递归的运算速度都差不多,那么我们输入一个n=50来看看结果,首先是非递归:

运算的结果很快就得到了在这里插入图片描述
然后我们来看一下递归:

可以看到,在输入50之后,下面是一片漆黑,只有个光标一直在闪,这是什么情况?这其实并不是出bug了,是因为程序还没有运行出结果,程序处于计算之中,反正我运行了10分钟还没出来。那为什么会出现这种情况呢?这也就是今天要说的递归在处理这一类有重复运算的问题时的弊端了。

我们现在给前面的递归程序加一个全局变量count来记录一下某一个中间值的重复运算次数,我这里计算的是Fib(3)的运算次数,当n == 3的时候,count++。

int count = 0;
long long Fib(int n) {
	if (n == 1 || n == 2) {
		return 1;
	}
	if (n == 3) {
		count++;
	}
	return Fib(n - 1) + Fib(n - 2);
}
int main() {
	int n ;
	scanf("%d", &n);
	long long ret = Fib(n);
	printf("%lld\\n", ret);
	printf("count = %d", count);
	return 0;
}

我们来看一下运行结果:
在这里插入图片描述
当n == 40的时候,我的Fib(3)就已经重复计算了快四千万次了,而且每次n + 1,重复运算可不是也+1这么简单,可能是以几何速度往上飙升的。在这个问题上,无论是时间还是空间,显然都是非递归的方法更胜一筹,也就是说递归并不能完美的解决所有问题。

总结

递归法固然是解决问题很好的一种思路和方法,但是以我目前所学到的知识来看,对于这种牵扯到重复运算的问题上(可能还有其他一些问题但是我还没学到)来讲,递归在基数较大的情况下,不是一个很好的选择,所以建议大家遇到这种问题的话最好还是采用非递归的方法,可以想一想怎么递归来开拓自己的思路,但是这种情况下我不太建议去使用递归。

以上就是我对递归处理这一类重复运算问题时存在的问题的一些看法和建议,如果有可以优化和指正的地方,欢迎大佬私信我讨论。

以上是关于递归4之递归的利弊的主要内容,如果未能解决你的问题,请参考以下文章

Java基础之方法的调用重载以及简单的递归

Java基础入门五)之方法以及递归算法

Leetcode之深度遍历递归与回溯法汇总

一对多和递归关系 - 强制设置值

算法之递归

递归逆序的使用