前言
Java和C的函数递归大同小异。只此一篇足以。
最近遇到了好多递归问题,printf不能以二进制的形式打印,好吧我就用递归弄一个函数,思来想去,还是把这个部分放在这里,也算有始有终。
一、什么是函数递归
IDE:VS2022。
C语言中,函数可以调用自身,这个调用自身的过程就是递归,递归可以很简洁解决问题,但结束递归往往是难处。但递归太深了,内存不够用,会栈溢出。(函数栈帧部分,函数调用会在栈上开辟新空间,而递归调用自身,也会开辟。如果递归太深了或者死递归必然栈溢出程序崩溃)
二、如何理解并使用递归
递归可以逐字拆分理解,递是递推,归是回归。先递推后回归,意思是递推有终点不能再递推而开始返回值(回归)的情况。
递归思想:与数学上的化归思想类似,将大的问题分解成小的子问题然后重复操作直到不能拆分位置,由复杂到简单转换过程。
递推的条件:
- 递推有终点。
- 每次递归(调用自身)会越来越进接近这个限制条件即终点。(之后便从终点开始逐步往上返回值。
- 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同。
三、递归实例
计算n的阶乘
简单回顾;数学上定义的n的阶乘为n!=1*2*3*4...*(n-1)*n,并规定0也有阶乘即0!=1;
由此可以知道1!=1;2!=1*2=2;3!=1*2*3=6等。
方法找递推公式
//设Fn=n!,n!=n*(n-1)!
//意味着Fn=n*F(n-1);递推公式就被找出来;
#include<stdio.h>
int Fn(int x);
int main()
{
int a;
scanf("%d", &a);
printf("%d", Fn(a));
return 0;
}
int Fn(int x)
{
if (x == 0)//限制条件
return 1;
else
return x * Fn(x - 1);
}
如上图举例F(4)=4*F(3)->F(3)=3*F(2)->F(2)=2*F(1)->F(1)=1*F(0);递推结束F(0)=1;
以上经历了四次调用,回归从终点F(0)=1开始->F(1)=1向上级返回F(2)=2*1=2->F(3)=6...直到F(4)被计算出来并返回给主函数。就是这一过程连续。
以上写法仅限x较小的情况,否则会出现整数溢出的情况。因为阶乘即使x在10范围已经很大了。
C语言递归解决爬楼梯问题
简单介绍一下,这是用递归解决著名青蛙跳台阶问题(换了一个形式)。
找规律解决
遇到复杂问题进行拆分转换
试着分析前面几项,设台阶数为n
一 n=1
跳法只有一种,即1.
——————
二 n=2
有1+1,2两种情况,每种也只有一种小的跳法,合计两种。
——————
三 n=3
有1+1+1,1+2,3三种情况
1+1+1;
1+2,2+1(表示先后顺序,即先跳一步再跳两步,和先跳两步再跳一步是不同的);
3;
合计4种。
——————
四 n=4
有1+1+1+1,1+1+2,1+3,2+2。(注意最多只能跳三步)
1+1+1+1;
1+1+2,1+2+1,2+1+1;
1+3,3+1;
2+2;
合计7种。
——————
规律可能还不明显,试着计算n=5的情况,发现一共有13种。
我们试着写数列
1,2,4,7,13...
有从第四项开始,每一项等于其前三项之和,有Fn=F(n-1)+F(n-2)+F(n-3);这与斐波那契数列有共同之处。
所以我们就不难写出了。如图(记得引头文件)。
#include<stdio.h>
int fact(int x);//函数声明
int main()
{
int n = 0;
scanf("%d", &n);
int a = fact(n);//接受一个整型(台阶数),返回一个整型(跳法)
printf("%d", a);
return 0;
}
int fact(int x)//函数定义
{
if (x == 1)//一阶有一种跳法
return 1;
else if (x == 2)//二阶有两种跳法
return 2;
else if (x == 3)//三阶有四种跳法
return 4;
else
return fact(x - 1) + fact(x - 2) + fact(x - 3);
}
动态规划(了解一下)
我们先定义一个dp数组,dp[i]就是dp的数组的一个元素。它的含义是在从初始位置跳到第i阶的方法数。记住这个含义。
接下来找状态转移方程(递推公式)。
1阶,方法数是1;
2阶,方法数是2;
3阶,方法数是4;
由于一次最多跨三步;
那么四阶的方法数呢?很明显在一阶基础上在跨3步就到了,同样在二阶基础上跨两步就到了,三阶跨一步就行了。
所以四阶的方法数就是前面三者之和,1+2+4=7;
再推广到一般,dp[i]=dp[i-1]+dp[i-2]+dp[i-3],这就是这道题的状态转移方程(递推公式)
int main()
{
int dp[47] = { 0 };
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
int n = 0;
scanf("%d", &n);
if (n > 0 && n <= 3)
;
else {
for (int i = 4; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
}
}
printf("%d", dp[n]);
return 0;
}
函数递归实现打印整数二进制
#include<stdio.h>
void to_binary(unsigned int n);
int main()
{
unsigned int number = 0;
printf("Please a integer to\n");
while (scanf("%d", &number) == 1)
{
to_binary(number);
puts("\n");
printf("Please a integer to\n");
}
}
//跟打印顺序十进制数1234一样
//比如某个二进制数10000 1000 100 10 1
//获取每个位数,然后从高位打印即可。
void to_binary(unsigned int n)
{
int r = 0;
r = n % 2;
if (n >= 2)
to_binary(n / 2);
putchar(0 == r ? '0' : '1');
汉诺塔问题
给定三根柱子,记为 𝐴,𝐵,𝐶 ,其中 𝐴 柱子上有 𝑛 个盘子,从上到下编号为 0 到 𝑛−1 ,且上面的盘子一定比下面的盘子小。问:将 𝐴 柱上的盘子经由 𝐵 柱移动到 𝐶 柱最少需要多少次?
移动时应注意(规则):
① 一次只能移动一个盘子
②大的盘子不能压在小盘子上
如果A中只有一个盘子,那么显然直接挪到C盘就一次。
如果A中有两个盘子,那么按照规则 :
一共有三次。
那么状态一有一次,状态二有两次。状态三可以理解为有状态二确定。
先把红色方框部分弄到B盘上,记为方法dp[2],那么dp[3]就可以理解为在dp[2]的基础上挪两次红色方框的整体和一次底盘。即dp[3]=2*dp[2]+1;
看到我用dp了,首先想到了动态规划。
动态规划
确定dp数组,这里用一维dp数组。dp[i]表示i个盘子从A盘挪到C盘的方法次数。
递推公式由前面易得dp[i]=2*dp[i-1]+1;
解释:
将n个盘子当作1个盘子和n-1个盘子整体化的两个盘子的情况。
dp[i]表示i个盘子从A盘挪到C盘的方法次数。
dp[i-1]表示i-1个盘子从A盘挪到C盘的方法次数。
而两个盘子的情况,需要挪动两次上面整体的盘子和一次最底部的盘子。
dp[i]=2*dp[i-1]+1;状态转移方程(递推公式)
接下来初始化dp数组,只需要初始化dp[0]=0即可。
int main()
{
int dp[30];//按需求增大定长数组空间
dp[0] = 0;//dp数组初始化
int n = 0;//开局有几个盘子
scanf("%d", &n);
for (int i = 1; i <= n; i++)//循环dp数组
{
dp[i] = 2 * dp[i - 1] + 1;
}
printf("%d", dp[n]);
return 0;
}
递归
int f(int n)
{
if (n == 0)
return 0;
else
return 2 * f(n - 1) + 1;
}
int main()
{
int n = 0;//开局有几个盘子
scanf("%d", &n);
printf("%d", f(n));
return 0;
}
当然,如果你一眼发现这个递推公式的通项公式是 dp[n]=2^n - 1;那么可以直接写成这样
#include<math.h>
int main()
{
int n = 0;//开局有几个盘子
scanf("%d", &n);
printf("%d", (int)pow(2,n) - 1);
return 0;
}
结尾
2024.5.12早上更新了汉诺塔问题和动态规划的解法。
补充(爬楼梯问题循环优化)
试着运行代码,并对比两种代码输入较大数字的输出效率(注意别溢出哦),试着统计一下fact(4)被计算的次数吧,递归就一定好吗?最后,辛苦看到最后的各位,谢谢阅读。