栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

初学C语言:对于递归的一些见解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

初学C语言:对于递归的一些见解

前言:本人为C语言初学者,学识尚浅,研究程度存在很大的局限性,眼界很窄。以下所有观点仅代表个人见解和思路,各位游刃有余的前辈可以给予批评和指正!各位与鄙人同路的学子可相互探讨、发表看法,交换观点!

递归释义(个人理解):在函数中调用函数自身

递归的必要条件:1.递归必须存在限制条件,杜绝死递归

                             2.每次递归后要保证逼近限制条件,递归存在结束点

递归的特点:用少量代码完成多次类似的复杂运算

貌似递归听起来是很高大上的东西,给人一种很厉害的感觉。但众所周知,每次函数的调用就会占用栈区空间,当递归到很深的程度时,迎来了递归的致命伤:栈溢出。

#include 

void fun(int i)
{
    if(i > 0)
        fun(i - 1);
}

int main()
{
    fun(10000);

    return 0;
}

首先,这串代码已经具备了两个必要的递归条件:

1.if(i > 0) 存在限制条件

2.fun(i - 1); 每次递归都在像限制条件靠近

但如果让代码跑起来,则得到了下面的结果:

代码直接挂掉了,如果我们F10调试一下,会发现问题:

非常明显的看到:Stack overflow -- 栈溢出 

所以仅仅满足两个必要条件是不够的,这里通过一个简单的例子说明了递归不能太深,不然会导致栈溢出的问题。

当然,我们说递归用少量代码来完成复杂的计算,那么,递归一定可以使代码简化而实现同样的运算效果吗?在斐波那契数列中,我们得到了答案。

#define _CRT_SECURE_NO_WARNINGS 1

#include 

int Fib(int n) 
{
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fib(n - 1) + Fib(n - 2);
	}
}

int main()
{
	int num = 0;
	int ret = 0;
	scanf("%d",&num);

	ret = Fib(num);
	printf("ret = %dn", ret);

	return 0;
}

以上代码即通过递归来实现斐波那契数求解,但是当我们需要求的位数足够大时,往往程序需要执行几分钟,CPU也处于满负荷状态,说明CPU并没有偷懒,那么罪魁祸首究竟是?

于是我们来模拟一些它的计算流程:

        可以看到对于一个斐波那契数的求解,需要求的数呈树状图形式,为2的指数次增长,不知道是否大家还记得“J”型增长曲线。用红圈框出来的部分即是重复求解的部分,越往下,重复求解的越多,如果我们对代码进行一些小修改,看看他究竟算了多少次第三个斐波那契数,那么是这样的:

#define _CRT_SECURE_NO_WARNINGS 1

#include 
int count = 0; //声明全局变量计算一下重复个数 

int Fib(int n)
{
	if (n == 3) //检索第三个斐波那契数被计算了多少次
	{
		count++;
	}
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fib(n - 1) + Fib(n - 2);
	}
}

}

int main()
{
	int num = 0;
	int ret = 0;
	scanf("%d",&num);

	ret = Fib(num); - 这是递归计算法

	printf("ret = %dn", ret);
	printf("第三个斐波那契数被计算了:%d次n", count);

	return 0;
}

假设计算第30个斐波那契数,运行结果如下:

不言则明,递归在计算斐波那契数时完全不占优势,尽管它可以根据公式把代码简化到一定程度,但效率比迭代低得多。

故,如果我们用迭代的思路,可以轻松求解:

#define _CRT_SECURE_NO_WARNINGS 1

#include 

int Fib_loop(int n)
{
	int a = 1, b = 1,c = 0, i = 0;
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		for (i = 3; i <= n; i++)
		{
			c = a + b;
			a = b;
			b = c;
		}
		return c;
	}

}

int main()
{
	int num = 0;
	int ret = 0;
	scanf("%d",&num);

	ret = Fib_loop(num); //这是迭代计算法

	printf("ret = %dn", ret);

	return 0;
}

 尽管我算第10000个斐波那契数,也可以瞬间得到答案(答案是错误的,但是验证了迭代的效率之高)

这是一些递归需要避雷的地方,当然,它的优点显而易见,只是在这里我不赘述,例如整数的拆分打印等等,我仅列出此代码供参考:

#define _CRT_SECURE_NO_WARNINGS 1

#include 

void print(unsigned int n)
{
	if (n > 9) //1.递归必须存在限制条件
	{
		print(n / 10); //2.每次递归后要保证向限制条件接近
	}
	printf("%d ", n % 10);
}

int main()
{
	unsigned int num = 0;
	printf("请输入一个正整数:");
	scanf("%u", &num);

	print(num);

	return 0;
}

接下来,我们讨论了这么多,是时候说一说递归的普遍思路和方法了。

以上代码我们可以知道,如果在知道公式的情况下,比如斐波那契数列:F(n) = F(n -1) + F(n - 2)

n >=2 , F(n) = 1 n< 2;完完全全就可以套公式到代码中,究其原因,还是因为我们知道了其实现原理,因为斐波那契数总是等于它前两项的和,我们发现了普遍规律。

例如如果我们想用递归求解n的k次方,如何找到它的普遍规律呢?n的k次方可以写成许多种形式,比如n*n^k-1, n*n^k-2,于此,我们也发现了它的普遍规律,每一次我们都可以剥离一个n出来,直到n的0次方。n的k次方,说白了也就是n个k相乘,我们要学会拆分整体为一个个相同的个体,n的k次方就完美的展示了这一点!

故递归的终极思路就是:化大为小,拆整为分

在整数的拆分打印中也是如此,这很直白的告诉了我们如果一个数字一个数字的剥离。

再比如字符串的逆序,如果我们想用递归实现“abcde”的逆序,我们就应该拆分成“a”和“bcde”逆序,在“bcde”中是“b”和“cde”逆序……如此下去,我们化大为小,逐步解决了问题。

以上就是我对于递归的一些简单思考和见解,学识尚浅,往指正!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1038874.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号