递归作为算法中的重要思想,在编程的应用中可以说是相当广泛。但递归又由于其本身的抽象性,对于初学者而言理解起来相当困难。作为一个初学算法的菜鸡,想和大家分享一下自己理解递归的方式,如有错误和不妥,还望大佬指出。
首先我们要明白,一个递归函数内部肯定长这样
![](https://img.haomeiwen.com/i15923009/799ee1432fc7f688.png)
也就是说,递归函数的终止条件一定要在递归函数的上面,否则递归函数就像一个没有底的坑,掉进去就永远出不来了。而如何写出递归函数的关键在于:找出解决问题的“笼统方法”。
而解决问题的“笼统方法”又分为三种表现形式。
“笼统方法”表现的第一种形式:直接解决这个问题的一小部分+递归解决这个问题的剩余部分
eg1:求n的阶乘
这是个非常简单的例子,但也给很多初学递归的人一种错觉:“哦这玩意儿就是递归啊,也没说的那么难理解嘛”,如果真是这样想的,那你可真的是naive。这只是递归的最简单的表现形式,但对于我们而言仍有大篇幅讨论的必要。假如说你现在是个完完全全的小白,你拿到这个问题首先应该想的是:这个问题我应该咋给他“应付”过去?
看看递归的程序是怎么“应付”的。
![](https://img.haomeiwen.com/i15923009/2499236587518572.png)
要想找n的阶乘,我只需要找出n-1的阶乘,然后把他和n乘一块儿,至于n-1的阶乘改咋办?抱歉那就不是我的事儿了,您找下一层解决去吧。有没有觉得这种程序风格让人觉得很敷衍?其实递归程序都是这种敷衍的风格,正由于它的“敷衍”导致递归函数的时间复杂度非常的糟糕。在这个例子中,n* 直接解决了这个问题的一部分,fac(n-1)则解决了这个问题的剩余部分,二者拼凑起来,就成了解决这个问题的“笼统方法”。找出笼统方法还不行,如果这个程序就这样一直敷衍下去那么问题肯定是没有办法解决的,总要有一个不敷衍人完成具体的工作,这就是递归程序中的边界条件。这个问题的简单之处就在于它的边界条件很好找,n最多只能减到1,那就返回1给上层函数就好了。但接下来的这和例子的边界条件找起来就要稍微麻烦些了。
eg2:求一个数组的和
这个程序用层for循环就能轻松解决,但我们现在讨论的是递归,我想你应该没有那么无聊。其实对于初学者来说,把循环改写为递归也算是一种不错的训练方式。废话不多说,直接上码
![](https://img.haomeiwen.com/i15923009/f096d23c2c8bf03b.png)
这里我们在传参数的时候我们除了将数组传进去之外,还多传了一个begin。因为如果不传这个begin的话,我们没有办法切割这个数组,也就没有办法去“笼统”的解决这个问题,最终的边界条件就更没法儿找了。在这段代码中,a[begin]+ 解决了这个问题的一部分,sum(a,begin+1)解决了这个问题的剩余部分。边界条件肯定就是begin跑到数组的最后,然后返回那个位置上的值就可以了。
“笼统方法”表现的第二种形式:递归解决问题的一部分+递归解决问题的一部分+……
emmm,我举一个最形象的例子,假如你现在在进行期末“预习”备考,你肯定不会从头到尾翻一遍书,你最多把老师们画的重点例题好好研究一下。但你一学期啥也没学啊,而一道题又恰好涉及到了多个知识点,这时候你能做的就是把这道题涉及到的知识点好好看一下然后去搞懂,如果这道题的知识点牵连到了其他的知识,这时你就只有把那些“子知识”搞懂你才能搞懂“父知识”,当你把重点题中的某个“父知识”搞明白后,就可以说你递归解决了这个问题的一部分。但还有另一部分那些“父知识”等着你递归解决呢伙计。其实这个过程就算是这样的表现形式,你上百度百科上去查相关词条其实也算是这种表现形式。
eg1:返回斐波那契数列中的第i个元素
上码:
![](https://img.haomeiwen.com/i15923009/2e7b33926a3042b2.png)
这个例子可以说是很典型了。在这个例子中 fib(i-1) 解决了原问题的一部分,fib(i-2) 解决了这个问题的另一部分,二者加一块拼凑起来就解决了原问题。
eg2:汉诺塔问题
只要你小时候你们家买过国产山寨按键机,我猜你们应该都玩过这个游戏,汉诺塔的规则我就不解释了,没玩过的可以自己去玩一下。(汉诺塔游戏)我们通常玩到的版本通常是只有三层四层或五层,其实原问题是那个印度神棍要把49层挪到目标柱子(她真要挪的话挪到太阳踏缩成黑洞都挪不完),但不管是挪3层还是49层,他们的本质都是一样的:把从上往下那n-1个盘子挪到空闲的柱子上作为辅助空间,把最底下那个最大的盘子(第n个)挪到目标柱子上,最后把那n-1个盘子挪到目标柱子上,所以所一共下来只要三步,无非是第一步和第三步需要递归完成。
![](https://img.haomeiwen.com/i15923009/36056614c48b7ecb.png)
边界的极端情况,以三个为例
![](https://img.haomeiwen.com/i15923009/590184538269eac8.png)
此时左边还剩一个,直接挪到目标柱上即可完成。
“笼统方法”表现的第三种形式:解决了子问题,就等于解决了原问题。
这种形式的递归问题可以说是最难解决的,因为相对应的等价问题并不是那么好找。比如求两个数的最大公约数的递归算法。
![](https://img.haomeiwen.com/i15923009/93517ef545e7e70a.png)
这段代码看似简单,但如果没人提醒一句“要用辗转相除法”是绝对不太好想的,举个栗子,要想求出18与12的最大公约数那就等价于求12与6的最大公约数,就等价于求6与0的最大公约数。第一次进行等价问题的转化才是最难的,这种类似问题也对编程学习者的数学转化能力有所要求。
最后一点需要强调,如何找出边界条件。我们要把目光放到变化的量上面,通常是将变化的量做为参数传入递归函数,进行边界条件判断时也是依据变化的量的极端情况进行判断的。
网友评论