注:
本笔记参考B站up鹏哥C语言的视频
目录
认识递归
什么是递归
程序调用自身的编程技巧称为递归(recursion)。递归作为一种算法在程序设计语言中广泛应用。
一个程序或函数在其定义或说明中直接或间接调用自身的一种方法,它通常把一个大型、复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归测略只需要少量的程序就可以描述出解题过程所需要的多次重复运算,大大减少了程序的代码量。
递归的主要思考方式在于:把大事化小。
例子:
#include<stdio.h>
int main()
{
printf("Hello World\n");
main();
//main函数自己调用了自己
return 0;
}
//结果为一直调用 Hello World直到程序崩掉
//报错:Stack overflow
递归的两个必要条件
· 存在限制条件,当满足这个限制条件时,递归不再继续。
· 每次递归调用之后越来越接近这个限制条件。
例1(按顺序打印一个整型值的每一位)
接受一个整型值(无符号),按照顺序打印它的每一位。例如:输入:1234,输出:1 2 3 4
思考:
1234 % 10 = 4 4
1234 / 10 = 123 123 % 10 = 3 3
123 /10 = 12 12%10 = 2 2
12 / 10 = 1 1 % 10 = 1 1
1 / 10 = 0
代码:
#include<stdio.h>
void print(unsigned int n)
{
if (n > 9)
{
print(n / 10);
//函数进如if语句后,print函数再次展开
//一直递归到逻辑发生变化
//调用结束,返回函数的上一层
}
printf("%d ", n % 10);
//从递归展开的最里层往外打印
}
//print(1234)
//print(123) + 4
//print(12) + 3 4
//print(1) + 2 3 4
int main()
{
unsigned int num = 0;
scanf("%u", &num);//1234
//递归 - 函数自己调用自己
print(num);//print函数可以打印参数部分数字的每一位
return 0;
}
即大事化小的思路。
上图的红框中的是两个递归的必要条件。靠上面的红框中,展示了递归的限制条件,靠下面的红框内容使递归越来越接近限制条件。(两个条件缺一不可)
存在问题的递归
例子
include<stdio.h>
void test(int t)
{
if (t < 10000)
{
test(t + 1);
}
printf("%d\n", t );
}
int main()
{
test(1);
return 0;
}
结果:
按F10,显示:
可以看到:Stack overflow - 栈溢出
知识点
内存中存在三个区域 - 栈区、堆区、静态区
栈区内存放:局部变量、函数形参、调用函数时的返回值等临时变量
堆区内存放:动态内存分配(如malloc/free等库函数就与动态内存相关)
静态区内存放:全局变量、静态变量
每一个函数的调用都要在栈区上分配一个空间。
如:
main函数使用后,在栈区内占了一块空间,这块空间被称为 main函数的栈帧空间。而main函数又把空间分配给函数内的局部变量。
如果main函数内有其它函数,栈区内还需要再分空间给main函数内部的函数,如此往复,每一个被调用的函数都需要空间,(如果递归层次太深)而栈区空间不够时,就会出现栈溢出的现象。
所以写递归代码时:
1.不能死递归,要有跳出条件,每次递归逼近跳出条件;
2.递归层次不能太深。
推荐网站:被称为程序员的知乎https://stackoverflow.com/(可以提问,回答问题 - 一般用英文(如果你厉害可以试试用中文回答问题))
例2(求字符串长度)
编写函数不允许创建临时变量,求字符串长度
先看:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "Hello";
//['H']['e']['l']['l']['o']['\0']
printf("%d\n", strlen(arr));
//打印结果为5
return 0;
}
要想求字符串长度,可以模拟实现一个strlen函数
如果可以创建临时变量(运用计数器):
#include<string.h>
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "Hello";
//['H']['e']['l']['l']['o']['\0']
printf("%d\n", my_strlen(arr));
return 0;
可行,但是不符合题目要求。
运用递归的思路:
my_strlen("Hello")
1+my_strlen("ello")
1+1+my_strlen("llo")
......
1+1+1+1+1+my_strlen("")
1+1+1+1+1 = 5
大事化小
代码:
#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
{
if (*str != '\0')
{
return 1 + my_strlen(str + 1);//"str + 1" - 下一个字符的地址
//这里不能用"str++",因为"str++"是先进行传递,
//再完成++,传递原来的值,留下了加完的值
//("++str"也有副作用,会留下加完的值,传入加完的值,可能出问题)
}
else
{
return 0;
}
}
int main()
{
char arr[] = "Hello";
//['H']['e']['l']['l']['o']['\0']
printf("%d\n", my_strlen(arr));
return 0;
}
(注:在递归中一般不建议运用“++”的写法)
my_strlen函数运行过程:
接收 str(此时*str内存放“Hello”) -- 判断 -- str != 0 -- 进入if语句 -- 再次进入my_strlen函数
接收 str(此时*str内存放“ello”) -- 判断 -- str != 0 -- 进入if语句 -- 再次进入my_strlen函数
......
接收str(此时*str内存放“\0”) -- 判断 -- str = 0 --进入else语句 -- 开始向上返回数值
......
最后返回结果 = 5
递归与迭代
迭代:不断重复做一件事情。(不一定是循环,但循环可以被称为迭代)
例3(求n的阶层)
求n的阶层(不考虑溢出)
ps:n! = 1*2*3*...*n
循环(迭代)的方法:
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
for ( i = 1; i <= n; i++)
{
ret = ret * i;
}
printf("%d ", ret);
return 0;
}
循环是一种迭代的方式。
关于n!(Fac(n)),有公式:
运用递归:
#include<stdio.h>
int Fac(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * Fac(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fac(n);
printf("%d\n", ret);
return 0;
}
例4 求第n个斐波那契数
求第n个斐波那契数。(不考虑溢出)
斐波那契数列:1 1 2 3 5 8 13 21 34 55 ...... (前两个数字之和等于第三个数)
关于斐波那契数(Fib),可以设置公式:
递归解法:
#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 ", ret);
printf("\ncount = %d\n ", count);
return 0;
}
这种代码效率太低 - 重复大量的计算
如:要求第50个斐波那契数
![]()
2的0次方 -- 2的1次方 -- 2的2次方 -- ......
循环(迭代)解法:
#include<stdio.h>
int count = 0;
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
count++;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%lld ", ret);
printf("\ncount = %d\n ", count);
return 0;
}