一 ,什么是递归?
写代码是为了解决问题,而递归是可以帮助我们解决问题的方法!在C语言中,而递归就是自己调用自己。如果又一个大型且很复杂的问题,我们可以层层把它拆解成一个与原问题相似,但规模小的子问题了求解;直到子问题不可被拆分,递归就结束了,所以,递归的思考方式就是把大事化小的过程!大事化小!
递归中的递 ---------->递推
归 ---------->回归
例题1:
------->思考输入2时,打印出来的数字是多少?
#include <stdio.h>
int Fun(int n)
{
if (n == 5)
return 2;
else
return 2 * Fun(n + 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int c = Fun(n);
printf("%d\n", c);
return 0;
}
在思考的过程中,其实脑袋里的过程,是不断往“细化”中走;
例题2:
写一个史上为简单的C语言递归代码:
int main()
{
printf("hehe\n");
main(); //在main函数中又调用了main函数
return 0;
}
二 ,递归的限制条件?
递归在书写的时候,有两个必要条件:
1.递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后,会越来越接近这个限制条件。
(简而言之,就是递归得有限制条件,每次的调用,接近这个限制条件)
三 ,递归案例
案例1:
求n的阶乘:
一个正整数的 阶乘(factorical) 是所有小于及等于该数的正整数的积,并且0的阶乘为1,自然数 n 的阶乘写作 n!
这里不考虑n太大的情况,n太大会存在溢出!
题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘
n 的阶乘的公式 :n! = n*(n-1)!
举例:
5! = 5 * 4 * 3 * 2 * 1
4! =4! * 3 * 2 * 1
=========== >5! = 5 * 4!
思路:
这样的思路就是把一个较大的问题,转换为一个与原问题相似,但规模较小的问题来解决!
用图来表示:
代码实现:
// n 的阶乘
#include <stdio.h>
int Fact(int n)
{
if (n == 0)
return 1;
else
return n * Fact(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int c = Fact(n);
printf("%d\n", c);
return 0;
}
注:为什么n的值太大会导致栈溢出?
==== > 在C语言中,每一次函数调用,都要为这次函数调用在栈区申请一块内存空间,用来为这次函数调用存放信息,这一块空间就叫:运行时堆栈 或者 函数栈帧空间 ,如果函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,知道函数递归不再继续,开始回归,才逐渐释放栈帧空间。
所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow);
案例2:
题目:顺序打印一个整数的每一位(输入一个整数 m,按照顺序打印整数的每一位)
比如:
输入:1234 输出:1 2 3 4
输入:520 输出:5 2 0
思路:
1. 首先是:如何得到数字的每一位数字呢?
2. 但是我们有了灵感,我们发现其实⼀个数字的最低位是最容易得到的,通过%10就能得到
代码实现:
#include <stdio.h>
void Print(int n)
{
if (n > 9)
Print(n / 10);
printf("%d ", n % 10);
}
int main()
{
int n = 0;
scanf("%d", &n);
Print(n);
return 0;
}
四 ,递归与迭代
递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误用的,就像案例1⼀样,看到推导的公式,很容易就被写成递归的形式:所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的方式(通常就是循环的方式)。
以下是案例1的非递归方式求解:
int main()
{
int n = 0;
scanf("%d", &n);
int fact = 1;
for (int i = 1; i <= n; i++)
{
fact *= i;
}
printf("%d\n", fact);
return 0;
}
上述代码是能够完成任务,并且效率是比递归的方式更高。
案例3:
题目:求第 n 个数的斐波那契数
计算第n个斐波那契数,是不适合使用递归求解的,但是斐波那契数的问题通过是使用递归的形式描述的:
代码实现+分析:
1.递归方式:
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 r = Fib(n);
printf("%d\n", r);
return 0;
}
输出:
但是当我们输入求第55个斐波那契数时:
代码一直在疯狂计算!
我们可以试着算一下3这个数字,在求斐波那契数的时候,被求了多少次:
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 r = Fib(n);
printf("%d\n", r);
printf("count = %d\n", count);
return 0;
}
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
if (n <= 2)
return 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fib(n);
printf("%d\n", r);
return 0;
}
有时候,递归虽然好,但是也会引入一些新的问题,所以我们一定不要迷恋递归,适可而止就好!
五 ,汉诺塔问题:
汉诺塔是什么?
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
我们思考一下,如果要完成64个盘子的转移,需要花多长时间呢?
假如可以每秒钟移动一个盘子,我们以一年366天这么算,一共需要
583,344,214,028.96527825212507589557年 ===>5833.45亿年
思路:
1.如果我们只有一个圆盘的话,我们可以直接A->C
2.如果有两个的话,A->B A->C B ->C
3.如果有三个的话 A -> C A -> B C -> B A -> C B -> A B -> C A -> C
以此类推:
发现每次完成的次数是 2^n -1
代码实现:
void move(char pos1, char pos2)
{
printf(" %c -> %c ", pos1, pos2);
}
// n :盘子个数
//pos1:起始位置
//pos2:中转位置
//pos3:目的位置
void Hanoi(int n, char pos1, char pos2, char pos3)
{
if (n == 1)
{
move(pos1, pos3);
}
else {
Hanoi(n - 1, pos1, pos3, pos2);
move(pos1, pos3);
Hanoi(n - 1, pos2, pos1, pos3);
}
}
int main()
{
Hanoi(1, 'A', 'B', 'C');
printf("\n");
Hanoi(2, 'A', 'B', 'C');
printf("\n");
Hanoi(3, 'A', 'B', 'C');
printf("\n");
Hanoi(4, 'A', 'B', 'C');
printf("\n");
return 0;
}