函数递归是一种解决问题的好办法,但是能想到函数递归的解决方法也并不是轻而易举,还是得多思考思考,搞清楚函数递归的基本原理以及技巧就可以很好的使用递归来解决问题
1.什么是递归
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
简单的说递归就是在一个函数中再调用这个函数
2.递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条。
递归最常见的的错误就是死递归,举一个很简单的例子:
#include <stdio.h>
int main()
{
printf("hehe\n");
main(); //会导致栈溢出
return 0;
}
在讲static关键字时提到过栈区,和静态区,今天再来看一下
2.1小练习1
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4
假设要实现上述题目的代码,按照现在所学的C语言知识看似只能用函数递归,那该怎么用呢?给大家用代码演示一下,看不懂也不要紧,我也会给大家剖析函数递归的代码
代码演示:
#include <stdio.h>
void print(int num)
{
if (num > 9)
{
print(num / 10);
}
printf("%d ", num % 10);
}
int main()
{
int num = 0;
scanf("%d", &num);
print(num);
return 0;
}
这段代码现在看不懂也没有关系,我们来分析一下,以输入123为例
①代码进入 print函数之后,进行判断:这时的num为123,123>9所以进入的条件语句,进入条件语句之后,执行print(num/10),这时还来不及执行下面的 printf函数,然后再次进入 print函数
②进入 print函数之后,这时的num为12,12>9所以又会进入条件语句,然后再次调用 print函数,这时同样也没有机会执行下面的打印函数,然后再次进入 print函数
③进入print函数之后,这时的num为1,1<9因此不会满足条件,不会进入条件语句
④这时代码再往下走遇到了打印函数 printf,这时就要打印了,打印1%10,也就是打印1,这时③这个 print函数就执行完了,这时屏幕上也就打印了1,然后代码就往回返,到了②的 print函数,因为②的 print函数还没有执行完,这时就要执行②中的 print函数
⑤返回上来时就要紧接着执行②中没有执行的代码了,这时就会打印num%10,也就是12%10也就是在屏幕上打印了2,然后这时②的 print函数就执行完了,代码再往回返
⑥代码返回到①的 print函数就要执行它还没有执行完的代码,这时屏幕上就打印123%10,也就是3,这时全部的代码就都执行完了,屏幕上也就打印出了1 2 3
要注意,在代码执行到print(num/10)时就会再次调用这个函数,也就是说会进入一个新的print函数,这就表明这个条件语句还没有执行完,并不会执行条件语句下面的语句
2.2小练习2
编写函数不允许创建临时变量,使用函数递归求字符串的长度
我们知道有一个函数strlen专门用来求字符串的长度,那我们也来实现一下,首先得知道字符串的长度是统计\0之前的字符个数,因此不能将\0包含进去,还要求使用递归,这时就可以将求出的作为函数的返回值返回给主函数
代码演示:
#include <stdio.h>
int my_strlen(const char* str)
{
if (*str != '\0') //不能统计\0
{
return 1 + my_strlen(str + 1);
}
return 0;
}
int main()
{
char str[] = "abc";
int len = my_strlen(str);
printf("%d", len);
return 0;
}
接下来对这段代码来进行一个解读
练习完这两个代码,应该对函数递归有了初步的认识了,函数递归的特点就是大事化小,可以看到使用函数递归写出的代码简洁
3.递归与迭代
C语言递归和迭代(循环)的相同之处在于,它们都是用来解决 重复性问题的编程技术。 不同之处在于,递归是一种分而治之的思想,它将一个复杂的问题分解成若干个规模较小的相同问题,而迭代则是一种重复执行某一操作的方法,它不断地重复执行某一操作,直到满足某一条件为止。
3.1练习
求n的阶乘
分别使用递归和迭代
1! 1
2! 2 * 1
3! 3 * 2 * 1
4! 4 * 3 * 2 * 1
......
//递归
#include <stdio.h>
int factorial(int n)
{
if (n <= 1)
return 1; //1的阶乘就是1所以返回1
else
return n * factorial(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d的阶乘为: %d", n, ret);
return 0;
}
求第n个斐波那契数 (不考虑溢出)
要求斐波那契数首先得知道斐波那契数列是什么
1 1 2 3 5 8 13 21 34 55....
像这样的数列被叫做斐波那契数列
关于这个数列的规律就是从第三项开始,每一项都等于前两项之和。即:F(n)=F(n-1)+F(n-2),其中n>=3,F(1)=1,F(2)=1。
代码演示:
#include <stdio.h>
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 ret = fib(n);
printf("第%d个斐波那契数列是:%d", n, ret);
return 0;
}
但是上述代码都有问题
在使用factorial来计算10000的阶乘时(不考虑正确与否),程序会崩溃
在使用fib来计算第50个斐波那契数列时会很慢
这是为什么呢?
因为fib在调用时进行了大量的重复运算,所以会导致在计算50个斐波那契数列时会非常的耗时间
我们可以看一下fib函数在调用时会重复计算多少次第3个斐波那契数列
代码演示:
#include <stdio.h>
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 ret = fib(n);
printf("第%d个斐波那契数列是:%d\n", n, ret);
printf("count = %d\n", count);
return 0;
}
在计算第35个斐波那契数列时,就单纯的计算第3个斐波那契数列都整整重复计算了3524578次
那么该怎么改进上述两个代码呢?
可以使用非递归 (迭代) 的方法来实现
//n的阶乘
//迭代
n的阶乘
#include <stdio.h>
int factorial(int n)
{
int i = 0;
int num = 1;
for (i = 1; i <= n; i++)
{
num *= i;
}
return num;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d的阶乘为: %d", n, ret);
return 0;
}
//第n个斐波那契数列
//迭代
#include <stdio.h>
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 ret = fib(n);
printf("第%d个斐波那契数列是:%d\n", n, ret);
return 0;
}
Tip:
1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销
函数递归经典例题(自主研究)
青蛙跳台阶
汉诺塔
关于函数递归的相关知识就分享到这里,感谢大家支持!