函数递归(详细解读)

目录

什么是递归

例题1

接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4.

递归的两个必要条件

例题2

2.1使用strlen函数,求字符串的长度

2.2编写函数,求字符串的长度

2.3编写函数不允许创建临时变量,求字符串的长度。

错误总结:

递归与迭代

求n的阶乘。(不考虑溢出)

求阶乘的一种基础方法

求第n个斐波那契数。(不考虑溢出) 

递归缺点:

迭代

写代码用递归还是迭代?


什么是递归

 程序调用自身的编程技巧称为递归( recursion)。(既函数自己调用自己)

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小(大事化小)的问题来求解,

递归策略

只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小

例子:

#include<stdio.h>
int main()
{
	printf("hehe\n");
	main();
	return 0;
}

 每一次函数调用,都要在栈区申请一块空间,main()函数内调用main()函数, 又会申请空间,会把栈区耗干,程序崩溃,栈溢出。

例题1

接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4.

思路:

1234

1234%10=4

              👇

1234/10=123

123%10=3

            👇 

123/10=12

12%10=2

          👇 

12/10=1

1%10=1

输入的这个数每一次%10就可以得到次数末位数字,这样就可以打印出4321,但题目要求的是正序打印即1 2 3 4

可以分析一下:

Print(1234)%10就可以得到4

Print(123) 4 -->先把123的每一位打印出来,再打印4,按照这样的思路,可以抽丝剥茧,把大的问题转化成细致的问题,一层一层往下剥,把一个大问题逐层逐层地转化成小问题

Print(12) 3 4 --> Print(1) 2 3 4

当这个函数只剩一位数时就不需要拆了,两位数以上才需要拆

void Print(unsigned int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	Print(num);
	return 0;
}

 print("%d ",n%10); 也可以用%u

%u - 无符号的整数
%d - 有符号的整数

代码运行:

图解:

回到这个代码

void Print(unsigned int n)
{
    if (n > 9)
    {
        Print(n / 10);
    }
    printf("%d ", n % 10);

如果上述代码没有/10操作,就会出现死递归,永远递归下去,所以递归要满足条件

递归的两个必要条件

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。

2.每次递归调用之后越来越接近这个限制条件。

例题2

2.1使用strlen函数,求字符串的长度

以下使用函数的时候必须要引用库函数#include<string.h>,strlen统计的是'\0'之前出现的字符的个数

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[10] = "abc";
	int len1 = strlen(arr);	
	printf("%d\n", len1);

	/*int len2 = my_strlen(arr);
	printf("%d\n", len2);*/
	return 0;
}

 代码运行:

2.2编写函数,求字符串的长度

数组名是数组首元素的地址,当arr作为 my_strlen()的参数,传到函数内,创建指针变量char *str,代表字符串第一个字符的地址即为a的地址,创建计数器cnt,只要指针str指向的字符不是'\0',cnt++,str++.

int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
#include<stdio.h>
int main()
{
	char arr[10] = "abc";

	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

  代码运行:

2.3编写函数不允许创建临时变量,求字符串的长度。

尝试一下用递归的方式解决

思路:当发现str指向的字符不是'\0'时,长度至少为1, 总长度就是1加上后面字符串的长度

my_strlen("abcdef") <--> 1+my_strlen("bcdef")

画图解释: 

 为了便于理解,接下来简化一下字符串, 变为"abc"

int my_strlen(char* str)
{
	if(*str != '\0')
		return 1 + my_strlen(str + 1);
	else
	    return 0;

}
int main()
{
	char arr[10] = "abc";

	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

                                      再次强调递归满足的两个条件:

1  满足语句条件,进入语句递归

2  以不断+1的方式逼近跳出的条件('\0')

if(*str != '\0')
        return 1 + my_strlen(str + 1);
    else
        return 0;

错误总结:

错误代码:

if(*str != '\0')
        return 1 + my_strlen(str++);
    else
        return 0;

原因

++是后置的,所以是先把str的地址传入函数,再++,是没用的,没有逼近跳出条件

 每一个函数的str都是独立的,代码会死递归

图解:

                                                str+1和++str写法的区别

1 my_strlen(str + 1) <--> 2 my_strlen(++str)

1 把下一个字符的地址传进去了,留下的str是不会动的,对于str是不会修改

2 留下的str动了,如果递归回来想使用这个原来的地址,那就不是原来需要的,就会出现问题

补充:

总结: 

1.内存分为一个个的内存单元,一个单元大小是byte

2.每个字节都有一个地址(地址之间相差1个字节)

递归与迭代

递归:是指函数/过程/子程序在运行过程序中直接或间接调用自身而产生的重入现象。

迭代:是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。

求n的阶乘。(不考虑溢出)

求阶乘的一种基础方法

fac(n)=n * fac(n-1)

图解:

 代码呈现:

int fac(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
	}
	return ret;
}

int main()
{
	int n = 0;
	scanf("%d",&n);
	int ret = fac(n);
	printf("%d\n", ret);
	return 0;
}

程序运行: 

求第n个斐波那契数。(不考虑溢出) 

                                                        "斐波那契数列:"

1  1  2  3  5  8  13  21  34  55  .........

即当n>=2时,这个数列从第3项开始,每一项都等于前两项之和。

图解:

递归的方式实现斐波那契数列计算

//求第n个斐波那契数
int count;//统计第n个斐波那契数重复计算的次数
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\n", ret);
	printf("count=%d\n", count);
	return 0;
}

count作用:

是用于计算第40(n输入的40)个斐波那契数时,统计第3个斐波那契数重复出现的次数

举例:

if (n == 3)
        count++;

printf("count=%d\n", count) ;

图解: 

可以发现36重复出现多次,而且如果n输入50时特别耗费时间,(如果你的参数比较大)那就会报错: stack overflow(栈溢出) 这样的信息

因为:

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。

递归缺点:

输入50,计算第50个斐波那契数

光标在动,说明程序还在运行。

打开任务管理器

 还在占用着内存,说明程序是正常运行的

总结:

递归可以解决问题,但是遇到数字大的时候解决问题的效率很低

寻求解决问题其他办法:

迭代

循环也是迭代的一种

//迭代的方式

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\n", ret);
	printf("count=%d\n", count);
	return 0;
}

思路:

写代码用递归还是迭代?

递归:写代码写起来很直接, 像公式一样写出来,代码没有明显问题

迭代:若递归写出来的效率太低,存在明显缺陷(斐波那契数列的问题用递归去写),则用迭代的方式提高效率

提示:

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。

2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。

3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

 欢迎各位大佬补充,谢谢支持!

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论
C语言是编程语言中的一朵奇葩,虽已垂垂老矣,但却屹立不倒,诞生了数十年,仍然是最流行的编程语言之一。C语言看似简单,却不易吃透,想要运用好,更是需要积淀。本书是一本修炼C程序设计能力的进阶之作,它没有系统地去讲解C语言的语法和编程方法,而是只对C语言中不容易被初学者理解的重点、难点和疑点进行了细致而深入的解读,揭露了C语言中那些鲜为普通开发者所知的秘密,旨在让读者真正掌握C语言,从而编写出更高质量的C程序代码。 全书一共11章:第1章重点阐述了C语言中不易被理解的多个核心概念,很多初学者在理解这些概念时都会存在误区;第2~8章对预处理、选择结构和循环结构的程序设计、数组、指针、数据结构、函数和文件等知识点的核心问题和注意事项进行了讲解;第9章介绍了调试和异常处理的方法及注意事项;第10章对C语言中的若干容易让开发者误解误用的陷阱知识点进行了剖析;第11章则对所有程序员必须掌握的几种算法进行了详细的讲解;附录经验性地总结了如何养成良好的编码习惯,这对所有开发者都尤为重要。 本书主要内容:  堆和栈、全局变量和局部变量、生存期和作用域、内部函数和外部函数、指针变量、指针数组和数组指针、指针函数函数指针、传址和传值、递归和嵌套、结构体和共用体、枚举、位域等较难理解的核心概念的阐述和对比;  预处理中的疑难知识点,包括文件的包含方式、宏定义及其常见错误解析、条件编译指令和#pragma指令的使用等;  if、switch等选择结构语句的使用注意事项和易错点解析;  for、while、do while等循环结构语句的使用注意事项和易错点解析;  循环结构中break、continue、goto、return、exit的区别;  一维数组、二维数组、多维数组、字符数组、动态数组的定义和引用,以及操作数组时的各种常见错误解析;  不同类型的指针之间的区别,以及指针的一般用法和注意事项;  指针与地址、数组、字符串、函数之间的关系,以及指针与指针之间的关系;  枚举类型的使用及注意事项,结构体变量和共用体变量的初始化方法及引用;  传统链表的实现方法和注意事项,以及对传统链表实现方法的颠覆;  与函数参数、变参函数函数调用、函数指针相关的一些难理解和容易被理解错的知识点解析;  文件和指针的使用原则、技巧和注意事项;  函数调用和异常处理的注意事项和最佳实践;  与strlen、sizeof、const、volatile、void、void*、#define、typedef、realloc、malloc、calloc等相关的一些陷阱知识点的解析;  时间复杂度、冒泡排序法、选择排序法、快速排序法、归并排序法、顺序排序法、二分查找等常用算法的详细讲解;  良好的编码习惯和编程风格。
二分查找是一种常用的查找算法,可以在有序的数组中快速查找目标元素,其时间复杂度为 O(log n)。在实现二分查找时,可以采用递归或非递归的方式。 递归实现二分查找的过程: 1. 如果 low > high,则表示查找失败,返回 -1。 2. 如果 low <= high,则计算 mid = low + (high - low) / 2,即中间位置的下标。 3. 如果 arr[mid] 等于目标元素 target,则直接返回 mid。 4. 如果 arr[mid] 大于目标元素 target,则在前半部分继续查找,即递归调用 binarySearch(arr, low, mid - 1, target)。 5. 如果 arr[mid] 小于目标元素 target,则在后半部分继续查找,即递归调用 binarySearch(arr, mid + 1, high, target)。 非递归实现二分查找的过程: 1. 如果 low > high,则表示查找失败,返回 -1。 2. 如果 low <= high,则计算 mid = low + (high - low) / 2,即中间位置的下标。 3. 如果 arr[mid] 等于目标元素 target,则直接返回 mid。 4. 如果 arr[mid] 大于目标元素 target,则在前半部分继续查找,即将 high 更新为 mid - 1。 5. 如果 arr[mid] 小于目标元素 target,则在后半部分继续查找,即将 low 更新为 mid + 1。 6. 重复步骤 1~5,直到找到目标元素或查找失败。 递归和非递归实现的二分查找算法本质上是一样的,只是实现方式不同。相对而言,递归实现简单易懂,但由于递归调用可能会产生大量的函数调用开销,因此在处理大规模数据时,非递归实现更为高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream_Chaser~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值