Python实现递归算法

递归概念

任何可以用计算机求解的问题所需的计算时间都与其规模有关,问题的规模越小,解题所需的计算时间往往也越短,从而比较容易处理。直接或者间接调用自身的算法称为递归算法,用函数自身给出定义的函数称为递归函数。使用递归技术往往使函数的定义和算法的描述简捷且易于理解,有些数据结构,如二叉树等,由于其本身固有的递归特性,特别适合用递归的形式来描述。

当我们设计递归算法时,应满足三点:①符合递归的描述:需要解决的问题可以化为子问题求解,而子问题求解的方法与原问题相同,只是数量增大或减少;②递归调用的次数是有限的;③必须有递归结束的条件

其中第③点是我们在编写递归函数时必须要注意的,一定要有递归结束条件,即每个递归函数都必须有非递归定义的初始值,在函数递归结束条件下,没有再调用递归函数。

if (满足递归结束条件):
    这个程序块中没有调用递归函数的语句
函数递归的调用机制

递归函数调用同样遵守函数调用机制,当函数调用自己时也要将函数状态、返回地址、函数参数、局部变量压入栈中进行保存。

实际上函数被调用时执行的代码是函数的一个副本,与调用函数的代码无关。当一个函数被调用两次,则函数就会有两个副本在内存中运行,每个副本都有自己的栈空间且与调用函数的栈空间不同,因此不会相互影响。这种调用机制决定了函数是可以递归调用的。

当有多个算法构成嵌套调用的时候,按照后调用先返回的原则进行,算法之间的信息传递和控制转移必须通过栈来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每调用一个算法,就为它在栈顶分配一个存储区,每退出一个算法,就释放它在栈顶的存储区,当前运行算法的数据一定是在栈顶的。

递归算法的优缺点

递归算法结构清晰,可读性强,并且容易用数学归纳证明算法的正确性,为设计算法和调试程序带来很大的方便。但是递归调用运行效率比较低,无论是计算时间还是占用的存储空间都比非递归算法要多。

有时希望在递归算法中消除递归调用,使其转化为一个非递归算法。通常采用的方法是,用一个用户的栈来模拟系统的递归调用工作栈,从而达到将递归算法改为非递归算法的目的。

算法实例
阶乘函数

我们首先通过阶乘函数来了解递归,我们可以很容易的将阶乘函数的递归公式写出来:

我们通过这个递归公式,可以很容易的写出计算阶乘的递归函数

#阶乘问题
def factorial(n:int):
'''
        n为输入,为n!
    '''
if n == 0:
return 1
else:
return n*factorial(n-1)


#时间复杂度为O(n)

可以发现,我们在写递归函数的时候,很大程度上依赖于我们根据阶乘这个具体问题总结出来的递归公式,根据递归公式可以很容易的写出对应的递归函数。

Fabonacci数列

学过数学的人应该都知道Fabonacci数列,1,1,2,3,5,8,13…,我们可以很容易的写出这个数列的递归公式表示

同样的,根据这个递归公式我们就可以方便的写出递归函数

#Fabonacci数列
def fabonacci(n:int):
'''
        n为输出fabonacci数列的第多少个
    '''
if n <= 1:
return 1
else:
return fabonacci(n-1)+fabonacci(n-2)


#时间复杂度为O(2^n)  调用过程类比二叉树
全排列问题

求解一个无重复元素数组的全排列,即list中所有元素需要考虑顺序问题的全部可能的排列。比如[1,2,3],它的全排列是 123,132,213,231,312,321,它的个数为len(list)!

我们根据上面的两个问题的思路,我们这里也要根据全排列问题,总结出来一个递归公式。我们记数组R[r_1,r_2,r_3,…]的长度为n,记P(R)为数组R的全排列,记R_i=R-[r_i],记r_iP(R_i)表示在全排列P(R_i)的每一个排列前加上前缀r_i得到的排列组合。

当n=1时,我们可以得到P(R) = R = [r] ,数组R中只有r一个元素,自然也就只有一种排列。

当n>1时,我们可以写出P(R) = r_1P(r_1)+r_2P(r_2)+…+r_nP(r_n)

上面的关系实际上给出了计算P(R)的递归公式如下:

既然总结出了全排列问题的递归公式,我们接下来就可以写出具体的递归函数

#组合全排列问题
def swap(s:list,m:int,k:int):
'''
        交换数组s中指定位置m,k处的两个字符
    '''
    s_ = s.copy() #切记不可以直接使用 s_ = s,否则修改s_会直接影响到s,因为使用s_ = s时,是将s_指向s所指向的数组首地址,这样其实s_和s指向的是同一个数组。
    temp = s_[m]
    s_[m] = s_[k]
    s_[k] = temp
return s_


def perm(s:str):
'''
        s为输入的list
    '''
    result = [''] #每次函数调用完返回的中间数组
    result_ = [] #临时辅助数组
    string = '' #临时辅助空字符串
    
if len(s) < 1:
return ['']
elif len(s) == 1:
return s
else:
for i in range(len(s)):
            str_ = swap(s,0,i)
for j in perm(str_[1:]): #获得riP(ri),其中perm(str_[1:])获得的是P(ri)
                string = s[i]+j
for w in result:
                    result_.append(w+string) #更新list
        result = result_ #这里可以直接使用 = ,是因为下面result_指向了【】,而result并没有改变
        result_ = []
return result


整数划分问题

正整数可以表示为一系列的正整数之和,例如:
n = n_1 + n_2 + n_3 + … + n_k  (其中n_1>=n_2>=…>=n_k>=1)

我们目的是对于一个正整数n,到底有多少条符合条件的划分

我们考虑能不能把问题分解,使问题的规模变小,这里我们引入一个中间变量m,对于正整数n,将可能的划分中最大加数n_1不大于m的划分个数记为q(n,m)。

那么我们很容易就能得到,当 m = 1  或者 n = 1 的时候,q(n,m) = 1, m=1时即n_i全为1,自然也就只有一种情况。

当 n=m 的时候,划分可以由 n_1 = n 的划分 和 n_1<=n-1 的划分组成 ,即q(n,m)=q(n,n)=1+q(n,n-1)

当 n < m 的时候,n_1 <= n < m ,则q(n,m) = q(n,n)

当 n > m 的时候,划分可以由n_1 = m的划分 和n_1<=m-1的划分组成,这个时候q(n,m) = q(n-m,m) + q(n,m-1)

根据上面的关系得到q(n,m)的递归公式如下

所以整数划分的递归函数可以写成下面这个样子

#整数划分问题
def q(n:int,m:int):
if n < 1 or m < 1:
return 0
if n == 1 or m == 1:
return 1
if n == m:
return 1 + q(n,n-1)
if n > m :
return q(n,m-1) + q(n-m,m)
if n < m:
        return q(n,n)
hanoi问题

下面是递归算法中最著名的汉诺塔问题,想必大家在最初学习递归算法的时候肯定被折磨过很多次,这里我们试着能不能像上面的问题一样,总结出一个递归公式,或者得到一个递归流程。

比如要将a上面的盘子转移到b上面,当a上面只有一个盘子的时候,直接a->b,如果a上面有两个盘子,步骤是a->c,a->b,c->b,那当我们变成三个盘子的时候,我们还像这样写出我们的步骤:a->b,a->c,b->c,a->b,c->a,c->b,a->b。

从上面我们的步骤中可以发现,我们要将a上面的n个盘子放到b上面,我们应该首先将n-1个盘子放到c上面,然后a->b,再将c上面的n-1个盘子,转移到b上。那么n-1个盘子放到c上面,首先应该将n-2个盘子放到b上面,然后a->c,再将b上面的n-2个盘子放到c上,接下来循环往复… 我们可以惊人的发现,这不就是递归的思想了吗,那整个hanoi的问题就可以简单的描述为:

可以很快的写出来hanoi的递归函数了:

#汉诺塔问题
def hanoi(n:int,a:str,b:str,c:str):
if n < 1:
        print('input error!')
if n == 1:
        print(a+'-->'+b)
else:
        hanoi(n-1,a,c,b)
        print(a+'-->'+b)
        hanoi(n-1,c,b,a)

个人博客:https://blog.csdn.net/Pual_wang/article/details/100604688

参考内容

计算机算法设计与分析(第四版) 王晓东著

https://www.cnblogs.com/king-lps/p/10748535.html

https://www.cnblogs.com/xiaoyunoo/p/3519577.html

人生不易,且行且珍惜

机器学习初学者

黄海广博士创建的公众号,黄海广博士个人知乎粉丝21000+,github排名全球前120名(30000+)。本公众号致力于人工智能方向的科普性文章,为初学者提供学习路线和基础资料。原创作品有:吴恩达机器学习个人笔记、吴恩达深度学习笔记等。

往期精彩回顾

备注:加入本站微信群或者qq群,请回复“加群

加入知识星球(4100+用户,ID:92416895),请回复“知识星球

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值