递归是什么

递归的实质

我们知道:函数可以调用其他函数实现部分功能,那么,函数可不可以调用自己呢?
答案:当然!
在这里插入图片描述

故事

从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说:从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说:从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说:……

怎么用C语言打印出来呢?
简单:不就是循环嘛!

#include<stdio.h>
int main(){
	int i=1;
	for(i=1;i<=20;i++){
		printf("从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说\n");}
	return 0;
}

重复打印的事,我们可以交给函数执行:改!
也就是之后无论哪个和尚说这个故事都可以调用这个函数,节省代码量和时间!在这里插入图片描述

#include<stdio.h>
void digui();
void digui(){
	int i=1;
	for(i=1;i<=20;i++){
		printf("从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说\n");}
}
int main(){
	digui();
	return 0;
}

欸,可是函数里面这句话也是一直重复的呀。。。
在这里插入图片描述

我自己调用自己一直重复打印这句话不就好了吗!

#include<stdio.h>
void digui();
void digui(){
	static int i=1;
	//静态局部变量,每次都保留上一次的值,但是作用域没变,即递归调用自己时不会初始化为1
	printf("从前有座山,山上有座庙,庙里有两个和尚,老和尚对小和尚说\n");
	i++;
	if(i<=20){
		digui();}	
}
int main(){
	digui();
	return 0;
}


静态局部变量(static)
static 用于描述具有文件作用域的变量或函数时,表示将其链接属性从external 修改为 internal,它的作用范围就变成了仅当前源文件可以访问。
但如果将 static 用于描述局部变量,那么效果又会不一样了。默认情况下,局部变量是 auto 的,具有自动存储期的变量。如果使用 static 来声明局部变量,那么就可以将局部变量指定为静态局部变量。static 使得局部变量具有静态存储期,所以它的生存期与全局变量一样,直到程序结束才释放。

递归从原理上来说就是函数调用自身这么一个行为。

来一个错误示范:

#include<stdio.h>
void recursion(void);
void recursion(void){
	printf("hi\n");
	recursion();//自己调用自己,无限月读!
}

int main()
{
	recursion();
	return 0;
}

别试!会炸!

于是我们悟道了一个道理:

编写递归程序需要注意的地方

递归程序需要正确设置结束条件,否则递归程序会一直走下去,直到崩溃。

#include<stdio.h>
void recursion(void);
void recursion(void){
    static int count=10;
	printf("hi\n");
    if(--count)
    {
       	recursion();
    }
}

int main()
{
	recursion();
	return 0;
}

栗子:递归求阶乘

n ! = 1 ∗ 2 ∗ 3 ∗ . . . ∗ n n!=1*2*3*...*n n!=123...n

首先使用循环
使用累乘的思想循环,一次成一个项:

#include<stdio.h>
long fact(int num);
long fact(int num)
{
	long result;
	for(result=1;num>1;num--)
	{
		result*=num;
	}
	return result;
}
int main()
{
	int num;
	printf("请输入一个正整数:");
	scanf("%d",&num);
	fact(num);
	printf("数%d的阶乘为:%ld",num,fact(num));
	return 0;
}

修改为递归

n ! = ( n − 1 ) ! ∗ n n!=(n-1)!*n n!=(n1)!n
( n − 1 ) ! = ( n − 2 ) ! ∗ ( n − 1 ) (n-1)!=(n-2)!*(n-1) (n1)!=(n2)!(n1)
… …
2!=1! *2

所以若fact()为求阶乘函数,即 n ! = f a c t ( n ) n!=fact(n) n!=fact(n)
则有

n ! = f a c t ( n ) = f a c t ( n − 1 ) ∗ n = f a c t ( n − 2 ) ∗ ( n − 1 ) ∗ n = . . . = f a c t ( 1 ) ∗ 2 ∗ . . . ∗ n n!=fact(n)=fact(n-1)*n=fact(n-2)*(n-1)*n=...=fact(1)*2*...*n n!=fact(n)=fact(n1)n=fact(n2)(n1)n=...=fact(1)2...n

即在fact(n)函数中调用自己计算fact(n-1),而在这继续调用fact(n-2)…一直至fact(1),再逐层将结果返回

在这里插入图片描述

#include<stdio.h>
long fact(int num);
long fact(int num)
{
	long result;
	if(num>0)//结束条件
	{
		result=num*fact(num-1);//自己调用自己
	}
	else
	{
		result=1;
	}
	return result;
}
int main()
{
	int num;
	printf("请输入一个正整数:");
	scanf("%d",&num);
	fact(num);
	printf("数%d的阶乘为:%ld",num,fact(num));
	return 0;
}

例子:递归求解斐波那契数列

数列长这样子:

 1    1    2   3   5   8   13   21    34

规律:
a n = a n − 1 + a n − 2 a_n=a_{n-1}+a_{n-2} an=an1+an2
若fun(n)函数为求解 a n a_n an项值,则有
f u n ( n ) = f u n ( n − 1 ) + f u n ( n − 2 ) fun(n)=fun(n-1)+fun(n-2) fun(n)=fun(n1)+fun(n2)
即需要调用两次自己!

# include <stdio.h>
int fun(int n); 
int fun(int n){
	int num;
	if(n==1||n==2)
	{
		num=1;
	}
	else
		num=fun(n-1)+fun(n-2);//调用自己
	return num;
}

int main (void)
{  
    int n;
    printf("Enter n:");
    scanf("%d",&n);
    
	printf("a_n=%d",fun(n));
	return 0;
}

对比一下用循环的思想做:

# include <stdio.h>
int fun(int n); 
int fun(int n){
	int num1,num2,num;
	int i;
	num1=num2=1;//纪录前两项为1 
	if(n==1||n==2)
	{
		num=1;//纪录前两项为1 
	}
	else
		for(i=3;i<=n;i++){
			num=num1+num2;//否则为前两项的和 
			//更新前两项 
			num1=num2;
			num2=num;
		}	
	return num;
}

int main (void)
{  
    int n;
    printf("Enter n:");
    scanf("%d",&n);
    
	printf("a_n=%d",fun(n));
	return 0;
}

递归的优势和劣势

优势:递归的思考角度跟通常的迭代(你可以理解为 for 循环之类的)迥然不同,所以有时候使用迭代思维解决不了的问题,使用递归思维则一下子迎刃而解。

劣势:递归的执行效率通常比迭代低很多,所以递归程序要更消耗时间;由于递归函数是不断调用函数本身,在最底层的函数开始返回之前,程序都是一致在消耗栈空间的,所以递归程序要“吃”更多的内存空间;递归的结束条件设置非常重要,因为一旦设置错误,就容易导致程序万劫不复(崩溃)。

汉诺塔

在线小游戏:https://www.hannuota.cn/

假设有64个盘子:对于汉诺塔的玩法,可以简单分解为三个步骤:

  • 将前 63 个盘子从 X 移动到 Y上,确保大盘在小盘下。
  • 将最底下的第 64 个盘子从 X 移动到 Z 上。
  • 将 Y 上的 63 个盘子移动到 Z 上。

在游戏中,由于每次只能移动一个圆盘,所以在移动的过程中显然要借助另外一根针才可以实施。也就是说,步骤 1 将 1~63 个盘子移到 Y 上,需要借助 Z;步骤 3 将 Y 针上的 63 个盘子移到 Z 针上,需要借助 X。

所以我们把新的思路聚集为以下两个问题:

  • 问题一:如何将 X 上的 63 个盘子借助 Z 移到 Y 上?
  • 问题二:如何将 Y 上的 63 个盘子借助 X 移到 Z 上?

解决这两个问题的方法跟解决“如何将 X 上的 64 个盘子借助 Y 移动到 Z 上?”这个问题是一样的,都是可以拆解成 1、2、3 三个步骤来实现。

问题一(“如何将 X 上的 63 个盘子借助 Z 移到 Y 上?”)拆解为:

  • 将前 62 个盘子从 X 移动到Z上,确保大盘在小盘下。
  • 将最底下的第 63 个盘子移动到 Y 上。
  • 将 Z 上的 62 个盘子移动到 Y 上。

问题二(“如何将 Y 上的 63 个盘子借助 X 移到 Z 上?”)拆解为:

  • 将前 62 个盘子从 Y 移动到 X 上,确保大盘在小盘下。
  • 将最底下的第 63 个盘子移动到 Z 上。
  • 将 X 上的 62 个盘子移动到 Y 上。

没错,汉诺塔的拆解过程刚好满足递归算法的定义,因此,对于如此难题,使用递归来解决,问题就变得相当简单了!

#include <stdio.h>

void hanoi(int n, char x, char y, char z);

void hanoi(int n, char x, char y, char z)
{
        if (n == 1)
        {
                printf("%c --> %c\n", x, z); // 剩下底部的那个圆盘
        }
        else
        {
                hanoi(n-1, x, z, y); // 将n-1个圆盘从x移动到y
                printf("%c --> %c\n", x, z);
                hanoi(n-1, y, x, z); // 将n-1个圆盘从y移动到z
        }
}

int main(void)
{
        int n;

        printf("请输入汉诺塔的层数:");
        scanf("%d", &n);

        hanoi(n, 'X', 'Y', 'Z');

        return 0;
}

参考资料

鱼C工作室、论坛

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小邹子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值