函数递归的相关知识点

1.什么是递归

递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。

写⼀个史上最简单的C语⾔递归代码:

上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问 题,代码最终也会陷⼊死递归,导致栈溢出(Stackoverflow)。

调试一下:

栈溢出:

每一次函数调用都会占有一块内存空间:函数栈帧(运行时堆栈),其是在栈区上申请的空间。当其放满时,就溢出了。

所以,函数是不可以这样无限制递归下去的,递归必须要有条件。

2.递归的思想

把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再 被拆分,递归就结束了。

所以递归的思考⽅式就是把⼤事化⼩的过程。

递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。

3.递归的限制条件

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

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

4.递归举例1:求n的阶乘

⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。 ⾃然数n的阶乘写作n!。

题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

分析和代码实现:

我们知道n的阶乘的公式: n ! = n∗(n−1)!

这样的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。

当 n==0 的时候,n的阶乘是1(数学定义),其余n的阶乘都是可以通过公式计算。

n的阶乘的递归公式如下:

 过程:

其中:

1.递归条件:n>0,递归停止条件:n=0,

2.不断的逼近跳出条件

5.递归举例2:顺序打印⼀个整数的每⼀位

输⼊⼀个整数m,打印这个按照顺序打印整数的每⼀位。

⽐如:

输⼊:1234

输出:1 2 3 4

分析和代码实现

这个题⽬,放在我们⾯前,⾸先想到的是,怎么得到这个数的每⼀位呢?

如果n是⼀位数,n的每⼀位就是n⾃⼰

n是超过1位数的话,就得拆分每⼀位

1234%10就能得到4

然后1234/10得到123

这就相当于去掉了4 然后继续对123%10,就得到了3

以此类推不断的 %10 和 /10 操作,直到1234的每⼀位都得到;但是这⾥有个问题就是得到的数字顺序是倒着的。

我们假设想写⼀个函数Print来打印n的每⼀位,如下表⽰:

 以此类推:

 直到被打印的数字变成⼀位数的时候,就不需要再拆分,递归结束。

在这个解题的过程中,我们就是使⽤了⼤事化⼩的思路

把Print(1234) 打印1234每⼀位,拆解为⾸先Print(123)打印123的每⼀位,再打印得到的4

把Print(123) 打印123每⼀位,拆解为⾸先Print(12)打印12的每⼀位,再打印得到的3

直到Print打印的是⼀位数,直接打印就⾏ 

6.递归与迭代

递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公 式,很容易就被写成递归的形式。

Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。

在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间 的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。

函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归 函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。

所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢 出(stackoverflow)的问题。

所以如果不想使⽤递归就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。 ⽐如:计算n的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。

事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰, 但是这些问题的迭代实现往往⽐递归实现效率更⾼。

当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运 ⾏时开销。

(有时候,一个代码,虽然递归非常不好想,但是,只要递归想到了,用递归写代码就会非常简单,就几行就写完了,但是如果是非递归,就要写好多行)

但是如果递归的不恰当书写,就会导致不可估量的后果,此时我们还是要放弃递归。

7.举例3:求第n个斐波那契数

斐波那契数列:

1 1 2 3 5 8 13 21 34 ……

第n个斐波那契数:

看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:

当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的, 这也说明递归的写法是⾮常低效的,那是为什么呢?

首先,我们通过以下代码,计算一下第40个斐波那契数在计算时,对第三个斐波那契数重复的次数:

所以,对于第50个斐波那契数计算时进行分析: 

 其中有大量的重复计算,⽽且递归层次越深,冗余计算就会越多。

修改一下,会发现代码运行速度特别快:

本篇文章中所涉及到的代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
计算阶乘
//int Fact(int n)
//{
//	if (n == 0)
//		return 1;
//	else//n>0
//		return n * Fact(n - 1);
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);//4   24    //5  120
//	int ret = Fact(n);
//	printf("%d\n", ret);
//	return 0;
//}

顺序打印⼀个整数的每⼀位
//void Print(int n)//1234
//{
//	if (n > 9)
//	{
//		Print(n / 10);//123
//		//printf("%d ", n % 10);//4
//	}
//	/*else
//	{
//		printf("%d ", n % 10);
//	}*/
//	printf("%d ", n % 10);
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	Print(n);
//	return 0;
//}

换成迭代的方法
//int Fact(int n)
//{
//	int i = 0;
//	int ret = 1;
//	for (i = 1; i <= n; i++)
//	{
//		ret *= i;
//	}
//	return ret;
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	int ret = Fact(n);
//	printf("%d\n", ret);
//	return 0;
//}

斐波那契数列
//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;
//}

计算次数
//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;
	while (n >= 3)
	{
		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;
}

(学习内容无偿分享,如果觉得有用的话,麻烦各位大佬三连!就是最大的支持啦!)

【问题描述】 【问题描述】编写函数f,功能是用递归的方法求斐波那契数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f求出斐波那契数列的第n项,并在主函数中输出。 斐波那契数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法求斐波那契数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f求出斐波那契数列的第n项,并在主函数中输出。 斐波那契数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法求斐波那契数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f求出斐波那契数列的第n项,并在主函数中输出。 斐波那契数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法求斐波那契数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f求出斐波那契数列的第n项,并在主函数中输出。 斐波那契数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 斐波那契数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8
生成哈夫曼编码(Huffman Coding)通常涉及构建一棵二叉树,其中叶子节点代表字符,非叶节点的权值为其子节点权重之和。这是一种用于数据压缩的无损编码方法。递归算法可以帮助我们高效地完成这一步。 下面是使用递归实现哈夫曼编码的一个关键步骤——构造哈夫曼树的过程: 1. **创建初始集合**:开始时,集合里包含所有待编码的字符及其出现频率作为键值对。 2. **选择最小元素**:从集合中选取频率最低的两个元素,合成一个新的节点,其值等于这两个节点的频率之和。新节点成为当前集合的新成员。 3. **递归调用**:继续上述过程,直到只剩下一个节点,这就是哈夫曼树的根节点。 4. **分配编码**:从根节点开始,向左分支走记为0,向右分支走记为1,直到到达叶子节点,形成一个路径,这就是该字符的哈夫曼编码。 递归函数可能如下所示: ```c typedef struct Node { char data; int freq; struct Node* left; struct Node* right; } HuffmanNode; void buildHuffmanTree(HuffmanNode** nodes, int count, HuffmanNode** result) { if (count == 1) { *result = *nodes; return; } HuffmanNode* newNode = malloc(sizeof(HuffmanNode)); newNode->freq = nodes[0]->freq + nodes[1]->freq; newNode->left = nodes[0]; newNode->right = nodes[1]; nodes[0] = newNode; // 更新集合 // 递归调用,处理新的集合 buildHuffmanTree(nodes, count - 1, result); } // ... 其他辅助函数来获取哈夫曼编码 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值