【算法专项三】递归

前言

最近在读一本叫做《算法图解》的书,很有意思,将很多算法解析的非常简洁明了,比算法导论不知道高到哪里去了(不是)

这里简单记录一下看书过程中的相关思考。

关于递归

关于递归,有一个很好的名词可以解释,那就是 “套娃”,例如,当我们在一个大箱子中找钥匙时,箱子中还存在其他的小箱子,而小箱子里面还可能有箱子,这时,我们仅需要按照以下三步执行:

  1. 检查当前箱子中的每样物品
  2. 如果该物品是箱子,则回到第一步
  3. 如果是钥匙,则大功告成!

需要注意的是,递归并不能提高算法性能,但是很大程度上能够提高可读性(前提是要能理解。。)

基线条件与递归条件

所谓递归函数,就是自己会调用自己,因此如果没有停止的条件,那就会调用个没完了!
例如,当我们想利用递归实现一个倒计时的函数,如果我们这样写:

def countdown(i):
	print(i)
	countdown(i-1)

我们会发现程序会无限循环下去,直到机器崩溃。。。

因此,当我们想去实现一个递归方法时,必须要告诉递归何时停止才行。因此,所有的递归函数都存在两个部分:

  1. 基线条件(base case)——指函数不再调用自己
  2. 递归条件(recursive case)——指函数调用自己

回到上面的例子:

def countdown(i):
	print(i)
	# 基线条件:
	if i <= 1:
		return
	# 递归条件:
	else:
		countdown(i-1)

虽然上面介绍了递归的逻辑设计,但是总感觉还是云里雾里,好像明白了,但是又感觉各种调用和返回有些跳跃。
这里,就必须要介绍递归实现的底层原理——栈。

栈这种数据结构自然不必多说,先进后出
我们要说的是计算机内部使用的被称为 “调用栈” 的结构。

这里,我们以下面这段代码举例:

def greet(name):
	print("hello, ", name)
	greet2(name)
	print("time to say goodbye")
	bye()

def greet2(name):
	print("nice to meet you, ", name)

def bye():
	print("ok, bye")

当我们调用greet("maggie")这个方法时,会发生以下事情:

  1. 首先,计算机会将函数greet、变量name以及值maggie以栈的形式,整块存储到内存中,逐步执行,直到碰到greet2方法,此时,会先打印出hello, maggie
  2. 在遇到第二个greet2方法后,计算机同样会将相关信息压入栈中,此时第二个内存块在顶部,因此会先执行greet2中的相关内容,也即会打印:nice to meet you, maggie
  3. 这里有一个重要概念:当我们调用greet2时,实际上greet方法只执行了一部分,也即:调用另一个函数时,当前函数暂停并出于未完成的状态。因此,当greet2执行完毕后,会再次回到函数greet中,并接着打印:time to say good bye,接着调用bye方法,打印,结束。

回到递归的例子中来,还是以上面的检查盒子为例,此时如果我们有盒子A,打开后发现有盒子B和C,这时我们检查盒子B,发现其中还有盒子D,当我们再检查盒子D时,发现已经是空的了,那么,此时调用栈类似于:
在这里插入图片描述
最右侧代表着待检查的盒子,我们可以看到,这个栈中包含了未完成的函数调用(例如上图中的盒子C还未检查),这就是递归中“自动记录”的特性。

对于任何函数递归调用而言,都可以通过这种函数栈的分析模式,总结起来,就是如下步骤:

执行、暂停、深入、停止、返回、继续

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值