C语言学习系列-->【函数的递归】

简介: C语言学习系列-->【函数的递归】

前言

小编怀着激动的心情编写本篇小博客,因为我要介绍的是递归——一种优雅的问题解决方法。递归将人分成三个截然不同的阵营:恨它的、爱它的以及恨了几年后又爱上它的。

希望各位读者在阅读小编的文章后,可以深刻理解递归思想。

观图有感

为了让读者形象地认识到递归,先看一组漫画。

1、假设你在玩密室逃脱时,发现一个宝箱

2、NPC告诉你,钥匙很可能在下面这个盒子里。

3、这个盒子里有盒子,而盒子里的盒子又有盒子。钥匙就在某个盒子中。为了找到钥匙,苦逼的你尝试了不同的方法:

第一种方法:

(1)创建一个要查找的盒子堆。

(2) 从盒子堆取出一个盒子,在里面找。

(3) 如果找到的是盒子,就将其加入盒子堆中,以便以后再查找。

(4) 如果找到钥匙,则大功告成!

(5) 回到第二步。

第二种方法:

(1) 检查盒子中的每样东西。

(2) 如果是盒子,就回到第一步。

(3) 如果是钥匙,就大功告成!

在代码中,我们会发现,第一种方法就是while循环,第二种方法时函数调用自己。

两个方法都不差,但是欢Leigh Caldwell在Stack Overflow上说的一句话:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要”。

4、最终,你终于拿到钥匙,逃出密室。

一、概述

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

举一个史上最简单的递归例子:

#include <stdio.h>
int main()
{
  printf("hehe\n");
  main();//main函数中⼜调⽤了main函数
  return 0;
}

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

递归思想就是将大事化小的过程。

二、递归的限制条件

由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致

无限循环。

递归在书写的时候,有2个必要条件:

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

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

三、递归的代码实现

例1:求n!

公式:n! = n ∗ (n − 1)!

C code

# define _CRT_SECURE_NO_WARNINGS
#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 ans = fact(n);
  printf("%d", ans);
  return 0;
}

红色箭头表示递推,只是将大事化小,并没有计算出结果;绿色箭头表示回归,计算结果返回上一级。

例2:顺序打印⼀个整数的每⼀位

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

C code

# define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void Print(int n) {
  if (n > 9) {
    Print(n / 10);
  }
  printf("%d ", n % 10);
}
int main() {
  int n;
  scanf("%d", &n);
  Print(n);
  return 0;
}

Print(n)
如果n是1234,那表⽰为
Print(1234) //打印1234的每⼀位
其中1234中的4可以通过%10得到,那么
Print(1234)就可以拆分为两步:
1. Print(1234/10) //打印123的每⼀位
2. printf(1234%10) //打印4
完成上述2步,那就完成了1234每⼀位的打印
那么Print(123)⼜可以拆分为Print(123/10) + printf(123%10)

以此类推

Print(1234)
==>Print(123)                      + printf(4)
==>Print(12)            + printf(3)
==>Print(1)   + printf(2)
==>printf(1)

四、递归与迭代

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

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

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

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

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

有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可⽽⽌就好。


目录
相关文章
|
24天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
48 10
|
24天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
43 9
|
24天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
43 6
|
24天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
166 6
|
24天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
52 6
|
4月前
|
机器学习/深度学习 C语言
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
要保持最小的步数,每一次汉诺塔问题(无论是最初还是递归过程中的),如果此时初始柱盘子数为偶数,我们第一步是把最上面的盘子移动到中转柱,如果为奇数,我们第一步则是将其移动到目标柱。
105 0
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
|
7月前
|
C语言
C语言递归问题【青蛙跳台阶】和【汉诺塔】
C语言递归问题【青蛙跳台阶】和【汉诺塔】
|
C语言
【C语言】用函数递归的方法解决汉诺塔问题
【C语言】用函数递归的方法解决汉诺塔问题
84 0
|
C语言
【C语言】递归详解汉诺塔问题
【C语言】递归详解汉诺塔问题
261 0
【C语言】递归详解汉诺塔问题
|
C语言
C语言经典递归题目 -- 汉诺塔问题
C语言经典递归题目 -- 汉诺塔问题
204 0
C语言经典递归题目 -- 汉诺塔问题