深入理解递归

​ 我们再写程序时经常会使用递归函数来解决问题,但是对于很多人来说,递归是比较难以理解的,或者说,很多人对于递归的理解是不够深入的,而如果在对递归的理解不深入的情况下就贸然地使用递归的话,往往就会使我们程序出现各种意想不到bug,而且往往也难以排错。而且有很多经典地算法也涉及到递归。因此深入理解递归对我们IT人员来说至关重要,本篇博客将会详细地讲解关于递归的内容。

首先是关于递归的定义:在编程语言中,我们将调用自身的函数称作递归函数,即定义一个函数,在这个函数的可执行代码中再调用自己本身函数。

其次,也是最重要的,关于递归函数的基本原理:递归函数是用栈结构来实现的,递归的整个过程就是一个入栈和出栈的过程,当程序执行一个函数时就会创建一个新的受保护的独立空间(新函数栈)。在递归函数执行时,由上一层进入下一层函数时,上一层会暂停执行,并且会暂停局部变量的值、寄存器中保存的数据等,各层之间的局部变量是相互独立、互不影响的,这点就好比在一个py文件中定义一个函数并执行,函数里面的变量与外面的变量是独立的,就算变量名相同也互不影响。但在这里要注意,如果函数参数的类型是对象的话,内层函数改变参数的有可能影响到外层函数,因为对象类型的变量本质上指向的是一个内存地址,如果地址中所储存的内容发生改变的话,各层函数的相同参数的值也会发生改变,因为各层暂停的变量的值都是相同的内存地址,同样可以类比在py文件中向函数里面传递一个数组,在函数内部修改数组的值,外面的数组的内容同样会发生改变。

然后,关于递归的几个基本规则:1.递归函数必须设置函数出口(结束条件),并且函数的执行方向必须朝着满足出口条件的方向进行,否则就会无限递归,造成栈溢出。2.当下一层的函数执行完毕后上一层的函数才会执行,所以只有当最后一层函数执行完毕后,每层函数才会由内向外依次执行,当最外层函数执行完毕后,整个递归函数才算执行完毕。

接下来,用几个具体的例子来理解递归:

1.求n!

def fn(n):
    if n ==1 or n == 0:
        return 1
    else:
        return n*fn(n-1)

print(fn(5))
#120

这个函数的执行情况如图

上面的题很简单,下面用一个竞赛题来逐步分析复杂的递归过程

2.回路计数问题:

下面用递归的方式来解这道题,算然该方法并不是最优解,但是通过追踪步骤可以让我们更加清晰地认识递归。

import math
import sys
sys.setrecursionlimit(10000)
n = int(input())
li = [i for i in range(2, n + 1)]
def dfs(li, li_tmp):
    global count
    if len(li_tmp) == len(li):
        count += 1
        return
    for j in range(len(li)):
        tmp = li_tmp[:]  # 进行浅拷贝,不然后面回不去,tmp每次指向的内存地址都不一样
        if li[j] not in li_tmp and math.gcd(li_tmp[-1], li[j]) == 1:
            li_tmp.append(li[j])
            dfs(li, li_tmp)
            li_tmp = tmp[:]
count = 0
for i in range(len(li)):
    li_tmp = [li[i]]
    dfs(li, li_tmp)
print(count)

我们以n=5为例,分析当li_tmp = li[0] 即li_tmp = [2]时,函数的递归过程

下面是调试的具体过程:

​ 一开始,当li = [2,3,4,5]和li_tmp = [2]传进去时,会先进行if判断,注意已经执行完的部分在以后的递归中不会再执行,因为上一层代码再次执行是执行后面没有执行完的代码,执行完if判断之后进入for循环,当遍历到的值满足条件时,就会进入下一层函数,这里要注意的是如果此时的for循环没有执行完,那么当从内向外再次返回执行时,就会接着后面的内容执行,没有执行完的循环也会再次执行,直到循环结束。就这样一层一层地执行,直到li_tmp = [2,3,4,5],最后一层函数判断if len(li_tmp) == len(li) 满足条件,执行return,函数执行完毕,这时候,函数就会返回到上一层。

而上一层函数n=3,for循环内执行到的dfs(li, li_tmp)执行完毕,此时li_tmp = [2,3,4,5],然后接着向下走li_tmp = tmp[:],注意这里一步使得该层函数的li_tmp指向了一个新的内存地址,li_tmp变为[2,3,4],然后返回到for循环顶部,发现不满足条件退出循环回到上上层函数。

然后要知道,在上上层li_tmp所指向的内存地址依然是之前的,所以li_tmp 又变为了[2,3,4,5],上面我们说过上层函数暂停时会暂停局部变量的值、寄存器中保存的数据等,各层之间互不影响。因为上上层的i=2,它的for循环还没有到头,所以它会再次进行for循环,并执行for循环里面的代码,从而再次进行新的递归。。。。后面的内容就大同小异了。

​ 通过分析上诉例子,我们可以得知,当上层函数暂停执行时,函数内部的变量会保存当时的值,而且其中的控制语句如for循环等也会暂停执行等待回归重启,同时也要注意各层函数之间传递的参数的类型。

posted @ 2022-03-22 19:29  草帽小子路飞  阅读(292)  评论(0编辑  收藏  举报