在菜鸟眼中的递归算法是怎样的?
1.从视觉(形式)上:简洁
2.从感觉(体会)上:牛逼
3.从知觉(使用)上:踟蹰
总结起来就是看起来简单,算法很牛逼,自己用起来不顺手,看了答案之后才觉得:原来是这么回事,我就差那么一丢丢
以汉诺塔入题
汉诺塔
有A、B、C三个柱子,将N个按上小下大顺序呢叠放的圆盘,从A柱移动到C柱
要求:一次只能移动一个圆盘
小圆盘上不可放大圆盘
不拘泥于答案去思考
当初学算法的时候,老师给出过答案,网上也有很多高手给出了汉诺塔问题答案,但是,说实话,看的快,忘得也快,归根到底,没懂!
那什么是懂?
懂就是流程化,标准化,当你拿到一个问题,心中123就已经出来,你只需要去分别完成123的时候,这就是懂了。
流程化的思路才是懂了。
流程化的汉诺塔解决思路
递归含义飘过
递归算法就是将大问题拆成若干同类的小问题,直到小问题被解决。
一定要动手
对于一个N的问题,菜鸟一定要去动手画一画,列出前几项,就像找规律一样
这里,我用{1,2,3,4},没有特殊含义,只是将1234看做一个整体
//以n=4为例:
move{1,2,3,4} from A→C
move{1,2,3} from A→B
move{1,2} from A→C
move{1} from A→B
move{2} from A→C
move{1} from B→C
move{3} from A→B
move{1,2} from C→B
move{4} from A→C
move{1,2,3} from B→C
move{1,2} from B→A
move{1} from B→C
move{2} from B→A
move{1} from B→A
move{3} from B→C
move{1,2} from A→C
move{1} from A→B
move{2} from A→C
move{1} from B→C
递归关键
大小问题是同类的,那我们分析问题也要如此,如果你能够将不同的问题,找到一个角度去观察,它们的形式一致,那么你已经成功了一半。
比较N=4与N=2
move{1,2,3,4} from A→C
move{1,2,3} from A→B
move{4} from A→C
move{1,2,3} from B→C
move{1,2} from A→C
move{1} from A→B
move{2} from A→C
move{1} from B→C
看到这里,是不是内心有什么东西被触动了,就像当你第一次看到别人的递归算法答案时,这么调用,在继续自己调用自己。。。
是的,我不光第一次看到别人打答案,第二次,第三次,甚至于每次遗忘重新拾起的时候,都是同样的感受。
成功了50%
我们已经找到了一个角度,它以同样的形式将N=4和N=2时的问题展现在我们面前,剩下50%就是将思路转换为代码
剩下50%,算法到代码
对于菜鸟而言,即便是算法给你了,转换为代码也是相当头疼的一件事
很重要的两句话,一眼可见底的步骤是最小单位,用f1()表示,将一眼不可见底的步骤用f()表示,在步骤外面套一个f()
如此一来,汉诺塔问题的递归算法代码实现起来就是
f(){
f();
f1();
f();
}
为了好看一点我们用move(n,from,to)来代替f1(),对应上面的move{1} from A → B,它是一眼可见底的
用hanoi(n,from,to)来代替f(),对应于上面的move{1,2,3} from A → B,它一眼不可见底,你不可能凭空说出如何把123按照游戏规则从A移动到B
一级美化之后,代码变为
hanoi( n, from, to){ //对应于move{1,2,3,4} from A → C
hanoi( n, from, to); //对应于move{1,2,3} from A → B
move( n, from, to); //对应于move{4} from A → C
hanoi( n, from, to); //对应于move{1,2,3} from A → C
}
有时候,你需要做点递归外的工作
细心的人会发现,不对啊,内部有三个字母ABC,参数却只有两个,泥坑我啊。
是的,因此我们需要在hanoi参数中再加一个参数,这个参数你要加在外层的f()中,然后内层依据外层处理。
按照大众做法来,我们放在中间吧,免得这里一套那里一套,省去读者转换思考的工作
继续美化代码
hanoi( n, from, mid, to){ //添加后,我们将N=4时记作 hanoi( 4, A, B, C);
//以外层为主,我们将在from to的中间了,注意,这样一来,就是说第一个参数代表起点,第二个辅助点,第三个重点
//无论你的标识怎么变,递归是由外而内的,内部意义依附于外部,这三个参数顺序意义是不变的
//hanoi( n, from, to); //添加参数后,我们记作 hanoi(3, A, C, B);
//因为其对应于对应于move{1,2,3} from A → B,A是起点,所以放在第一个,B是终点,所以放在第三个,C没用到,放中间
hanoi( n, from, to, mid); //由外层hanoi()可知,A对应from,B对应mid,C对应to,需要根据外层参数顺序,调整内层参数顺序
move( n, from, to); //最小语句,不用改,to确实是终点的含义,如果你把终点记作mid,那这里就要修改了
//hanoi( n, from, to); //这里对应于move{1,2,3} from A → C,为迎合参数顺序,对应于hanoi(3. A, B, C),
hanoi( n, from, mid, to);
}
至此,你可以根据自己的需求实现自定义的move( n, from, to),比如可以在里面写一个打印函数,print出"move n from → to",以输出整个移动步骤
还可以返回一个1,略修改代码,你可以输出整个步骤所需的步数,还可以实现某个数+1的操作,同样输出整个移动步数。
注意,你还需要在move中去完成边界判断,n为多少时结束
递归算法三板斧从思路到实现总结
第一板斧
将第k个问题的解决方法“找”出来,k可以选择4,也可以选择N
将“第2个”问题的解决方法写出来,找出一个非最小单位的问题,最小单位一步完成,如1,从A到C
第二板斧
找到一个角度,将第N个问题和第2个问题的解决办法,从形式上统一起来,解决问题的写法统一的时候,完成了50%
第三板斧
将第N个问题解决办法中的所有一眼看不到底的步骤,统一用一个函数表示,需要注意的是,比如hanoi外层和内层的hanoi参数不一致,需自行调整
没了。
递归算法三板斧“套用”跨台阶问题
跳台阶问题:一个台阶总共有n级,如果一次可以跳1级,也可以跳2级
套用一
k=N,可以从N-1跳上去,也可以从N-2上跳上去
k=2,可以从1跳上来,也可以从地面0上跳上来
套用二
get(N)
get(N-1)
get(N-2)
get(N-1)+get(N-2)
get(2)
get(1)
get(0)
get(1)+get(0) 注意边界get(1)=get(0)=1
这个形式很简单,说白了就一加法,前面两步只是为了凑步数写的,只要第三步就行了,这个例子略牵强,读者别介意
套用三,注意完成边界判断
int f( int n){
if( n==1|| n==2)
return n;
return f( n-2)+ f(n-1);
}
以上就是我对如何实际操作递归算法的理解了,希望对刚学算法的同学或者刚接触算法的准程序员有所帮助,有需要改进的地方,请不吝赐教,每个人看问题的角度都有独到之处,只有多碰撞才能有火花。
当然,没有什么是完全独立的,我们总是站在巨人的肩膀上,非常感谢那么多人无私的将自己的想法表达出来,但这个材料很久了,当时查了多少资料已不记得了。
如果有大神路过,麻烦交流下,告知大神是如何具体操作递归问题的,将不甚感激。