大家好啊!这里是土豆的博客,一个正在努力学习技术的大学生。
上一篇和大家聊了递归的四步法,并且用阶乘那个例子带着大家完整走了一遍。如果还没看上一篇的话,建议先去翻一翻,不然这篇直接看可能会有点懵。
那今天这篇,咱们就继续用四步法来做两道练习题。这两道题看起来很像,但有一个关键的区别——把“缩小问题”和“还原回去”这两步的顺序换一下,结果就完全不一样了。我觉得这个对比挺好的,应该能帮你更深刻地理解递归的执行顺序。
好了,废话不多说,咱们直接开始。
案例1:倒序输出一个数字的每一位
题目:输入一个正整数,用递归的方式把它的每一位数字倒序输出。比如输入 1234,输出 4 3 2 1。(不考虑负数)
咱们还是用四步法一步一步来分析。
第一步:明白这个函数用来干什么
这个函数的功能很直白:接收一个整数 n,在屏幕上倒序打印它的每一位。
// 输入一个整数 n,倒序打印它的每一位数字 void printReverse(int n) { // 先不用管里面的实现 }注意返回值是 void,因为这个函数只负责打印,不需要返回计算结果。
第二步:递归得确定终止条件
这里我觉得需要你先思考下,当n 等于多少的时候,我们可以直接处理?
既然咱们题目的要求是倒序输出每一位数字,那对于个位数来说是不是就输出就可以了呢?
所以当n 只有一位数的时候(也就是 n < 10),直接把它打印出来就行了,不需要再递归。
void printReverse(int n) { // 终止条件:n 是一位数时,直接打印 if (n < 10) { cout << n << " "; return; } }
第三步:想办法把问题“变小一号”
这一步我觉得咱们可以举个例子,那这里有引入了一个新的问题就是,咱们在举例子的时候,不能举太极端的例子,就比如个位数,要不然看不出来要怎么把问题变小
所以在这里咱们得尽量弄一个比较普适的例子,所以就拿1234来说
大家先暂停思考下,好好想想这块内容
首先1234 怎么变小?是不是去掉最后一位就行了。所以怎么去呢?
是不是很快就能想到用n / 10,1234 / 10 = 123,这样就得到去掉最后一位的数字了
既然1234变小了,那我们毕竟采用递归的思想,所以应该继续调用printReverse(123)
也就是调用printReverse(n / 10)。
所以在第三步咱们明确了这里的代码要写调用 printReverse(n / 10)。
第四步:怎么把缩小后的问题还原回去
咱们先不用管里面具体的实现逻辑,就先假设printReverse(123)已经帮我把 123 倒序打印好了。
那现在我要倒序打印 1234,要怎么利用这个结果呢?1234 的最后一位是 4,所以我们肯定得有个输出是不是,所以有:
cout << 4 ;那咱们怎么得到4呢,是不是想到了%这个符号,咱们可以通过1234%10来得到
所以这里就是
cout << n%10 ;现在重点来了,咱们是先打印再调用呢,还是是先调用后打印呢
这两个顺序看起来差不多,但结果完全不一样。
我们先分析一下逻辑:题目要求是倒序输出,也就是从最后一位开始打印。对于 1234 来说,最后一位是 4,也就是 n % 10,所以4必须第一个被打印。
那怎么保证 4 第一个打印呢?很简单,在调用递归之前,先把4打印出来。这样屏幕上先显示 4,然后递归再去处理剩下的 123,递归内部会依次打印 3、2、1,整个顺序就变成了 4 3 2 1。
所以我们的选择是:先打印 n % 10,再调用 printReverse(n / 10)。
把这一步写进代码,结合四步 就是:
void printReverse(int n) { // 第二步:终止条件 if (n < 10) { cout << n << " "; return; } // 第四步(先打印) + 第三步(再缩小) cout << n % 10 << " "; printReverse(n / 10); }到这里,这个题其实就解完了。
但你有没有想过——如果反过来,先调用再打印,会发生什么?这个问题我当时也好奇过,所以专门试了一下,结果发现输出完全变了。具体怎么回事,咱们接着往下看。
重点来了:调换一下顺序会怎样?
咱们先来看下代码
void printReverse(int n) { if (n < 10) { cout << n << " "; return; } printReverse(n / 10); // 先缩小 cout << n % 10 << " "; // 后打印 }结果是什么呢
答案是1 2 3 4——变成正序输出了!
这样我来手画个结构图,咱们一起来看看:
因为如果咱们先递归,程序会一路扎到最深处,此时n=1,所以程序先打印 1,然后返回上一层打印 2,再返回上一层打印 3……最后打印 4。
所以第三步(缩小)和第四步(还原)的先后顺序,决定了是从大到小处理还是从小到大处理。
对本题来说有:
所以确定这两步骤的顺序也是十分关键的。
案例2:计算一个数字的各位之和
题目:输入一个正整数,用递归计算它各位数字之和。比如输入 123,返回 1+2+3=6。
建议大家先暂停,自己用四步法想一想,再看分析。
第一步:明白这个函数用来干什么
// 输入一个整数 n,返回它各位数字之和 int sumDigits(int n) { // 先不用管里面的实现 }
第二步:递归得确定终止条件
当 n < 10 时,它只有一位,所以各位数字之和就是它自己。
int sumDigits(int n) { //终止条件,当n为个位数的时候,也就是n<10 if (n < 10) { return n; } }
第三步:想办法把问题“变小一号”
依旧咱们先举个例子:123
和倒序输出一样,用 n / 10去掉最后一位,问题从 123 缩小成 12。
和上一道题一样的思路:也就是sumDigits(12)即sumDigits(n / 10)
第四步:怎么把缩小后的问题还原回去
拿到sumDigits(12)的结果,也就是 1+2=3,怎么得到sumDigits(123) 的结果?
123 的最后一位是n%10,也就是 3。所以把它加上去就行了:3 + 3 = 6。
所以还原方式是:n % 10 + sumDigits(n / 10)
完整代码:
int sumDigits(int n) { if (n < 10) { return n; } return (n % 10) + sumDigits(n / 10); }最后调用 sumDigits(123),返回 6。
咱们看,这道题和倒序输出题目的结构几乎一模一样,只是把“打印”换成了“加法”。
所以这也能体现四步法的好处,套路是很固定的,具体操作换一下就行。
一个比较容易犯的问题:终止条件没写严谨
刚学递归的时候,最常犯的错误就是第二步终止条件考虑不周全。
就比如倒序输出那个例子,如果没有写 if (n < 10) 这个终止条件:
void printReverse(int n) { cout << n % 10 << " "; printReverse(n / 10); }对于 n = 1234,程序会一直执行:1234 → 123 → 12 → 1 → 0 → 0 → 0……无限循环,直到程序崩溃。
所以后来我养成一个习惯:写完递归代码之后,用几个例子在脑子里跑一遍,检查终止条件有没有写全。
写在最后
递归这个东西,刚开始接触确实会觉得别扭,但我觉得只要多练几道,套路其实就那么几个。
今天这篇咱们用四步法做了两道练习题,还发现了第三步缩小和第四步还原的顺序一换,结果就完全不一样。同时我也希望这两个案例能帮你更深入地理解递归的执行顺序。
如果你学到后面就会发现,递归有时候会重复计算很多次同样的东西,比如斐波那契数列,这时候可以用一个数组把算过的结果存起来,这个技巧叫“记忆化搜索”,等咱们以后聊到数组的时候再详细展开~
之后我会继续分享一些相关的C++知识入门学习经验的。
如果这篇文章对你有帮助,麻烦点赞、关注和收藏吧,谢谢!😄
有什么问题或者想法,欢迎在评论区留言,我们一起交流!
我们下篇见~