最近在学习递归函数,想深入了解一下递归原理和一些大神们对递归的理解,因为自己在使用递归时总感觉力不从心,于是就搜集的一些大神的博客供大家参考,其次也是对自己进行一个总结吧,下面是借鉴的大神的博客,不喜勿喷。
下面借鉴两个例子来更好的理解递归和循环。
递归:你打开一扇门,看到屋里还有一扇门(这扇门可能跟以前的门大小一样(静),也可能门小一些(动)),你走过去,你会发现你手里的钥匙还可以打开它,推开门,发现里面还有一扇门,你继续打开...,若干次后,你打开前一扇门,发现只有一个屋子,没有门了,你开始原路返回,每走回一间屋子,你数一次,直到走到入口的时候,你可以回答出你到底用了这些钥匙,打开了几扇门。
循环:你打开的这扇门,进入屋子后你发现还有一扇门(这扇门可能跟以前的们大小一样(静),可有可能小一些(动)),你走过去,发现手里的钥匙可以打开他,推开门,发现里面还有一扇门,(前面门如果一样,这门也一样,第二扇门如果比第一扇门相比变小了,这扇门比第二扇门也变小了(动静如一,要么没有变化,要不同样变化)),你继续打开这扇门...,一直这样下去,入口处的人始终等不到你回去告诉他答案。
那从这个例子中总结到:
递归就是有去(递取)有回(归来)。
具体来说,为什么可以“又去”?
这要求这要求递归的问题需要是可以用同样的解题思路来回答除了规模大小不同,其他完全一样的问题。
为什么可以“有回”?
这要求这些问题不断从大到小,从近及远的过中,会有一个终点,一个临界点,一个baseline,一个你到了那个点就不用在往更小,更远的地方走下去的点,然后从那个点开始原路返回到原点。
也可以理解为递是描述问题,归是解决问题,如果最初一味的往远方进,连尽头都没走到,何谈归来。
大神引用了一篇文章的的递归思想归纳为:
递归的基本思想就是把规模大的问题转化为规模小的相似子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。
需要注意的是,规模大转化为规模小是核心思想,但递归并非只做这步转化,而是把规模大的问题分解为规模小的子问题和可以在子问题解决的基础上剩余的可以自行解决的部分。而后者就是归的精髓所在,是在实际解决问题的过程。
我试图用我的理解到的递归思想用程序表达出来,确定了三个要素:递 + 结束条件 + 归。
def recursion(大规模): if end_condition: end else: #先将问题全部描述展开,再有尽头'返回'依次解决每步剩余部分的问题 recursion(小规模) #go solve #back
但是,我很容易发现我这样疏漏了我经常遇到的一种递归情况,比如递归遍历二叉树的先序。我将这种情况用如下递归程序表达出来:
def recursion(大规模): if end_condition: end else: #先将问题转换为子问题描述的每一步,都解决该步中剩余的部分问题 solve #back recursion(小规模) #go
总结到这里,我突然发现其实递归可以使‘有去有回’,也可以是‘又去无回’。但其根本就是‘由大到小,由近及远的去’。‘递’是必需的,‘归’是非必需的,依赖于要解决的问题,有的需要在去的路上解决,有的需要在回来的路上解决。有递无归的递归其实就是我们很容易理解的分治思想。
其实理解递归可能没有“归”,只有去(分治)的情况后,我们应该想到递归也许可以既不需要在“去”的路上解决问题,也不需要在“归”的路上解决问题,只需在路的尽头解决问题,即在满足停止条件时解决问题。递归的分治思想不一定是要把问题规模递归到最小,还可以是将问题递归穷举其所有的情形,这时通常递归的表达力体现在将无法书写的嵌套循环(不确定数量的嵌套循环)通过递归表达出来。
这种递归用程序描述如下:
def recursion(大规模): if end_condition: solve else: for x in x: recursion(小规模)
由这个例子,可以发现这种递归对递归函数参数出现了设计要求,即便递归到尽头,组合的字符串规模(长度)也没有变小,规模变小的是递归函数的一个参数。可见,这种变化似乎一下将递归的灵活性大大地扩展了,所谓的大规模转换为小规模需要有一个更为广义的理解了。
递归函数练习:
1.求阶乘n!
#方式1:
def factorial(x,ret):
if x == 1:
return ret
return factorial(x-1,ret * x)
#方式2:
def factorial(n):
if n == 1:
return n
else:
return factorial(n - 1) * n
ret = factorial(5,1)
2.用辗转相除求两个数的最大公约数
def gcd(a,b): return b if a % b == 0 else gcd(b,a % b)
ret = gcd(36,8)
3.汉诺塔
#i用于记录步数 i = 1 def move(n,fr,to): ''' i代表当前的步数 将编号为n的盘子从fr柱子移动到to柱 ''' global i i += 1 print('第{}步骤,将{}号盘子从{}---->{}'.format(i,n,fr,to)) def hanio(n,start_pos,trans_pos,end_pos): #当n==1时,只需要将盘子从起始柱子移动到目标柱子即可 if n == 1: move(n,start_pos,end_pos) else: #第一步:将n-1个盘子移动到过渡柱上 hanio(n-1,start_pos,end_pos,trans_pos) #第二步:然后将底下的大盘子移动到目标柱上 move(n,start_pos,end_pos) #第三部:重复上述的步骤,递归处理放在过渡柱上n-1个盘子,此时借助原来的起始柱子作为过度柱 hanio(n-1,trans_pos,start_pos,end_pos) hanio(5,'A','B','C')
4.斐波那契数列
def fibonacci(n): if n == 1 or n == 2: return 1 return fibonacci(n - 1) + fibonacci(n-2) ret = fibonacci(5) for i in range(1,6): print(fibonacci(i),end=' ') print('斐波那契数列:',ret)
5.求1+2+3+....n的和
def totalfunc(n): if n == 1: return n else: return totalfunc(n - 1) + n ret = totalfunc(5)
6.二分法求数组中最大值
def my_max(li,low,high): max = 0 if low > high - 2: max = li[low] if li[low] > li[high] else li[high] else: mid = int((low + high) / 2) max1 = my_max(li,low,mid) max2 = my_max(li,mid + 1,high) max = max1 if max1 > max2 else max2 return max li = [12,234,345,656,67,78,199] ret = my_max(li,0,len(li)-1)