目录
一.递归是什么
递归其实是一种解决问题的方法,在C语言中,递归就是函数自己调用自己。
举例:写一个史上最简单的递归
int main()
{
printf("hehe\n");
main();//函数自己调用自己
return 0;
}
结果:main函数进去执行打印hehe,进去之后又要调用main函数;死递归,无限递归,会栈溢出。
为啥会栈溢出:只要是函数调用都要向栈区内存申请一块空间,这样无限申请调用下去,
没有释放,栈空间放满了,就溢出了,程序崩了。
1.递归思想:慢慢体会需要
把一个复杂问题能不能转换成根原问题相似的但规模较小的问题求解;直到子问题不能再被拆分。所以递归思考方式就是,大事花小的过程.
递归中的递就是递推,归就是回归的意思,慢慢体会
2.递归限制条件
递归在书写的时候,有2个必要条件:没有肯定错,有但不一定对
(1)递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
有些情况继续,有些情况停止
(2)每次递归调用之后越来越接近这个限制条件。
渐渐的停下来~。
2个条件都有当满足,慢慢接近限制条件,当满足条件不再继续,但递归层次太深,2必要条件有也不算对,栈溢出。
二.递归举例
1.求n的阶乘
递归思路
(1)因为0的阶乘是1,当我们n==0的时候就不再递归;
(2)假如n==5;5!=5*(n-1);4!=4*(n-1).....这样递归下去直到n==0不再递归
2种情况公式:
n > 0必须,n*Fact(n-1)
n ==0 , 1
int Fact(int n)
{
if (n == 0)
{
return 1;
}
else
{
return n * Fact(n - 1);//120 24 6 2 1 1
}
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = printf("%d", Fact(n));
return 0;
}
这个代码限制条件:
n > 0; 递归
n = 0, 不在递,但递归的过程会慢慢逼近这个条件,满足这个条件回归。
2.看图了解递归
假如 n =3
3.顺序打印一个整数的每一位
输入n = 123
输出 1 2 3
思路:
(1)123%10 =3取到一个;123 / 10 =12;
(2)当n >9 ;n/10,条件不满足回归
void print(int n)
{
if (n > 9)
{
print(n / 10); //12 1 n<9
//3 2 1
}
printf("%d ", n % 10);//123
}
int main()
{
int n = 0;
scanf("%d", &n);//123
print(n);
return 0;
}
总结:
1.每次函数调用都会存一份空间,看图。
2.递归往往只有少量的代码,完成了大量运算!
3.每一次函数调用,都会向内存栈区申请一块空间。
4.这一块空间主要用来存放函数中局部变量和函数调用过程中的上下文的信息
这一块空间一般叫:函数的运行时堆栈,也叫函数栈帧。
5.编译器自动根据需要开辟空间;不是程序员来维护的。
6.开辟的空间需要回收的,函数调用完回收;main函数空间也要回收;后进先出。
7.关于函数调用过程中如何开辟的空间;如何创建函数栈帧的空间;又如何销毁?
8.stackoverflow栈溢出的名词
9.stackoverflow程序员的技术网站用于提问回答
10.关于栈区溢出:程序员没写好必要条件,会一直导致函数调用向栈区申请空间,然后死循环;程序崩掉。
三.递归与迭代
我们要知道递归写代码的劣势
(1).在C语言中每次函数调用,都需要为本次函数调用在栈区申请一块内存空间来保存函数调用的各种局部变量的值,这块空间被称为运行时堆栈,也叫函数栈帧。
(2).函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,
每一次递归函数调用都会开辟属于自己栈帧空间,真到函数递归不再继续,开始回归,才逐层释放栈帧空间。
(3)所以采用函数递归方式完成代码,要是递归层次太深,就会浪费太多栈帧空间,也可能引起栈溢出stackoverflow的问题。
(4)层次太深,浪费栈区空间,可以采用迭代的写法;迭代就是重复做一件事请,就是循环。
1.迭代写n的阶乘
int Fact(int n)
{
int i = 0;
int sum = 1;
for (i = 1; i <= n; i++)
{
sum = sum * i;
}
return sum;
}
int main()
{
int n = 0;
scanf("%d", &n);
int c = Fact(n);
printf("%d", c);
return 0;
}
各自好处
迭代:这样实现Fact函数值调用了一次,创建的函数栈帧空间就小一些,不会频繁开辟大量空间,迭代实现效率比递归更高
函数递归:递归形式非常清晰,写的代码量,写法简单,照着公式一下就写出来了。
四.斐波那契数
1.用递归求斐波那契数
求第n个斐波那契数
斐波那契数:
1 1 2 3 5 8 13 21 34 55.......
1 2 3 4 5 6 7 8 9 10求第那个斐波那契数
第一个数和第二个数固定是1;从第3个数或往后的数数字开始都是前2个数字之和。
斐波那契数公式:
{
1.n <= 2; return 1
2.n = Fib(n - 1) + Fib(n - 2);//前2个数之和
}
int count = 0;//统计第3个斐波那契数被计算了有多少次
int Fib(int n)
{
if (n == 3)
{
count++;
}
if (n <= 2)
{
return 1;
}
else
{
return n = Fib(n -1)+ Fib(n-2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int c = Fib(n);
{
printf("%d", c);
}
printf("%d", count);
return 0;
}
为啥不会栈溢出?二叉树:左右根
因为他不会全部展开计算,最多占50层二叉树性质,先算左边的算完,再算右边的,
算一点释放一点空间。但是效率很低!
这个代码你计算第50个斐波那契数你会计算不出来,因为数据太大了。
你可以去试试输入50;就开始占用你cpu,电脑风扇狂转。因为它一直在计算,但是效率不高
更适合用迭代
2.用迭代求斐波那契数
我们发现斐波那契数不太合适;所以可以用迭代方式去做
1 1 2 3 5 8 13 21 34 55.......求第50个
思路:前2个不用计算,相当于我们只用求48个。
{是个循环,当n>=3才计算,第一个和第二个不用计算
a + b = c
1 1 2
a = b //a = 1
b = c //b = 2
c = a + b
3 2 1
}
3.递归和循环的选择:
1.如果使用递归写代码,非常容易,很容易拆成子问题,那就使用递归。
2.如果使用递归写出的问题,是存在明显的缺陷,那就不能使用递归,得用迭代的方式处理。
4.总结
在学习数据结构的时候,会经常使用递归,到时候慢慢体会。
而且后期找工作,你写出递归的代码了,面试官你问你用非递归咋写。考察思路
总结:递归虽然好,但是也会引入一些问题,所以我们一定不要迷恋递归,适可而止就好!