C语言版讲解函数递归

文章目录

1.函数递归的定义及思想

首先让我们来了解一下什么是函数递归?直截了当地来讲,函数递归就是在一个函数内部调用函数自身 ,从而实现问题的解决,实现函数的功能。

函数递归是一个将大型复杂问题层层分解 为规模更小的,且与原问题相似 的子问题的过程,子问题不能再被拆分,意味着函数递归的结束,这是函数递归的核心思想。递归的"递"是递推之意,递归的"归"是回归之意。

在函数递归中,存在着两个必要条件,这两个必要条件如下:

1.递归的递推过程,存在限制条件,当满足这个限制条件时,递推便不再进行,递归开始回归。

2.每次递归调用之后都越来越接近这个限制条件。

2.函数递归案例

(1)求n的阶乘

阶乘是数学概念,n的阶乘就是1~n之间所有数字的乘积(包括1和n),n的阶乘用n!表示,例如4的阶乘为4!=4×3×2×1=24。

那么怎么用递归来解决求n的阶乘呢?

根据4!可以发现一个大数的阶乘是这个大数本身和比这个大数小1的数的阶乘的乘积,如上4!=4×3×2×1,也就是=4×3!,而3!=3×2!,2!=2×1!,1!=1×0!,0!=1。

根据推理可以得出阶乘公式:n! = n × (n - 1)!

同时也可以得出n的阶乘的递归公式:

F a c t ( n ) = { 1 , n = = 0 n × F a c t ( n − 1 ) , n > 0 Fact(n)= \begin{cases} 1, & n==0 \\ n \times Fact(n-1), & n>0 \end{cases} Fact(n)={1,n×Fact(n−1),n==0n>0

由以上公式不难写出代码:

c 复制代码
int Fact(int n)
{
	if (n == 0)
		return 1;
	else
		return Fact(n - 1) * n;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fact(n);
	printf("%d\n", ret);
	return 0;
}

运行结果如下:

(2)顺序打印⼀个整数的每⼀位

从键盘输入一个数,然后顺序输出这个数的每一位。例如,输入的数为1234,那么输出应该为1 2 3 4。

这道题围绕/和%展开,就拿1234为例。1234%10等于4,可以取出这个数的最后一位,然后1234/10等于123 ,再拿123%10等于3,以此类推可以根据循环得出4 3 2 1.,如下所示,但这是逆序输出的:

c 复制代码
int main()
{
	int n = 0;
	scanf("%d", &n);
	while (n > 0)
	{
		printf("%d ", n % 10);
		n /= 10;
	}
	return 0;
}

运行结果如下:

怎么顺序打印出这个数字的每一位呢?根据递归可以很轻松地解决这个问题。我们已经知道了1234%10等于4,如果想要顺序打印出,那么这个4必须最后出现。那就先顺序打印123的每一位,顺序打印123的每一位并且最后打印出3,就得先打印12的每一位,以此类推可以封装一个Print函数,传入初始参数为n,当参数不是一位数时,进行递归调用,递归调用函数的参数为n/10。随着递归的进行,当参数为一位数时,递归结束。在递归回归的过程中根据n%10来打印数字。具体解题代码如下:

c 复制代码
void Print(int n)  
{
	if (n > 9)
		Print(n / 10);  

	printf("%d ", n % 10);  

}

int main()
{
	int n = 0;
	scanf("%d", &n);  
	Print(n);

	return 0;
}

运行结果如下:

3.递归与迭代的对比

递归与迭代是我们在使用代码解决问题时的两个角度,两种方式。它们各自是既有优点也有缺点的,特点如下:

(1)递归:会占用更多的内存空间,有可能导致栈溢出 。因为在C语言函数的调用过程中,每一次的函数调用都会在内存中的栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈。如果递归层次太深,可能会引起栈溢出问题;函数性能的下降;有些复杂问题用递归来解决,既简洁又简单。

(2)迭代:使用迭代完成的任务效率高;但有时候不容易想到。

(3)案例:分别使用递归与迭代来求解斐波那契数列中的第n位。

斐波那契数列就是前两个数的和为第三个数,第一个和第二个数均为1,数列为:1 1 2 3 5 8 13 21 ...

用递归来解决这个问题非常简单,代码如下:

c 复制代码
int Fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);
	return 0;
}

运行结果如下:

但当我们从键盘输入50时,程序迟迟得不到结果,这是为什么呢?因为递归的层次太深,需要计算的太多。而且冗余计算的格外多。例如,当我们求第50个数是多少时,我们需要求出第49和第48个数是多少,分别求出第49和第48个数是多少时,又需要分别求出第48、47和第47、46个数是多少,以此类推,重复计算会特别多。

如下为测试代码:

定义一个全局变量count,最后打印count的值。

c 复制代码
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 n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);
	printf("%d\n", count);
	return 0;
}

运行结果如下:

可以看出计算n等于3时的斐波那契数的次数高达3900万之多。所以使用递归来解决这个问题性能是非常低的。

那么尝试使用迭代来解决问题吧。我们可以使用循环,来求出前两个数的和,再把和赋值给第二个数,把第二个数赋值给第一个数,再求和。代码如下:

c 复制代码
int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;

	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}

	return c;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);

	return 0;
}

同样可以得出运算结果,但效率更高。

4.总结

递归是解决实际问题的一种方式,通过将大事化小来解决问题。看似简单的背后实则巧妙,但有时候有些问题不适合用递归来解决,这时候就可以寻求迭代之策。

关于递归的讨论就到这里,欢迎评论,留言,分享!

相关推荐
猿长大人1 小时前
C# | 函数式编程入门
开发语言·c#·.net
W_326001 小时前
Python 组合数据类型——序列(列表 & 元组)
开发语言·python
Billy121381 小时前
Day10-C++20 Ranges(上):惰性求值与组合式数据处理
开发语言·c++进阶学习
有点。2 小时前
C++深度优先搜索(二)
开发语言·c++·深度优先
二进制杯莫停2 小时前
A和B环境的python版本相同,B环境无法安装pip依赖,离线安装
开发语言·python·pip
l1t2 小时前
kryonix提交的DuckDB 统一并优化标量执行器基础设施 - #24564 PR
开发语言·数据库·数据仓库·sql
unityのkiven3 小时前
C# 中的奇异递归模板模式:MonoSingleton<T> 的实现
java·开发语言·c#
wuminyu3 小时前
JUC包的AQS与JVM的ObjectMonitor机制对比分析
java·linux·c语言·jvm·c++
就不掉头发4 小时前
C++函数模板
java·开发语言·c++