递归动漫讲解咯
递归的回归逻辑
代码示例
#include "stdlib.h"#include "stdio.h"// 如何按照字典序进行输出 枚举int arr[10];// 打印函数void print_one_result(int n) {// 遍历for (int i = 0; i <= n; i++) {if (i) {printf(" ");}printf("%d", arr[i]);}printf("\n");}/**** @param i 起始索引* @param j 跟踪当前数* @param n 边界数*/// 递归函数void f(int i, int j, int n) {// p(1) = j > nif (j > n) {return;}// p(n) =for (int k = j; k <= n; k++) {arr[i] = k;print_one_result(i);f(i + 1, k + 1, n);}}int main() {int n;scanf("%d", &n);f(0, 1, n);return 0;}讲解
我们换一种超级通俗、有趣、细节拉满的方式,把这个递归代码讲透!
把自己想象成一个导演,正在拍一部叫做《递归探险记》的电影。
电影名称:《递归探险记》
主演:
你:扮演递归函数
f。小助手:
print_one_result,负责在你发现宝藏时拍照记录。宝藏数组
arr:一个神奇的盒子,用来存放你一路上找到的宝藏(数字)。总导演:
main函数,他只负责喊“开始!”。
故事背景:你需要按字典序(就是字典里单词的排序方式,比如 "1" 排在 "1 2" 前面, "1 2" 排在 "1 3" 前面)探索所有从 1 到 n 的数字组合。每找到一个组合,小助手就会拍一张照片记录下来。
你的装备(函数参数):
i:你当前所在的位置(比如第i个藏宝点)。j:你下一步可以选择的最小数字(为了保证字典序和不重复,你不能回头选更小的数字)。n:地图的边界,数字不能超过n。
你的任务(函数 f 的代码逻辑):
检查边界:如果
j > n,说明你已经走到地图外面了,这条路不通,立即返回。循环探索:从你可以选择的最小数字
j开始,一直到地图边界n:a. 放置宝藏:把当前数字k放进宝藏数组arr的第i个位置。b. 拍照留念:喊小助手print_one_result(i)过来,把当前arr盒子里从第0个位置到第i个位置的宝藏拍下来。c. 派遣分身:克隆一个自己(递归调用f),让他去探索下一个位置i+1。并且告诉他,他只能从比你当前数字k大1的数字(k+1)开始选,这样才能保证顺序。d. 分身回归:你的克隆人探索完回来后,你继续尝试下一个数字(k增加1)。能力明确:你可以分身,但是你每次只能控制一个身体,因为你只有一个灵魂,大多数情况下你分身都得先把行动明确好,回归后分身可能要继续完成行动再回归,直到最后完全还魂,你就利用完分身啦!
补充声明:fn代表i=n
开拍(递进)!以 n=3 为例
总导演 main:“Action!f(0, 1, 3)!”
第一幕:你(f0)的探险
你(
f0):装备i=0,j=1,n=3。你:检查
j=1是否大于n=3?不大于。开始循环k从1到3。
场景 1a:k=1
你:把
1放进arr[0]。现在arr盒子里是[1, ?, ?]。你:“小助手,拍照!”
小助手:咔嚓!拍下来
1,打印出来。你:“我需要一个分身去下一个位置!”
你克隆了自己(我们叫他
f1)。你对
f1说:“f1,你去位置i=1,记住,你只能从k+1=2开始选数字,总边界还是3。”你(
f0)现在暂停,等待f1回来。
第二幕:分身 f1 的探险
分身 f1:装备
i=1,j=2,n=3。f1:检查
j=2是否大于n=3?不大于。开始循环k从2到3。
场景 2a:k=2
f1:把
2放进arr[1]。现在arr盒子里是[1, 2, ?]。f1:“小助手,拍照!”
小助手:咔嚓!拍下来
1 2,打印出来。f1:“我也需要一个分身!”
f1 克隆了自己(我们叫他
f2)。f1 对
f2说:“f2,你去位置i=2,从k+1=3开始选,总边界3。”f1 现在暂停,等待
f2回来。
第三幕:分身的分身 f2 的探险
分身 f2:装备
i=2,j=3,n=3。f2:检查
j=3是否大于n=3?不大于。开始循环k从3到3。
场景 3a:k=3
f2:把
3放进arr[2]。现在arr盒子里是[1, 2, 3]。f2:“小助手,拍照!”
小助手:咔嚓!拍下来
1 2 3,打印出来。f2:“我也需要一个分身去下一个位置!”
f2 克隆了自己(我们叫他
f3)。f2 对
f3说:“f3,你去位置i=3,从k+1=4开始选,总边界3。”f2 现在暂停,等待
f3回来。
第四幕:最深的分身 f3 的探险
分身 f3:装备
i=3,j=4,n=3。f3:检查
j=4是否大于n=3?是的!f3:“报告!前面没路了!” 说完,
f3直接消失了(return)。
还魂(回归)的时刻开始了!
回归到 f2
f2 一直在等
f3,突然f3消失了,f2知道f3探索完了。f2 的循环
k从3到3也执行完毕了。f2:“我的任务完成了!” 说完,
f2也消失了,并把控制权交还给了f1。
f2 回归前后数据对比:
回归前 (即
f2调用f3之前的状态):f2的装备:i=2,j=3,k=3arr盒子:[1, 2, 3]
回归后 (即
f3返回,f2继续执行之后的状态):f2的循环k已经执行完(k变成了4)。f2函数执行完毕,准备消失。arr盒子:[1, 2, 3](因为f3没修改它)。程序回到
f1调用f2的地方,f1准备继续执行。
回归到 f1
f1 一直在等
f2,突然f2消失了,f1知道f2探索完了。f1 的循环继续,
k从2增加到3。
场景 2b:k=3 (这是 f1 循环的下一次迭代)
f1:把
3放进arr[1]。注意! 这里arr[1]的值被覆盖了。现在arr盒子里是[1, 3, ?]广告:其实这里的?是3,只是小助手可以拍照的范围被限制了!
f1:“小助手,拍照!”
小助手:咔嚓!拍下来
1 3,打印出来。f1:“再派一个分身!”
f1 又克隆了自己(我们叫他
f4)。f1 对
f4说:“f4,你去位置i=2,从k+1=4开始选,总边界3。”f1 再次暂停,等待
f4回来。
第五幕:分身 f4 的探险
分身 f4:装备
i=2,j=4,n=3。f4:检查
j=4是否大于n=3?是的!f4:“报告!没路了!” 说完,
f4消失了。
再次回归到 f1
f1 等
f4消失后,它的循环k从3到3也执行完毕了。f1:“我的任务也完成了!” 说完,
f1消失了,把控制权交还给了你(f0)。
f1 回归前后数据对比 (第二次回归):
回归前 (即
f1调用f4之前的状态):f1的装备:i=1,j=2,k=3arr盒子:[1, 3, ?]
回归后 (即
f4返回,f1继续执行之后的状态):f1的循环k已经执行完(k变成了4)。f1函数执行完毕,准备消失。arr盒子:[1, 3, ?](因为f4没修改它)。程序回到
f0调用f1的地方,f0准备继续执行。
回归到你(f0)
你(
f0)一直在等f1,突然f1消失了,你知道f1探索完了。你的循环继续,
k从1增加到2。
场景 1b:k=2 (这是 f0 循环的下一次迭代)
你:把
2放进arr[0]。注意!arr[0]的值被覆盖了。现在arr盒子里是[2, ?, ?]。你:“小助手,拍照!”
小助手:咔嚓!拍下来
2,打印出来。你:“派一个分身去
i=1,从k+1=3开始!”这个新分身
f5的故事和之前类似,他会发现2 3,然后他的分身f6会发现没路。f6回归 →f5回归 → 回到你这里。
你的循环继续,k 增加到 3,然后是 k=3 的场景,最后你的循环也结束了。
最终回归
你(
f0)的所有任务都完成了,你也消失了。程序控制权回到总导演
main那里。main:“Cut!完美收官!”
总结一下“回归”的魔法
暂停与等待:当你(一个函数调用)派遣一个分身(递归调用)时,你自己就暂停了,进入“等待”状态。
独立的探险:你的分身有他自己的一套装备(参数
i,j,n)和他自己的循环变量k。他的探险是独立的。回归即恢复:当分身探险结束(
return),他就消失了。这时,你在你暂停的地方恢复执行。你继续你刚才没完成的循环,k的值还是你暂停时的值(然后会自增)。共享的宝藏盒:
arr数组是大家共享的。当一个分身修改了它,后面的分身(或回归后的你)看到的就是修改后的值。这就是为什么序列会不断变化。
整个过程就像一群探险家在走一个分叉很多的迷宫,每个人走到一个路口就派一个队友去探索一条支路,自己则在路口等着。当队友探路回来报告“此路不通”或“已探索完毕”,这个人就继续探索下一条支路。最终,所有可能的路径都被探索过,所有宝藏组合都被记录下来了。
