【C语言学习笔记】再次深入理解递归——总结设计易错点

2023-10-30

写在前面

其实我也说不太清楚到底递归算不算算法,因为我一开始从0基础接触递归是从《算法图解》这本书中得知的(也很推荐刚学算法的朋友可以先看看这本书,写的挺不错的),也就把它当成算法了。但写了那么多题目,渐渐的感觉递归这个东西把,它更像是一种工具而已,要用的时候很自然而然地就和加减乘除一样用上了,不会特别感受得到它地存在(也正是因为理解地不深刻,导致我经常用错陷入死循环)。


再次深入理解递归

回想起第一次接触递归是在这道题上母牛的故事——函数与类斐波那契数列,其实当时在写这道题时真的没有刻意去说 啊 我要用递归,只不过考到了类斐波那契数列,感觉如果用函数会思路会更清晰简洁一点。

我们现在再回顾一下这个题目的核心代码

int cow(int n)
{
	if ( n<=4 ) return n;
	else return cow(n-1) + cow(n-3);
}

虽然只有五行代码,但却囊括了递归的两大基本要素,即基线条件和递归条件,其实我觉得第一个名字有点绕,或者说不好理解。

我更倾向于把基线条件理解为“结束条件”,递归条件就比较好理解了,就是递归的主体部分。如上面中

if ( n<=4 ) return n;

是基线条件,当n小于等于4就跳出递归,而

else return cow(n-1) + cow(n-3);

就是递归条件啦,在没有达到结束底线的时候就执行它,得到答案。

也许你会说,这个直接在main函数里面通过while循环也能实现啊,是的,这也印证了Leigh Caldwell在Stack Overflow上说的一句话:

“如果使用循环,程序的性能可能会更高,如果使用递归,程序可能更容易理解,如何选择要看什么对你来说更重要。”

还是这道题,我们看下整个代码

#include <stdio.h>

int cow(int n)
{
	if ( n<=4 ) return n;
	else return cow(n-1) + cow(n-3);
}

int main(void)
{
	int n;
	while ( ~scanf("%d", &n) )
	{
		if ( n==0 ) break;
		printf ( "%d\n", cow(n) );
	}
	

	return 0;

}

可以看出,用了递归函数后整个程序显得结构分明,十分利于理解,如果把递归用while或者for循环写到main里面以打表的形式(还可以用滚动数组的方式优化打表的空间复杂度)算结果也不是不行,且在性能上,如果有多组输入输出的话,循环会更快(因为递归每次都有从大数据一直算算算算到基线条件)但只是阅读性差了点而已。


设计递归的易错点

在深入理解完递归后,我们重点关注下在实际应用时的易错点,因为到最后都要学以致用,实践才是唯一标准。

下面是我个人在用递归时的几个常见错误,均有例题解说以加深印象:

错误一:分裂基线条件和递归条件

母牛的故事——函数与类斐波那契数列

对,还是这道题,这道题上收录了一开始我写错的代码,现在拿出来分析下

#include <stdio.h>

int cow_num( int year );

int main()
{
	int y;
	while ( ~scanf("%d", &y) )
	{
		if ( y==0 ) break;
		if ( y<=4 )
			printf ( "%d\n", y );
		else
			printf ( "%d\n", cow_num(y) );
	}
	

	return 0;

}

int cow_num( int year )
{
	int num;
	num = cow_num(year-1) + cow_num(year-3);
	return num;
}

通过对比可以很明显的看出这里我把基线条件写到main函数里面了,导致递归缺少跳出的条件,根本的不出答案。

错误二:忘记写基线条件

过河问题——动态规划的经典线性模型

这个和上一个错误有点像,但这个更致命,连跳出条件都忘记写了,会陷入死循环的,调试时直接崩掉死机。

int DP(int i)
{
	//打表前几个数据 
	dp[1]=a[1];
	dp[2]=a[1];
	dp[3]=a[1]+a[0]+a[2];
	dp[i] = min ( ( DP(i-1) + a[0] + a[i] ),( DP(i-2) + a[0] + a[i] + 2*a[1] ) );
	return dp[i];
}

上面这个就是没有基线条件,dp(i-1)会一直执行,从4,3,2,1,0,-1,-2,-3,-4…永无止境的循环下去。

绝对值排序——快速排序

这道错题也是,在写快速排序的时候忘记写结束递归的条件了,导致快排无法实现。

错误三:找不准递归条件

跳台阶——超时、递归、斐波那契

这点就很扎心了,其实整个递归最核心的部分就是递归条件,这个式子一旦列错那这道题肯定就解不出来,有时候可能递归着递归着自己就被绕进去了,深陷其中无法自拔。上面这个跳台阶就是例题之一,真的有点晕,找不准或者说不确定这是不是递归条件。有时候又可能出现多递归一次或少递归一次的情况(但这个往往都是和下标从0开始数还是1开始数有关)。

一半这种情况的解决方案,我是更倾向于还是老老实实地拿起笔和纸在草稿纸上老老实实地画吧,把前面几组的数据自己先算一算把,假若还是理解不了,实在不行找规律也行呀(笑)。


写在后面:

第100篇博客啦!本来想在这个有纪念意义的时刻写点其他东西的,但还是写了技术博客,也不侨情做做了,以后继续努力,坚持输入和高质量的输出把。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C语言学习笔记】再次深入理解递归——总结设计易错点 的相关文章

随机推荐

  • Markdown格式文档图片设置居中

    1 使用div设置对齐方式 div align center img src 图片路径 div 此处的center可以更换 left 左对齐 right 右对齐 center 居中 下图示例 div align center img src
  • 《算法》第二章——快排非递归实现

    思路 其实就是用栈保存每一个待排序子串的首尾元素下标 下一次while循环时取出这个范围 对这段子序列进行partition操作 代码 include
  • 如何保证代码质量

    代码质量的评估维度很多 我自己的理解有这几个层次 能用 gt 能读 gt 能改 gt 能适应业务的变更 高质量的代码不是一蹰而就的的 是从特别小的细节例如变量命名规则到高大上的架构设计 一点点积累而成的 关于架构设计的部分 正在阅读 重构
  • 13、不同存储引擎的数据表在文件系统里是如何表示的?

    MySQL 支持 InnoDB MyISAM Memory Merge Archive CSV BLACKHOLE 几种存储引擎 不同存储引擎的数据表在文件系统中的表示也各不相同 MySQL 中的每一个数据表在磁盘上至少被表示为一个文件 即
  • 融云出海:社交泛娱乐出海,「从 0 到 1」最全攻略

    9 月 21 日 融云直播课社交泛娱乐出海最短变现路径如何快速实现一款 1V1 视频社交应用 欢迎点击上方小程序报名 本期我们翻到 地图 的实践篇 从赛道 品类选择 目标地区适配 用户增长 变现模式 本地化运营 跨国团队管理等方面完整描绘
  • 用matlab使用灰狼算法规划15个城市的最短路径

    在Matlab中使用灰狼算法规划15个城市的最短路径需要以下步骤 建立矩阵 首先 您需要建立一个矩阵来存储15个城市之间的距离 定义灰狼算法参数 然后 您需要定义灰狼算法的各种参数 例如种群数量 迭代次数 学习因子等 运行灰狼算法 接下来
  • "当B发生时,是A发生的概率降低了,可以由此推出,当B不发生时A发生的可能性增大了"的直观解释

    一 当B发生时 是A发生的概率降低了 可以由此推出 当B不发生时A发生的可能性增大了 数学上的推导是容易的 即 二 接下来找一种直观上的解释 设有一个矩形的面积为1 设其为事件 发生的概率 A发生的概率即为A的面积 A B同时发生的概率即为
  • 【数据结构】线性表的链式存储结构简单实现及应用

    链表是指用一组任意的存储单元来依次存放线性表的结点 这组存储单元即可以是连续的 也可以是不连续的 甚至是零散分布在内存中的任意位置上的 因此 链表中结点的逻辑次序和物理次序不一定相同 为了能正确表示结点间的逻辑关系 在存储每个结点值的同时
  • android 启动过程分析

    Servicemanager需要先启动 zygote后面的service需要用到servicemanager的服务
  • 使用R语言生成新的数据列

    使用R语言生成新的数据列 在R语言中 我们可以使用各种方法来生成新的数据列 这些方法可以帮助我们处理和分析数据 为我们提供更多的洞察力 本文将介绍几种在R语言中生成新数据列的常用方法 并提供相应的源代码示例 使用算术运算符生成新列 我们可以
  • 小程序整改获取用户隐私

    整改要求如下 请开发者自查并去除频繁跳出来的手机号登录界面 手机号授权弹窗 去除小程序内非必要页面的手机号登录界面 手机号授权弹窗 正常的内容浏览是不支持获取用户手机号或其他隐私信息 在必要场景下才告知用户需授权手机号 且页面需要有 用户协
  • Android初级教程 - 四大存储之SP存储

    1 SharedPreFerences是什么 是安卓的一种最轻量的储存类 储存为xml文件储存到 data data 包名 shared prefs下 一般用来存储一些比较简单的数据 比如用户名姓名 密码等等 2 如何储存数据 Shared
  • 软件测试大型网站如何进行压力测试及性能调优优化方案

    性能测试在大型网站系统的设计和开发中非常重要 通常会和容量预估等工作结合在一起 穿插在系统开发的不同方案 性能测试可以帮助我们及时发现系统的性能短板 评估系统的能 性能测试在大型网站系统的设计和开发中非常重要 通常会和容量预估等工作结合在一
  • CMake简介,打包so文件,编译实际项目

    CMake简介和使用示例 CMake是常用的跨平台编译器 图像这块在给服务端做开发时 常有两个需求 1 代码打成 so包 供别人调用 2 编译 测试 用valgrind测内存情况 工程较大时 借助CMake完成很方便 下面分别给出两种情况下
  • 含Java岗988道题分享 备战金九银十,你准备好了吗?,阿里,腾讯秋招面试题解析。

    在前段时间里公司的项目基本都很闲 很多人觉得工作起来没意思相继走了 而我考虑到自己的发展 并没有裸辞 而是一边上班 另一边在面试 从3月底开始面试 面到5月底 三十家公司 因为疫情原因有些面试是远程面试 我从不打没准备的仗 我是一个喜欢总结
  • chown 命令

    NAME chown change file owner and group SYNOPSIS chown OPTION OWNER GROUP FILE chown OPTION reference RFILE FILE 当使用 refe
  • 数据库-面试题(持续更新)

    来自牛客网的汇总 1 MySQL查询时 只有满足联接条件的记录才包含在查询结果 这种联接是 内联接 内联接 典型的联接运算 使用像 或 lt gt 之类的比较运算符 包括相等联接和自然联接 内联接使用比较运算符根据每一表共有的列的值匹配两个
  • 在simulink中查看bode图

    打开simulink 在library里面找到inport和outport 然后在inport和outport之间使用传递函数 想要查看bode图的传函 连接 按如下路径点击Analysis Control Design Linear An
  • 【2.学习__签名证书和加密证书】

    实习期学习一些签名和加密的知识 暂时先这样 有时间了再整理 学习的方法 先学习证书文件内容 结构 再针对问题进行学习 证书相关的知识 1 证书的结构大致是什么样的 证书的机构分为三部分 tbsCertificate 包含 主题 和 发行者的
  • 【C语言学习笔记】再次深入理解递归——总结设计易错点

    写在前面 其实我也说不太清楚到底递归算不算算法 因为我一开始从0基础接触递归是从 算法图解 这本书中得知的 也很推荐刚学算法的朋友可以先看看这本书 写的挺不错的 也就把它当成算法了 但写了那么多题目 渐渐的感觉递归这个东西把 它更像是一种工