引言
本节笔记主要记录如何使用 Python 构建一些高级的数据结构,如堆栈等。
堆栈
- 在嵌套结构中,堆栈是进度追踪的天然机制。
- 堆栈主要的操作:压栈、出栈。
- Python 中可以使用列表的来实现堆栈,只需要保证在列表的一端进行操作即可。尤其是配合
pop
和append
方法可以轻松实现。 此外,也可以使用列表其它特性来实现堆栈:
# 列表的头作为栈顶 top, stack = stack[0], stack[1:] # python 1.x+ top, * stack = stack # python 3.x # 列表的尾作为栈顶 stack, top = stack[:-1], stack[-1] # python1.x *stack, top = stack # python 3.x
栈的详细实现代码,请参见采用一般函数实现的 stack.py 和采用类实现的 stack_class.py。实际上在示例二中,类可以扩展 Python 内置数据类型,并且还可以复用。
找出类中代码执行性能瓶颈的一个方法就是在数据结构中添加能够追踪并统计调用次数的代码,然后可以在运行一段时间后进行分析。实际实现比较容易,只需要通过继承的方法,即可扩展出一个带有性能监控的类了。完整的代码实现及测试参见 stacklog.py,以下为核心扩展代码:
class StackLog(Stack): """ 扩展类,添监控的功能 """ def __init__(self, start=list()): # 实际上还可以这样:super(StackLog, self).__init__(start) super().__init__(start) self._pushes = 0 self._pops = 0 self._max_len = 0 def push(self, x): # 真正进行入栈操作 super().push(x) self._pushes += 1 # 记录最大的长度,实例级别的统计 self._max_len = max(self._max_len, len(self)) def pop(self): # 进行真正的出栈操作 self._pops += 1 return super().pop() def status(self): return self._max_len, self._pushes, self._pops
需要注意的是,上面的两种方法实现的栈效率不是特别高,特别是对于大型的栈。因为上面的实现涉及到大量的复制列表操作,所以对于大规模的栈来说,这样的开销就比较大了。为此,可以采用另外一种方法来实现栈:元组树。可以把栈的元素存放在由元组组成的二叉树上:每个元素都组成一个元组
(object, tree)
,object
是栈中的元素,而tree
则要么是None
,要么是指向下个元组的引用(有些类似于 C 指针)。所以,一个栈:[1, 2, 3, 4] (假设头部为栈顶),则对应的元组树表示为 (1, (2, (3, (4, None))))。这样,对对象的操作就知识对元组的顶层操作,不会涉及整个栈的复制。基于元组树的优化实现参见 stack_tuple_tree.py,在学习这个例子时,需要留心如何采用有些技巧遍历树中的节点的。此外,
__getitem__
重载可以帮助实现x[i]
,in
,for
等操作,请参见相应代码的测试。当然了,另外一种优化上面 stack_class.py 堆栈的方法是使用
append
和pop
组合方法,从而直接对列表元素操作,无需复制列表。总结:基于元组树的栈在没有索引的情况下性能优越,而采用原地修改列表(即使用
append
和pop
)的方法效率也很高。运行时间分析统计:
- 使用
timer.clock
获取 CPU 浮点时间来计算; - 使用
profile
模块收集性能数据并产生报告。
- 使用
集合
集合支持的操作:交集、并集和测试元素是否存在。此外,还有差集、子集测试等。通常可以利用集合去除重复元素。
Python 中有内置的集合
set
,提供了很多优化的集合操作。一些简单测试如下:>>> a = set([1, 2, 3, 4]) >>> b = set([3, 4, 5, 6]) # 差集 >>> a -b {1, 2} >>> b - a {5, 6} # 交集 >>> a & b {3, 4} # 并集 >>> a | b {1, 2, 3, 4, 5, 6} >>> 1 in a, 2 in b (True, False) # 此外,可以深入判断元素是否存在! >>> 4 in a, b (True, {3, 4, 5, 6})
使用列表的方式实现集合的代码参见 set_list.py。但实际上,这种实现的效率是比较低的,尤其是
in
判断操作再加for
循环会导致指数级变慢。所以,更好的一种实现是使用字典的键来代替列表存储数据。用字典实现集合可以使用快速的哈希取值,效率会更高。即字典的键存储元素,值为None
。实现代码参见 set_dict.py,该版本只能支持可哈希的对象。事实上,也可以通过继承内置类型来实现同样的上述的堆栈和集合。但是也会出现一些问题,比如会暴露一些其他的函数等。