C语言修炼秘籍之——函数递归

☆ Welcome to House's blog~ ☆

本人主页:神王豪斯(重拾基础期)-CSDN博客

所属专栏:重拾C语言——神王降世的第一步!_神王豪斯(重拾基础期)的博客-CSDN博客

文章特点:一些我认为比较重要的内容都有加粗处理,大家放心食用~

本人是一名不太聪明的计科专业学生,将会在这里记录我的代码学习进程,如果文章中有哪些地方说的不对或者代码有哪些可以改进的地方就有劳大家打在评论区啦!那咱们废话不多说,直接开始吧!

1、函数递归的作用与含义

1.1 函数递归有什么作用?

函数的递归是一种十分奇妙的东西,它不像是一种简单的编程技巧,也不像是孤立的代码片段,它更像是一种思维,在我们处理一些复杂问题时起到化繁为简的效果。它主要具有以下几个作用:

 1. 简洁地解决具有重复性结构的问题:对于一些问题,如果它们具有重复的子结构,使用递归可以以一种相对简洁和直观的方式来表达解决方案。

例如:树和图的遍历、汉诺塔问题等场景中,递归能够清晰地描述问题的解决过程。

2. 分治法的实现:将一个复杂的大问题分解为规模较小、结构相似的子问题,通过递归地解决子问题来解决原问题。

例如:归并排序、快速排序等排序算法中,通过将数组不断地分割成子数组,对每个子问题进行排序,最终完成整个数组的排序。

3. 易于理解和编码:对于某些具有递归性质的问题,使用递归的方式去思考和实现代码,在逻辑上更容易理解和实现,代码也更加简洁明了,能更好地反映问题的本质结构。

4. 数学问题的求解:在解决一些数学相关的问题,如:计算斐波那契数列、阶乘等。

1.2 什么是函数递归?

函数递归是指在函数的定义中调用函数自身的方法。

也就是说,一个函数在其函数体内部直接或间接调用了自身。当某个问题可以被分解为具有相同逻辑结构但规模更小的子问题时,常常会采用递归的方式来解决。

我们可以先看一个最最简单的函数递归:

#include<stdio.h>

int main()
{

    main();

    return 0;

}

如上图所示,我们在main函数中又调用了main函数,因此当程序开始运行到函数体中的main()时又会因为调用了自己再次从头开始运行。

你也许会想:“这样不就死循环了吗?必然会出问题的呀!”

你是对的,在我们真的在编译器上运行后,系统会给我们紧急报错——栈溢出(后面会出一个有关栈溢出内容的文章,点个关注敬请期待呀~)

但至少我们对这抽象的四个字——函数递归已经有了一个初步的认识。

2. 递归思想

2.1思想简述

就像我们刚刚提到的,函数递归思想是指将一个复杂问题逐步分解为规模更小结构相同或相似子问题,通过不断调用自身函数来解决子问题,直到达到某个基础条件或边界条件,从而解决原始问题的思想方法。

在递归思想中,问题的解决过程被转化为一个重复调用自身函数的过程,每个子问题的求解都依赖于更小规模的子问题的求解结果,这个向下传的过程便是我们的“”’。通过不断缩小问题规模,最终在基础条件下得到直接的结果,再逐层回溯得到整个问题的解,这个过程便是我们的“”。

哦~这下明白了,如果要防止陷入死循环的糗境,我们需要在写递归代码有两个必要条件:

a.需要有个边界条件

b.函数递归的过程需要是不断靠近边界条件的过程

接下来就让我们通过几个例题来细细体会递归的美妙~

3. 例题呈现        

3.1  求n的阶乘

⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作n!。
举例:5!=5*4*3*2*1
           4!=4*3*2*1
           ...(以此类推)

3.1.1 题目分析与代码实现

我们都清楚n ! = n * (n-1)... * 2 * 1 = n * (n-1) !

由此我们便可搭出用递归思想解决题目的大致框架:

根据框架图,我们便可以写出相应的代码:

int Factorial(int n)
{
    if(n==0)
    {
        return 1;
    }
    return n*Factorial(n-1);
}

写完后让我们来RUN一遍吧!

#include <stdio.h>

int Factorial(int n)
{
     if(n==0)
        {
            return 1;
        }
         return n*Factorial(n-1);
}


int main()
{
 int x = 0;

 scanf("%d", &x);

 int ret = Fact(n);

 printf("%d\n", ret);

 return 0; 
} 

  

"这函数一写完确实是一下就算出结果来了啊!可它的执行过程具体是怎么样的还是很好奇..."

那我们就看一下算 4!是如何进行的吧!

上图便是进入函数后的大致流程,可以看出,当下一级的数值还没计算出时,这一步的运算会先搁置,直到最后一级的数被算出来了,再一步步地往回传数值从而计算出结果。

当然,也许你会说“搞这么复杂???我直接一手for循环就解决战斗了啊牢底 ლ(′◉❥◉`ლ)”

你说的确实不错,但这里就涉及到递归与迭代之间的关系了。

4. 递归与迭代

“递归你刚说完我差不多了解了,迭代是个什么东西?”

-迭代是一种重复反馈过程的活动,目的是为了逼近所需的目标或结果。在计算机编程中,迭代是通过重复执行一段代码(如循环结构),在每次循环中对变量进行更新操作,逐步接近问题的解。

说白了,迭代就是循环,我们之前学过的for循环,while循环都是迭代的应用~

4.1 二者的区别

4.1.1 代码可读性

 -递归:在解决某些问题(如树形结构的遍历)时,递归的代码逻辑可能更加清晰和直观,更容易理解问题的本质和解决思路。

 -迭代:对于一些复杂的递归问题,如果递归深度过大或者逻辑过于复杂,可能会导致代码难以理解和调试;而迭代通过循环结构和变量更新来解决问题,在一些情况下代码的可读性可能更好。

4.1.2 性能

 -递归:由于函数调用需要分配新的栈空间来保存函数的局部变量、返回地址等信息,多次递                      归调用可能会导致栈空间的大量消耗,甚至出现栈溢出的错误。并且函数调用也会带                      来一定的性能开销,因此在性能上,递归可能不如迭代高效。

 -迭代:不需要反复的函数调用和栈空间分配,执行效率通常比递归高。

由此看出,二者之间的区别使得他们不同的应用场景

4.1.3 应用场景

-递归:用于处理树和图的遍历、分治策略问题、数学计算等具有天然递归性质或依据递归定义的问题。

-迭代:解决大规模数据问题避免栈溢出、对性能要求高、问题逻辑简单用循环易理解实现的情况。

4.1.4 生动的比喻

做一个生动的比喻可能能够帮助你更好的理解二者间的关系与区别:

我们可以把解决一个问题的过程想象成攀登一座山峰到达宝藏的位置。

递归就像是一个勇敢但有点一根筋的探险家,他坚信自己可以通过不断地“召唤分身”来解决问题。比如他站在一个地方,发现前面有几个岔路,他就会“召唤”出几个自己(也就是进行函数自身调用),分别去探索每一条路。每个被召唤出来的“分身”遇到新的岔路又会继续召唤新的“分身”,直到某一个“分身”走到没有岔路的尽头(达到递归的终止条件),才开始陆续返回并报告探索结果。

迭代则像是一个沉稳且脚踏实地的徒步者。他沿着山路一步一步地前进,每走一段路就根据当前的情况和目标调整自己的步伐和方向(更新循环变量)。他不会“分身”,只是靠着自己一步一个脚印,持续稳定地朝着宝藏的方向前进,直到最终到达目的地。

5. 总结

函数递归是编程世界中一项强大而迷人的技术,它既能够优雅地解决复杂的问题,又能让我们以独特的视角去理解和处理数据结构与算法。然而,正如一把双刃剑,递归在带来简洁与高效的同时,也伴随着性能和内存消耗的挑战。因此我们要牢记记住,在实际应用中,需要根据具体的问题和场景,权衡递归与其他方法的利弊,做出最合适的选择~

十分感激你看到这里,如果本篇内容对你有所启发的话麻烦点个赞吧(虔诚脸),你的点赞、收藏、关注都是对我莫大的鼓励~

我后续还会根据这篇内容更新有关递归经典题目——斐波那契数列以及青蛙跳台阶问题,敬请期待!

再次感谢你的观看,让我们下次再见!

​​​​

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值