python递归函数写法_Python递归函数什么意思?怎么用?

好的,让我来试试能不能用尽量容易理解的方式讲一讲递归。

我会举几个例子,可以自己挑着看,如果你觉得哪一个例子,更容易理解,可以评论里跟我说一声。

我们分下面几部分:基础知识

递归的写法

f(n) = f(n-1) +1递归函数举例

倒序输出正整数

二叉树的递归查找举例

递归习题

习题部分有问题的可以在评论区评论。

基础知识

要理解递归需要的基础知识只有一点:在你调用了一个函数之后,函数会从内存中开拓出一个新的地方,来保存当前函数的参数变量,以及你在这个函数里面声明的变量。即使是自己调用自己。

看下面的例子:

def func(depth):

if depth == 2:

print("当前深度为2,即将返回到 func(1) ")

return

print("当前深度为:%d" % depth)

func(2)

print("从 func(2) 中返回到 func(1),当前深度为%d" %depth)

调用 func(1) 输出为:

当前深度为: 1

当前深度为2,即将返回到 func(1)

从 func(2) 中返回,当前深度为 1

这段代码做的事情很简单,我们调用 func(1) 然后 func(1) 又自己调用了 func(2) 。我们可以在看到在 func(1) 中 depth 值为1,虽然在进入 func(2) 后,depth 为 2,但是从 func(2) 中返回后,depth 值仍为1。也就是说,func(1) 中的 depth 变量跟 func(2) 中的 depth 变量是不同的两个变量。

我们用调用图表示是这样子的:

函数内的局部变量也是一样道理的,func(1)中的局部变量是属于func(1)的,与func(2)的无关。这些都是函数部分的基础知识。

递归的写法

递归函数,其实就是自己调用自己的函数。这是递归函数的第一要素。

即然递归函数是自己调用自己的函数,那样子不就死循环了吗?为了防止造成死循环,我们就需要有一终止递归的条件,我们需要知道在什么情况下,递归函数不再自己调用自己,这个终止递归的条件,就是递归函数的第二要素。

所以,其实写递归函数,只需要确定以下两个要素就可以了:终止递归的条件

非终止递归条件下,自己调用自己

一般情况下,我们写递归条件,直接两步走就行了:先判断是否处于终止递归,是则返回

做一些处理,然后自己调用自己

其实,我们上面基础知识中的代码,就已经用到了递归。我们就拿这部分代码来举个例子:

def func(depth):

# 这是第一部分,判断终止递归的条件(深度为2时,终止递归,不再自己调用自己)

if depth == 2:

print("当前深度为2,即将返回到 func(1) ")

return # 直接返回,跳出当前的函数

# 这是第二部分,做一些处理(这里是打印depth的值),然后自己调用自己

print("当前深度为:%d" % depth)

func(2) # 自己调用自己

print("从 func(2) 中返回到 func(1),当前深度为%d" %depth)

f(n) = f(n-1) +1 递归函数举例

这种函数,我们经常在数学中看到:

f(n) = f(n-1) + 1

f(1) = 1

其实数学中的这种函数就是递归函数:

f(1) = 1 # 这是递归的终止条件

f(n) = f(n-1) + 1 # 这是第二部分,自己调用自己的部分

所以我们写成代码是这样子写的:先判断递归的终止条件:

def f(n):

if n == 1:

return 1 # 我们知道 f(1) 的值是1,不需要通过调用 f(0) + 1 来确定 f(1) 的值,我们直接返回1就行了

2. 现在,写第二部分:

def f(n):

# 这是第一部分

if n == 1:

return 1 # 我们知道 f(1) 的值是1,不需要通过调用 f(0) + 1 来确定 f(1) 的值,我们直接返回1就行了

# 这是第二部分

return f(n - 1) + 1 # 调用 f(n-1)取得 f(n-1) 的值,加1计算得到 (fn) 的值并返回

调用 f(3) 输出如下:

>>> f(3)

3

整个过程的调用如下:

正序与倒序输出正整数

假设我们有一个正整数 2019 我们要求正序与倒序输出其中各位的数,如:

# 正序输出,每行一个

2

0

1

9

# 倒序输出,每行一个

9

1

0

2

我们想到的算法是,我们先用求余10的方法,取得它的个位数:例如2019 % 10 = 9

使用除以10的方法,取得剩下还没有输出的的整数:2019 / 10 = 201

整个过程应该是:

# 2019

2019 % 10 = 9 => 输出 9

2019 / 10 = 201

# 201

201 % 10 = 1 => 输出 1

201 / 10 = 20

# 20

20 % 10 = 0 => 输出 0

20 / 10 = 2

# 2

2 % 10 = 2 => 输出 2

2 / 10 = 0

# 0

0 => 直接返回,终止。

我们可以看到,这是一个倒序输出的算法。

我们的递归函数终止条件是什么:如果当前值等于0,那么就终止返回

而我们的递归函数的第二部分是什么?我们看上面的算法可以看到,我们一直重复做的除以10,求余10其实就是我们的递归函数的第二部分:

def reverse_order(n):

# 第一部分,递归终止条件判断

if n == 0:

return

# 第二部分

print(n % 10) # 打印 n % 10 的值,也就是它的个位数

reverse_order(int(n / 10)) # 调用自己打印剩下的还没有输出的整数

运行结果示例:

>>> reverse_order(2019)

9

1

0

2

调用图如下:

那么怎么进行正序打印呢?

其实很简单,相对于倒序打印来说我们进行的操作是:

先打印出 2019 % 10 = 9

调用 reverse_order(201) 打印剩下的正整数 201

我们只要调换一下这两个操作的顺序就行了:

先调用 reverse_order(201) 来打印 201

201打印过了,现在我打印出 2019 % 10 = 9

代码如下:

def order(n):

# 第一部分,递归终止条件判断

if n == 0:

return

# 第二部分

order(int(n / 10)) # 先打印 n / 10 部分

print(n % 10) # 再打印 n % 10 的值,也就是它的个位数

结果示例如下:

>>> order(2019)

2

0

1

9

调用图如下:

所以现在大家应该可以理解调用自己后,再处理(打印),与先处理(打印)后再调用自己所产生的区别了吗?

二叉树的递归查找举例

接下来我们举一个与数据结构结合的例子:

假设,我们有一个结点:我是一个小结点

我们可以给这个结点添加一个左结点,但是左结点必须小于这个结点:在我左边的是比我小的

我们也可以给这个结点添加一个右结点,但是右结点必须大于这个结点:在我右边的是比我大的

这种拓扑结构就是树结构。在这里,我们做了“左小右大”的规则限定。

假设当前结点为结点12:根结点为12的树

我们面临的问题是:当前结点不是我们目标结点(结点75),请在当前结点的左子树或右子树中,查找值为75的结点。

根据左小右大,我们可以知道因为目标结点值75大于当前结点的12,所以我们应该去 当前结点.右子树 中去找目标结点75:

def 查找结点(当前结点, 目标结点值): # 当前结点为 结点12, 目标结点值为75

if 目标结点值 > 当前结点值: # 75 > 12,去右边找

查找结点(当前结点.右子树, 目标结点值) # 即 查找结点(结点12.右子树, 75),查到后,把结果返回

走到根结点12的右边来

当我们在 350 结点处时,我们面临着与在根结点12时,一样的问题:当前结点不是我们目标结点,请在当前结点的左子树或右子树中,查找值为75的结点。

因为75小于350,所以我们要去 当前结点.左子树 去找结点75:

def 查找结点(当前结点, 目标结点值): # 当前结点为 结点350, 目标结点值为75

if 目标结点值 < 当前结点值: # 75 < 350 要去左子树查找

return 查找结点(当前结点.左子树, 目标结点值) # 即查找结点(结点350.左子树,目标结点值75),查到后,把结果返回走到结点50处

现在,我们又面临着跟之前一样的问题:当前结点不是我们目标结点,请在当前结点的左子树或右子树中,查找值为75的结点。

因为75大于50,所以要去 当前结点.右子树 中找:

def 查找结点(当前结点, 目标结点值): # 当前结点为结点50, 目标结点值为75

if 目标结点值 > 当前结点值: # 75 > 50,去右边找

return 查找结点(当前结点.右子树, 目标结点值) # 即 查找结点(结点50.右子树, 75),查到后,把结果返回找到啦!

当我们走到结点50的右子树中去时,发现当前结点的值就是75,就是我们要查找的值,我们就可以返回这个结点了,不用再往下去了。

def 查找结点(当前结点, 目标结点值): # 当前结点为结点75, 目标结点值为75

if 目标结点值 == 当前结点值: # 5= == 50,找到啦!

return 当前结点

现在我们可以知道,这就是第一步递归终止条件:如果当前结点是我们要找的结点,直接返回

在这里我们不考虑不存在的条件,如果考虑的话,递归终止条件再加一个就是了:如果当前结点不是我们要找的结点,可是它已经没有子树了,直接返回 None,表示没有找到我们要的结点

如果当前结点就是我们要找的结点,直接返回当前结点

而之前一直面临的同一个问题,其实就是递归函数第二部分的代码。我们把之前的代码合并在一起就完成了这个递归函数的代码(还是按两步走写):

def 查找结点(当前结点, 目标结点值):

# 这是第一部分,递归函数的终止条件判断

if 目标结点值 == 当前结点值:

return 当前结点 # 如果找到了直接返回

# 这是第二部分,根据情况调用自己

if 目标结点值 > 当前结点值:

return 查找结点(当前结点.右子树, 结点值)

elif 目标结点值 < 当前结点值:

return 查找结点(当前结点.左子树, 结点值)

(不知道为啥,举了这么个例子,就直接上伪代码吧,大家看得懂就行 ==)

递归习题

最经典的递归例题,斐波那契数列:

f(n) = f(n-1) + f(n-2)

f(1) = 1

f(2) = 1

写一个递归函数 f(n) 打印出斐波那契数列的第 n 位。

(斐波那契数列从第1位起为:1、1、2、3、5、8、13……)

汉诺塔问题:

(不想打了,我直接拉百科的描述过来了==)

相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

写一个递归函数,f(n) 输出应该怎么操作才可以把 n 个盘子从 A杆转移到 C 杆上。

欧几里德算法求最大公约数

假如需要求 1997 和 615 两个正整数的最大公约数,用欧几里德算法,是这样进行的:

1997 / 615 = 3 (余 152)

615 / 152 = 4(余7)

152 / 7 = 21(余5)

7 / 5 = 1 (余2)

5 / 2 = 2 (余1)

2 / 1 = 2 (余0)

至此,最大公约数为1

以除数和余数反复做除法运算,当余数为 0 时,取当前算式除数为最大公约数,所以就得出了 1997 和 615 的最大公约数 1。

写一个递归函数,f(n, m) 求得 n, m 的最大公约数。

想不到了,大家有什么好的例题可以评论里讲一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值