前言

在前面的文章中我们提到过C允许函数调用其自己调用自己,而这种调用便是我们今天主角——递归(recursion)。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义时有直接或者间接调用自身的一种方法,它通常把一个大型复杂问题层层转化为一个与原问题相似但规模较小的问题来求解,使用递归只需要少量的代码就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思想——大事化小

一、递归引入

我们先通过一个例子来进入递归的学习。该函数在主函数中调用fun()函数,这次调用称为“第一级递归”,然后fun()函数调用自己,这次调用称为“第二级递归”,之后过程以此类推。在该程序中共有四级递归。为了进一步了解递归的过程,我们引入一个变量n,显示它的值与地址。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void fun(int n)
{printf("Level %d:n location %p\n", n, &n);   //#1if (n < 4)fun(n + 1);printf("LEVEL %d:n location %p\n", n, &n);   //#2
}
int main()
{fun(1);return 0;
}

程序输出为:

Level    1:n location 00B7FCE8
Level    2:n location 00B7FC10
Level    3:n location 00B7FB38
Level    4:n location 00B7FA60
LEVEL  4:n location 00B7FA60
LEVEL  3:n location 00B7FB38
LEVEL  2:n location 00B7FC10
LEVEL  1:n location 00B7FCE8

接下来我们输入地分析上述程序是如何工作的。 首先主函数main()调用了带参数1的fun()函数,执行结果是fun()中的形参为1,故打印语句#1 打印Level 1。然后由于n<4,fun()(第一级)调用实际参数为n+1的fun()函数(第二级),其形参仍然小于4继续打印语句#1 打印Level 2。以此类推,下面两次调用打印的分别是Level 3及Level 4。

当执行到n=4时(此时处于第四级),在执行完打印语句#1后,if的判断条件为假,不再调用fun()函数,而执行打印语句#2,打印完后程序返回到第三级,继续执行第三级后面的语句,在执行完第三级的程序后返回第二级,以此类推,直到返回到主函数中。

下图更为直观地展现了程序的走向。

二、递归的基本要素

在我们初次接触递归时可能会觉得递归有些难以理解,有些绕不过来,其实简单来说递归就包含三个要素:①使用递归编写该函数的作用是什么  ②递归结束条件 ③写出等价函数关系。我们用一个简单的例子来讲解我们是怎么来编写递归函数的。

1、要素一:明确该递归函数的目的

我们编写一个函数首先应该明确的便是它的功能是什么,编写它来解决我们一个怎样的问题。我们编写一个函数来求解n!定义函数如下:

//求解n!
int fun(int n)
{}

在明确了其功能后我们来看看其第二要素

2、要素二:递归终止条件

一个递归函数必须包含能让递归调用停止的语句,否则,函数会一直调用自己,进入一个无底洞。会引起栈溢出错误(StackOverflowError)。通常,在编写递归函数时我们经常使用if或其他等价的判断条件在形参等于某个特定值时终止递归,因此这也提醒我们每次调用时形参必须不同,形参需要发生改变。

当n的值等于0或者1时,它的阶乘为1,根据要素二我们继续完善代码:

//求解n!
int fun(int n)
{if (n == 0 || n == 1){return 1;}
}

当然递归的结束条件不唯一。

3、要素三:写出等价函数关系

这个要素的要点是,通过不断缩小参数范围,缩小之后,我们可以通过一些变量写出的式子,使原函数的结果不变。

例如缩小n!,我们可以写成n!=n*(n-1)!。这样范围就变成了n-1,范围变小了,为了使函数保持不变我们乘了一个n。因此,我们找的等价关系为:fun(n)=n*fun(n-1)。

熟练这个步骤需要更多的练习才能熟练掌握。

根据要素三我们进一步完善代码:

//求解n!
int fun(int n)
{if (n == 0 || n == 1){return 1;}if (n >= 2)return fun(n - 1) * n;
}

到此,这个递归函数就完成了。熟练地写出递归函数需要一个漫长的练习过程。在后面的文章中会讲解经典的使用递归求解的问题。

三、递归与循环的区别与联系

在学习递归与循环区别之前,我们先对一个知识点进行补充,以便更好地理解上文所说的栈溢出。

1、存储区域

C语言与内存息息相关,在C语言中拥有三种不同的内存池,分别为:静态区(static)、栈区(stack)、堆区(heap)。

静态区(static):全局变量存储,在程序的整个生命周期都存在。

栈区(stack):局部变量存储(自动、连续的内存)

堆区(heap):动态存储(非常大的内存池,非连续分配)

a.静态区

静态内存在程序的整个生命周期都存在,而且通用用来存储全局变量,通过static关键字创建变量。如下:(注意:其定义在函数外部)

int sum;

在很多系统中这个变量占用4byte的内存。全局变量是静态的,所以它的整个程序中只有一次拷贝。我们也可以通过static将局部变量强制转化为全局变量。如下:

static int sum;

b.栈区

栈用来存储函数内部的变量(包括main()函数),它是一个LIFO(Last In Last Out—后进先出)的结构,在数据结构专栏中我们会详细介绍。每当一个函数声明一个新的变量它将被压入栈中。每当一个函数运行结束后,这个函数所有在栈中相关的变量都将被删除,而且它们所占用的内存也会被释放,这就产生了局部变量。栈内存在函数调用时分割为连续的帧。

但是栈区的大小通常都有一个限制,根据操作系统的不同而不同。如果一个程序尝试将过多的信息放入栈内时就会发生我们上文所说到的栈溢出。

c.堆区

堆是一个相对于栈的概念。堆是一个可以动态使用的大内存池。这一部分系统不能自动管理,你必须显示地申请(如使用malloc之类的函数)和释放内存(如free)。在变量使用结束时不释放内存或者释放内存失败会造成内存泄漏,该内存仍然被占用。与栈区不同的是堆区除了物理内存的大小限制之外通常没有其他大小限制。堆区内存的访问需要使用指针。

内存使用案例:

#include<stdio.h>
#include<stdlib.h>int x;int main()
{int y;char* arr;y = 1024;printf("stack memory:%d\n", y);arr = malloc(100 * sizeof(char));arr[0] = 'a';printf("heap memory:%c\n", arr[0]);free(arr);return 0;
}

2.递归与循环

C语言是一门面向对象编程语言,而对象包括数据和操作,而递归与循环是非常相似的操作,但它们都有适合自己操作的数据。递归与循环本质上都是代码的重复使用。

递归与循环的不同点:

(1)程序规模:递归的规模很小,且受到系统栈大小的限制;而循环规模很大,几乎不会受到限制。

(2)重复单位:递归的重复单位为函数;循环的重复单位为语句。

(3)问题解决方向:递归往往是自上而下,通过将问题的规模不断减小来解决;而循环可以是自上而下也可以自下而上,但通常是自下而上。

等等。

四、总结

要熟练地使用递归来解决问题需要我们进行更多的练习,把握递归的核心思想。

【C语言】函数番外篇——递归相关推荐

  1. 微信红包程序c语言,C语言实战番外篇——模拟微信抢红包

    2019.12.3 如图为运行效果 抢红包.png 输入的名字的时候即可中文也可英文 1.输入中文:打完拼音后按下空格键即可 2.输入英文需两次回车,也可先shift,再输入,再回车 该程序用到的知识 ...

  2. 应用统计学与R语言实现笔记(番外篇四)——bookdown使用与OR值计算

    本期是之前做的应用统计学与R语言实现笔记的番外篇四,本期主要关注两个问题,一个是重新利用R的bookdown包创建新的电子书,另一个是计算公共卫生当中一个比较常见的指标OR值. 文章目录 1 book ...

  3. clang static analyzer源码分析(番外篇):RegionStore以及evalCall()中的conservativeEvalCall

    引子 我们在上一篇文章<clang static analyzer源码分析(番外篇):evalCall()中的inline机制>中提及了clang如何创建CallGraph,如何进行函数i ...

  4. Java番外篇1——正则表达式

    Java番外篇1--正则表达式 1.什么是正则表达式 正则表达式定义了字符串的模式 正则表达式可以用来搜索.编辑或处理文本 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别 2.正则表达式 ...

  5. 你所能用到的数据结构之番外篇---逆袭的面向对象(一)

    对于番外篇,我深刻能明白在大多数人眼里就和电视剧的广告一样,说实话,我也不喜欢这种感觉,因为这样会让人觉得是在欺骗消费者啊~~~阿西巴~~~但是我实在发现如果不在这里对面向对象来个入门级的介绍,后面的 ...

  6. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  7. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    转载自:https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程:给深度学习入门者的Python快速教程 - 基础篇 给深度学习入门者的Python快速教程 - ...

  8. #3使用html+css+js制作网页 番外篇 使用python flask 框架 (I)

    #3使用html+css+js制作网页 番外篇 使用python flask 框架(I 第一部) 0. 本系列教程 1. 准备 a.python b. flask c. flask 环境安装 d. f ...

  9. 安卓开发学习日记第四天番外篇_用Kotlin炒冷饭——越炒越小_莫韵乐的欢乐笔记

    安卓开发学习日记第四天番外篇--用Kotlin炒冷饭--越炒越小 前情提要 安卓开发学习日记第一天_Android Studio3.6安装 安卓开发学习日记第二天_破坏陷阱卡之sync的坑 安卓开发学 ...

最新文章

  1. 程序员的朋友圈应该是这样的。。。
  2. 学习如何写 Bug 的一天! | 每日趣闻
  3. Delphi 2009 泛型容器单元(Generics.Collections)[1]: TListT
  4. gin自定义日志格式
  5. JavaSE----基础语法(方法)
  6. root cause of message type unknown during BP delta upload
  7. matlab逆变换法产生随机数_matlab数值积分方法(一)
  8. 大学生php实训心得1500_【有奖征文】第五届大学生国际学术研讨会
  9. html5语异性元素,异性的5句性暗示
  10. checkbox已设置为checked--true-但不勾选问题解决方法(只第一次勾选有效)
  11. mysql统计信息表行数不准确_mysql SQL调优-统计信息不准的原因
  12. Python交互界面方向键、退格键乱码
  13. 程序员开发的“逆天”软件
  14. phpSQLiteAdmin - 基于Web的SQLite数据库管理工具 - OPEN 开发经验库
  15. 计算机网络——网络安全
  16. 智慧路灯杆系统车流量监测解决方案
  17. 文档服务器 件排名,全国服务器排名
  18. 民数记研读1——于宏洁
  19. eclipse 缓解眼睛疲劳保护眼睛
  20. linux 下载工具

热门文章

  1. Oracle(11g)数据库安装详细图解教程
  2. 格灵深瞳将登陆科创板募资18亿,AI天才未来能否走出巨亏困局?
  3. jQuery扁平化风格手风琴菜单
  4. 地图比例尺学习、超图最小可见比例尺和最大可见比例尺
  5. HP2132打印机简易说明
  6. Elastic App Search:免费的产品,可提供出色的搜索体验
  7. Python之路----文件操作
  8. 心里窝火无语的图片_无语的说说心情、图片【12句】
  9. ebs 编译无效对象——adutlrcmp.sql not giving promt
  10. msp430g2553流水灯