C语言函数递归知识非常详细版


前言


本文主要介绍函数递归相关知识,介绍的非常详细,大家可参考学习,如有不足,请批评指正。

提示:以下是本篇文章正文内容,下面案例可供参考

一、递归的基本知识

(一).什么是递归?

  • 递归其实是一种解决问题的方法,在C语言中,递归就是 函数自己调用自己。
    最简单的一种递归:
 #include <stdio.h>
 int main()
 {
 printf("hehe\n");
 main();//main
函数中⼜调⽤了main函数
return 0;
 }

在上述简单的递归程序中,main函数自己调用自己,不过最后构成死递归,最终会导致栈溢出。

  • 递归的思想:把一个大型复杂的问题层层转化位一个与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束了。所以递归的思考方式就是把大事化小的过程。而递归中的递就是递推的意思,归就是回归的意思。

(二).递归的限制条件

递归在书写的时候必须有两必要条件:

  • 递归存在限制条件,当满足这个限制条件的时候,递归便不会再继续。
  • 每次递归调用之后越来越接近这个限制条件
    建议以后我们在写递归时候,要时刻检查这两个条件,最好在相应的语句后面加上这两个条件的注释,会让自己代码的逻辑更加清楚

二、递归的简易应用

(一).求n的阶乘

  • 设计思路分析:
    我们都知道n的阶乘的公式:n!=n*(n-1)!
    举例:
    在这里插入图片描述
    这样的思路就是把一个较大的问题,转换为一个与原问题相似,但规模较小的问题来求解的。
    当n==0的时候,n的阶乘为1,其余n的阶乘都是可以通过公式来计算
    故而递归公式如下:
    在这里插入图片描述
#include<stdio.h>
int Fact(int n)
{
	if (n == 0)
	{
		return 1;
	}
	else if (n > 0)
	{
		return n * Fact(n - 1);
	}
}
int main() {
	int n = 0;
	scanf("%d", &n);//注意这里不考虑n太大的情况,n太大会存在溢出
	int ret = Fact(n);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述
咱们再画图推演一下递归的过程,同时也可以通过调试来理解这个过程
在这里插入图片描述

(二).顺序打印一个整数的每一位

  • 面对这个问题我们首席要考虑的是怎么得到这个数的每一位呢?分两种情况:如果n是一位数呢,n的每一位就是n自己,如果n是超过1位数的话,就得拆分每一位。这个我们呢可以通过取模操作来实现
    在这里插入图片描述
    然后需要考虑的是如何打印每一位:
    在这里插入图片描述
    以此类推就可以将大的问题拆分成子问题:
    在这里插入图片描述
    直到打印的数字变成一位的时候,就不需要再拆分,此时代表递归的结束。
  • 代码实现:
# include<stdio.h>
void print(int n)
{
	if (n > 9)
	{
		print(n/10);
	}
	printf("%d ", n % 10);
	return;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	print(n);
	return 0;
}
  • 画图演示一下这个过程:
    在这里插入图片描述

(三).求第n个斐波那契数(递归与迭代版本)

问题分析:

  • 运用递归分析:
    由于斐波那契数列的第n个数是第n-1个数和第n-2个数的和,而菲薄纳契数列前两个数都是1,我们可以很容易总结出公式
    在这里插入图片描述
    将大问题分解成子问题,很容易写出递归代码;
    递归方法代码实现:
#include<stdio.h>
int Fib(int n)//运用递归方法
{
	if (n <= 2)
	{
		return 1;
	}
	else if (n > 2)
	{
		return Fib(n - 1) + Fib(n - 2);
	}
}
int main()
{
	int n = 0;
	while (scanf("%d", &n) != EOF)
	{
		int r = Fib(n);
		printf("%d\n", r);
	}
}
  • 运用迭代分析:
    递归的缺点:
    当我们n输入50的时候,需要很长时间算出结果,这个效率是很低的,究其原因是因为,递归程序是不断展开的,展开的过程中,我们很容以就能发现,在递归程序中会有重复计算,而且递归层次越深,冗余计算就会越多
    如图很明显:
    在这里插入图片描述
    我们可以通过下面一段代码来形象观察一下冗余计算的程度:
 #include <stdio.h>
 int count = 0;
 int Fib(int n)
 {
    if(n == 3)
    count++;//统计第3个斐波那契数被计算的次数
    if(n<=2)
    return 1;
 else
    return Fib(n-1)+Fib(n-2);
 }
    int main()
 {
      int n = 0;
      scanf("%d", &n);
      int ret = Fib(n);
      printf("%d\n", ret); 
      printf("\ncount = %d\n", count);
      return 0;
}

在这里插入图片描述
当我们仅仅是输入40的时候,即求第40个斐波那契数列的时候,第三个斐波那契数就被重复计算了39088169,这些计算是非常冗余的,所以当当n很大的时候我们用递归方法来计算斐波那契数列的时候是非常不明智的。
所以我们为了提高效率采用迭代的方法:即运用循环的方法进行求解;迭代方法代码实现:

int Fib(int n)//(迭代方式)
{
	int a = 1;
	int b = 1;
	int c = 1;
	if (n == 1 || n == 2)
	{
		return 1;
	}
	else if (n > 2)
	{
		while (n>2)
		{
			c = a + b;
			a = b;
			b = c;
			n--;
		}
		return c;
	}
}
int main()
{
	int n = 0;
	while (scanf("%d", &n) != EOF)
	{
		int r = Fib(n);
		printf("%d\n", r);
	}
}

故而,有时候递归虽然代码简洁,但是会引入一些问题,所以我们不要过分的迷恋递归,掌握好度

三、递归的经典题型

(一).青蛙跳台阶问题

  • 问题描述:一只青蛙一次只能跳1级或者跳2级台阶,求该青蛙跳上第n级台阶的时候共有多少种跳法。

  • 问题分析:青蛙从一开始想跳到两级台阶,一种方法是先跳一级再跳一级,另一种是直接跳两级,运用递归思想,将大问题化为小问题,我们要跳到n级台阶,一种方法是从第n-1级台阶跳一级跳到第n级台阶,另外一种方法就是从n-2级台阶跳两级直接跳到第n级台阶。所以可以得到(设Func(n)为跳上n级台阶总共有几种跳法)Func(n)=Func(n-1)+Func(n-2);而Func(1)=1;Func(2)=2;(跳上第一级台阶只有一种方法,跳上第二级台阶共有两种方法)
    依次类推我们可以得到表格:
    在这里插入图片描述
    进而总结出公式:
    在这里插入图片描述
    运用递归思想我们可以很容易的得到代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Func(int n)
{
	if (n == 1)
		return 1;
	else if (n == 2)
		return 2;
	else
		return func1(n - 1) + func1(n - 2);
}
 
int main()
{
	int n = 0;
 
	scanf("%d", &n);
	int r = Func(n);
	printf("青蛙跳上第%d级台阶共有%d种跳法\n", n, r);
	return 0;
}              

(二).汉诺塔问题

  • 问题背景:
    法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
  • 问题描述
    假设有n个盘子,三根柱子ABC,一开始这n个盘子按照小盘子在上,大盘子在下的方式摞放在A柱子上,我们需要把这n个盘子移动到C柱子上,并且一次只能移动一片且不管在那根柱子上必须满足大盘子在下,小盘子在下的规则,要求打印出每一次移动的过程。
  • 问题分析:
    如果我们呢要把n个盘子从A柱子移动到C柱子上,我们利用递归的思想将大问题化为小问题,我们先将上面的n-1个盘子借助C柱子放到B柱子上,然后再将最后一个盘子(最大的)直接放到C柱子上,最后将B盘子放到C柱子上。依次递归下去。如果只有一个盘子的时候,我们直接将它从起始位置移动到最终位置即可。
    如图分析:
    第一次:
    在这里插入图片描述
    第二次:
    在这里插入图片描述

依次类推即可;

  • 代码实现:
#incldue<stdio.h>
//汉诺塔代码实现:(递归函数的应用)我们最终要把A柱上的盘子全部移动到C柱子上
void move(char pos1, char pos2)//移动函数
{
	printf(" %c->%c ", pos1, pos2);
}
//num为盘子的总数
//pos1为起始位置,pos2为中转位置,pos3为最终位置
void Hanoi(int num, char pos1, char pos2, char pos3)
{
	if (num == 1)//如果只有一个盘子,直接将盘子移动到最终位置即可
	{
		move(pos1, pos3);
	}
	else
	{
		Hanoi(num - 1, pos1, pos3, pos2);//先将n-1个盘子移动到中转位置B上
		move(pos1, pos3);//将最大的盘子移动到最终位置
		Hanoi(num - 1, pos2, pos1, pos3);//将剩余n-1个盘子从中转位置B移动到最终位置C上去。
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);//输入盘子数量
	Hanoi(n, 'A', 'B', 'C');
}
  • 结果显示:
    在这里插入图片描述

总结

本文对函数递归相关知识进行了详细的介绍,如有不足请批评指正,请大家多多支持!

  • 44
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值