递归是数学上数学归纳法的模型。
不要陷入递归的具体细节。
截取知乎上一个很棒的解释:
链接:https://www.zhihu.com/question/24385418/answer/258015386
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
递归当然只能以递归的思路理解,把它展开纯属自讨苦吃。
递归思路,说白了是如下三步:
1、对于问题N,如果N-1已经解决了,那么N是否很容易解决。
甲:想上天?你先给我找个天一样高的梯子!
乙:想要天一样高的梯子?你先给我弄棵天一样高的树!
甲:想要天一样高的树?你先给我弄把能顶到天的锯子!
……
举例来说,如果要把一个N层汉诺塔从A搬到C,那么:
如果前N-1层可以找别人搞定,咱只管搬第N层,会不会变得非常容易?
你看,这一下就简单了:这时当它只有两层就好了,先把前N-1层看作一个整体,把它搬到B;然后把最下面的第N层搬到C;然后再把前N-1层从B搬到C。
类似的,假如接到“搬前N-1层”这个任务的是我们,怎么搬呢?
简单,像前东家一样,把前N-2层外包出去,我们只搬第N-1层——其实和前面讨论过的“外包N-1层,只搬第N层”完全一样嘛。
依此类推,一层层“外包”下去——我不管你们有多伤脑筋,反正只要你们把我外包给你的活干了,我就能干了我的活!
注意,这里千万别管“接到外包工作的家伙有多伤脑筋”——丢给他就让他头疼去,我们就别再捡回来痛苦自己了。
这一步就是“递推”。
注意这里的搬法:搬第N层,就需要把前N-1层搬两次,另外再把第N层搬一次;搬第N-1层,又需要把前N-2层搬两次,然后再把N-1层搬一次,依此类推。
很容易知道,一共需要搬2^N-1次。
2、一步步递推下去,终究会有个“包工头”,接到“搬第一层”的任务。
第一层怎么搬?
太简单了,让搬哪搬哪。
换句话说,到此,“递推”就到了极限,简单粗暴直接做就可以了。
3、既然第一层搬了,那么第二层当然就可以搬了;第二层搬了,第三层又可以搬了……依次类推,直到第N层。于是问题搞定。
这一步就是“回归”。
如上三步加起来,就是“递归”。
推而广之,任何问题,不管规模为N时有多复杂,只要把N-1那块“外包”给别人做之后,我们在这个基础上可以轻易完成N,那么它很可能就适合用“递归”解决。
那么,怎么最终确定它能不能用“递归”做呢?
看当N取1或2之类最简情况时,问题是否可以解决——然后写程序解决它。
容易看出,“递归”其实和“数学归纳法”的思路非常像:证明N=1时成立;证明若N=n-1成立,则N=n时也成立;如上两步得证,则命题在n>1时一定成立(n为自然数)。
你看,我们没必要从1开始逐一验证每个自然数,只要证明了“基础条件”、再证明了“递推条件”,大自然的规律会帮我们搞定一切。
换句话说,只要我们:
1、写程序告诉电脑“如何分解一个问题”
2、写程序告诉电脑“当该问题分解到最简时如何处理”
那么,“具体如何递推、如何回归”这个简单问题就不要再操心了,电脑自己能搞定。
——写出问题分解方法、写出分解到最简后如何解决,这是我们的任务;把问题搞定,是电脑的任务。这就是递归的魅力。
正是由于这种“我提供思路你搞定细节”的特点,“一切皆递归”的函数系语言才被称为“声明式编程”(而不是必须一步一步指导电脑如何做的“命令式编程”)。
这么简单轻松的事,非要自己吭哧吭哧一步步做出来——O(2^N)规模啊——难怪你们要陷入困境。
递归就是把复杂的问题分解,把规模大的问题,变成规模小的问题来解决。
什么是递归
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
递归的能力
用有限的语句来定义对象的无限集合;
通俗点来理解就是,我程序只有一套,但是我可以通过递归(自身调用自身)的特性,不管你有多少个值,我都能妥妥的给你按照特定的程序逻辑处理。
- 递归需要有边界条件、递归前进段和递归返回段
- 当边界条件不满足时,递归前进;当边界条件满足时,递归返回
以下是另一个可能更有利于理解递归过程的解释:
- 我们已经完成了吗?如果完成了,返回结果。如果没有这样的终止条件,递归将会永远地继续下去。
- 如果没有,则简化问题,解决较容易的问题,并将结果组装成原始问题的解决办法。然后返回该解决办法。
汉诺塔问题的递归解决代码:
把问题拆分为3步,首先把N-1的情况外包出去交给B作为缓存(即假设已经完成,N-1怎么完成请去找N-2,N-2请去找N-3,一直到1的时候,我们就知道怎么做了),第二步就是把A上的移动到C上,第三步就是把缓存区B移到C上,即完成汉诺塔。
code:
#include <stdio.h> void hanoi(int n, char from, char buff, char to) { if( n > 0 ) { if( n == 1 ) { printf("%c -> %c\n", from, to); } else { hanoi(n-1, from, to, buff); hanoi(1,from, buff, to); hanoi(n-1, buff, from, to); } } } int main() { hanoi(3, 'a', 'b', 'c'); return 0; }
汉诺塔由于有重复的操作,所以我们想到可以用递归来实现。
同样,现在我们实现字符串的全排列,比如 abc三个字符,它的全排列是3!种。
abc acb bac bca cab cba
一个字符的全排列是它本身,比如字符a的全排列就是a本身。
考虑到我们可以先找出a,然后全排列bc,而bc全排列又可以分解,那么递归是一个不错的方式。
code:
#include <stdio.h> _Bool isswap(char *s,char begin,char end) { for(int i=begin;i<end;i++) { if(s[i]==s[end]) return 0; } return 1; } void permutation(char *s, int b, int e) { if( (0 <= b) && (b <= e) ) { if( b == e ) { printf("%s\n", s); } else { int i = 0; for(i=b; i<=e; i++) { if(isswap(s,b,i)) { char c = s[b]; s[b] = s[i]; s[i] = c; permutation(s, b+1, e); c = s[b]; s[b] = s[i]; s[i] = c; } } } } } int main() { char s[] = "abcc"; permutation(s, 0, 3); return 0; }