python数据结构与算法-递归
python数据结构与算法-递归
- 前言
- 作用域
- 运行时候的堆与栈
- 递归函数
- 整数之和
- 反转列表和字符串的递归
- 使用类型反射
- 总结
前言
学习python数据结构和算法。该书的第三章节-递归。
作用域
图1 简单程序中的作用域
作用域分为:
Local 局部作用域:
举例:图中第16行内棕色区域。
Enclosing 封闭作用域:
举例:运行到23行时,historyOfOutput列表在绿色区域中查找不到,需要在紫色区域中找。紫色区域为封闭作用域。
Global 全局作用域:
举例:PI常量位于全局作用域。
Built In 内置作用域。
举例:int函数为python内置函数。内置作用域
运行时候的堆与栈
图2 运行时堆栈和堆
当Python解释器执行程序的第23行和第24行时,运行时堆栈如图2所示。运行时堆栈上有三条激活记录。推送到运行时堆栈上的第一条激活记录是针对模块的。当模块首次开始执行时,Python解释器从上到下遍历模块,并将模块范围内的任何变量定义放入模块的激活记录中。在这个程序中,参考PI的值为3.14159。
然后,在模块的末尾,if语句调用了main函数。这导致Python解释器推送主函数的激活记录。主函数中定义的变量包括historyOfPrompts、historyOfOutput、rString、r和val。每个变量都出现在主函数的激活记录中。
当主函数开始执行时,它调用了getInput函数。当该调用发生时,为函数调用推送了一条激活记录。该激活记录包含prompt和x变量。这个激活记录没有出现在图中,因为当我们执行程序的第23行和第24行时,Python解释器已经从getInput函数返回。当解释器从函数调用返回时,相应的激活记录会从运行时堆栈中弹出。
最后,程序在第26行调用showOutput函数,并开始执行该函数。调用showOutput时,showOutout函数调用的激活记录被推送到运行时堆栈上。该作用域的本地引用(仅包括val变量)存储在此函数调用的激活记录中。
递归函数
- 1.确定函数的名称、传递参数以及函数应返回的值。
- 2.递归函数的基本情况。基本情况是一个if语句,它通过返回一个值来处理递归函数中的一个非常简单的情况。
- 3.朝着基本情况靠近。使用一个或多个参数递归调用函数,参数在某种程度上小于上次调用时传递给函数的参数
整数之和
求和公式:
∑i=1ni=n(n+1)2\sum_{i=1}^{n} i = \frac{n(n+1)}{2} i=1∑ni=2n(n+1)
def sumFirstN(n):return n * (n + 1) // 2def main():x = int(input("Please enter a non-negative integer: "))s = sumFirstN(x)print("The sum of the first", x, "integers is", str(s) + ".")if __name__ == "__main__":main()
递归三要素:
1.函数名和传递参数。
2.基本情况
∑i=10i=0\sum_{i=1}^{0} i = 0 i=1∑0i=0
3.朝着基本情况 n= 0 靠近。
def recSumFirstN(n):if n == 0:return 0else:return recSumFirstN(n-1) + ndef main():x = int(input("Please enter a non-negative integer: "))s = recSumFirstN(x)print("The sum of the first", x, "integers is", str(s)+".")if __name__ == "__main__":main()
反转列表和字符串的递归
非递归形式的反转
def revList(lst):# 初始化一个空列表作为累加器accumulator = []# 从原列表的末尾开始遍历,将每个元素添加到累加器中for i in range(len(lst) - 1, -1, -1):accumulator.append(lst[i])# 返回累加器,即反转后的列表return accumulator
递归形式
1.函数名和参数,返回值。
2.基本情况 列表为空
3.朝着基本情况靠近
def revList(lst):# 基本情况:如果列表为空,返回空列表if lst == []:return []# 递归情况:处理列表的剩余部分# lst[1:] 是除了第一个元素外的所有元素restrev = revList(lst[1:])first = lst[0:1] # 获取第一个元素# 将剩余部分和第一个元素拼接起来result = restrev + firstreturn resultdef main():# 打印反转后的列表print(revList([1, 2, 3, 4]))if __name__ == "__main__":main()
def revString(s):# 基本情况:如果字符串为空,返回空字符串if s == "":return ""# 递归情况:处理字符串的剩余部分# s[1:] 是除了第一个字符外的所有字符restrev = revString(s[1:])first = s[0:1] # 获取第一个字符# 将剩余部分和第一个字符拼接起来result = restrev + firstreturn result```python
def main():# 打印反转后的字符串print(revString("hello"))if __name__ == "__main__":main()
使用切片使列表或字符串在每次递归调用时变小。可以使列表或字符串变小,而无需实际减小其物理尺寸。使用索引跟踪的位置列表中的递归可以使列表或字符串变小。在这种情况下,编写一个调用辅助函数进行递归的函数会有所帮助。使用名为revListHelper的嵌套辅助函数来执行实际的递归。列表本身在辅助函数中不会变小。相反,当返回空列表时,index参数会变小,倒数到-1。revList2函数只包含一行代码来调用revListHelper函数。
因为revListHelper函数嵌套在revList2中,所以除了revList2函数之外,其他任何东西都看不到辅助函数,因为我们不希望其他程序员调用辅助函数,除非先调用revList2功能。
重要的是要注意,在递归函数中使用它时,您不必物理地使列表或字符串变小。只要索引对您可用,递归函数就可以将索引用于列表或字符串,并且每次递归调用时索引都会变小。
还有一件事要注意。在这个例子中,索引在每次递归调用时接近零而变小。递归函数的参数还有其他减小的方法。例如,这个例子可以重写,这样索引就会随着列表的长度而增长。在这种情况下,索引和列表长度之间的距离是每次递归调用时都会变小的值。
def revList2(lst):def revListHelper(index):if index == -1:return []restrev = revListHelper(index-1)first = [lst[index]]result = first + restrevreturn resultreturn revListHelper(len(lst)-1)def main():print(revList2([1, 2, 3, 4]))if __name__ == "__main__":main()
使用类型反射
反转列表或者字符串两个函数中的许多相似之处,归因于Python的重载。 Python具有另一个非常出色的功能,称为Reflection。
反射是指代码能够检查可能将作为参数传递给函数的对象的属性的能力。反射的一个有趣方面是能够查看对象的类型。如果我们编写类型(OBJ),则Python将返回代表OBJ类型的对象。例如,如果OBJ是对字符串的引用,则Python将返回STR类型对象。此外,如果我们编写str(),我们会得到一个是空字符串的字符串。换句话说,写入str()与写“”是同一件事。同样,list()写作[]一样。使用反射,我们可以编写一个递归反向函数,该函数将用于字符串,列表以及支持切片和串联的任何其他序列。递归版本的反向,将在中提供逆转字符串和列表。
def reverse(seq):SeqType = type(seq)emptySeq = SeqType()if seq == emptySeq:return emptySeqrestrev = reverse(seq[1:])first = seq[0:1]result = restrev + firstreturn resultdef main():print(reverse([1, 2, 3, 4]))print(reverse("hello"))
总结
介绍了一些对您理解本文稍后介绍的算法很重要的概念。了解运行时堆栈和堆是如何工作的,以便在我们的程序中调用函数,成为一名更好的程序员。构建一个关于代码工作原理的心理模型,可以预测代码的功能。编写递归函数对计算机程序员来说也是一项重要的技能。以下是本文应该学到的:
•能够识别程序中的各种作用域。
•能够识别变量引用属于哪个范围:局部、封闭、全局或内置范围。记住LEGB规则。
•能够通过绘制程序执行时的运行时堆栈和堆的图片来跟踪程序的执行。
•能够通过编写一个基本情况和一个递归情况来编写一个简单的递归函数,其中函数以较小的值调用。
•能够跟踪递归函数的执行,在执行时显示运行时堆栈和堆。
•了解一点反射,因为它与检查Python代码中的类型有关。
参考文献
[1]Data Structures and Algorithms with Python[M]. 2016.