函数递归与题目解析——C语言

94 阅读6分钟

1 递归是什么?

递归是一种解决问题的方法,在C语言中,递归就是函数自己调用自己。

1.1 递归的思想

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

递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。

1.2 递归的限制条件

递归在书写的时候,有2个必要条件:

  • 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。

在下面的例子中,我们逐步体会这2个限制条件。

2 递归题目解析

2.1 求n的阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。

自然数n的阶乘写作n!。

题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

2.1.1 分析和代码实现

我们知道n的阶乘的公式:n! = n * (n一1)!

从这个公式不难看出:如何把一个较大的问题,转换为一个与原问题相似,但规模较小的问题来求解的。

n的阶乘和n-1的阶乘是相似的问题,但是规模要少了n。有一种有特殊情况是:当n==0的时候,n的阶乘是1,而其余n的阶乘都是可以通过上面的公式计算。

这样就能写出n的阶乘的递归公式:

那我们就可以写出函数Fact()求n的阶乘(递归是基于函数的),假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,函数如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

//递归
int Func(int n)
{
	if (n == 0)
		return 1;
	return n * Func(n - 1);
}

//非递归
int Func2(int n)
{
	int m = 1;
	for (int i = 1; i <= n; i++)
	{
		m *= i;
	}
	return m;
}

int main()
{
	int n = 5;
	printf("%d\n", Func(n));
	printf("%d\n", Func2(n));
	return 0;
}

2.2 顺序打印一个整数的每一位

输入一个整数m,按照顺序打印整数的每一位。

比如:

输入:1234 输出:1 2 3 4

输入:520 输出:5 2 0

2.2.1 分析和代码实现

这个题目,放在我们面前,首先想到的是,怎么得到这个数的每一位呢?

如果n是一位数,n的每一位就是n自己

n是超过1位数的话,就得拆分每一位

1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4

然后继续对123%10,就得到了3,再除10去掉3,以此类推

不断的%10和/10操作,直到1234的每一位都得到;

但是这里有个问题就是得到的数字顺序是倒着的

但是我们有了灵感,我们发现其实一个数字的最低位是最容易得到的,通过%10就能得到

那我们假设想写一个函数Print()来打印n的每一位,如下表示:

如果n是1234,那表示为

Print(1234) //打印1234的每一位

其中1234中的4可以通过%10得到,那么

Print(1234)就可以拆分为两步:

  1. Print(1234/10) //打印123的每一位

  2. printf(1234%10) //打印4

完成上述2步,那就完成了1234每一位的打印

那么Print(123)又可以拆分为Print(123/10) + printf (123%10)

以此类推下去,直到被打印的数字变成一位数的时候,就不需要再拆分,递归结束。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//打印一个数的每一位
void Print(int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n%10);
}

int main()
{
	int n = 1234;
	Print(n);

	return 0;
}

在这个解题的过程中,我们就是使用了大事化小的思路

把Print(1234)打印1234每一位,拆解为首先Print(123)打印123的每一位,再打印得到的4

把Print(123)打印123每一位,拆解为首先Print(12)打印12的每一位,再打印得到的3

直到Print打印的是一位数,直接打印就行。

2.3 青蛙跳台阶问题[1]

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

2.3.1 分析和代码实现

我们设定Jump(n)可以计算青蛙跳上一个n级的台阶总共有多少种跳法。

假设n = 4,青蛙跳到第4级台阶时有两种跳法,从第3级跳上来或者从第2级跳上来

可以写作Jump(4) = Jump(3) + Jump(2)

而青蛙跳到第3级台阶时也有两种跳法,从第2级跳上来或者从第1级跳上来

Jump(3) = Jump(2) + Jump(1)

我们知道当台阶为1级时,只有1种跳法;为2级时,有2种跳法。

即 Jump(2) = 2;Jump(1) = 1

接下来我们推广到第n级(n>2)台阶时前一次跳的情况。它也有两种跳法:一是从第n-1级台阶起跳,二是从第n-2级台阶起跳。这样一来,我们就可以写出关于n的递推公式

那我们就可以写出函数Jump()求n级台阶的跳法,假设Jump(n)就是到n级台阶的跳法,那么Jump(n-1)就是求到n-1级台阶的跳法,Jump(n-2)就是求到n-2级台阶的跳法,函数如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>


//青蛙跳台阶

int Jump(int x)
{
	if (x == 1)
		return 1;
	else if (x == 2)
		return 2;
	else
		return Jump(x - 1) + Jump(x - 2);
}


int main()
{
	int n = 4;
	int ret = Jump(n);
	printf("%d\n", ret);
	return 0;
}

2.4 计算一个数的每位之和

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//计算一个数的每位之和
int DigitSum(int n)
{
	if (n < 10)
	{
		return n;
	}
	return n%10 + DigitSum(n/10);
}

int main()
{
	int n = 1234;
	printf("%d", DigitSum(n));
	return 0;
}

2.5 计算x的y次幂

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//计算x的y次幂
int MI(int x, int y)
{
	if (y == 0)
		return 1;
	else if (y == 1)
		return x;
	else
		return x * MI(x, y - 1);
}

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

2.6 汉诺塔问题[2]

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//汉诺塔
void Print(char x, char y)
{
	printf("%c->%c ", x, y);
}
void Hanno(int x, char pos1, char pos2, char pos3)
{
	if (x == 1)
		Print(pos1, pos3);
	else
	{
		Hanno(x - 1, pos1, pos3, pos2);
		Print(pos1, pos3);
		Hanno(x - 1, pos2, pos1, pos3);
	}
}

int main()
{
	int n = 3;
	Hanno(n,'A','B','C');
	return 0;
}

正文完

参考资料:

[1]青蛙跳台阶问题: www.nowcoder.com/share/jump/…

[2]汉诺塔问题: www.nowcoder.com/share/jump/…

C语言实现汉诺塔: www.bilibili.com/video/BV13g…