《Python 编程》笔记(十六)

这篇笔记介绍了如何使用 Python 实现堆栈、集合和二叉搜索树。堆栈部分讨论了列表实现及其性能优化,包括元组树的使用。集合部分对比了列表和字典实现的优劣。二叉搜索树部分展示了其用于快速搜索的优势。文中还提到了性能分析和优化的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

本节笔记主要记录如何使用 Python 构建一些高级的数据结构,如堆栈等。

堆栈

  • 在嵌套结构中,堆栈是进度追踪的天然机制。
  • 堆栈主要的操作:压栈出栈
  • Python 中可以使用列表的来实现堆栈,只需要保证在列表的一端进行操作即可。尤其是配合 popappend 方法可以轻松实现。
  • 此外,也可以使用列表其它特性来实现堆栈:

    
    # 列表的头作为栈顶
    
    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 堆栈的方法是使用 appendpop 组合方法,从而直接对列表元素操作,无需复制列表。

  • 总结:基于元组树的栈在没有索引的情况下性能优越,而采用原地修改列表(即使用 appendpop)的方法效率也很高。

  • 运行时间分析统计:

    • 使用 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,该版本只能支持可哈希的对象。

  • 事实上,也可以通过继承内置类型来实现同样的上述的堆栈和集合。但是也会出现一些问题,比如会暴露一些其他的函数等。

二叉搜索树

  • 它是一种有序的数据结构:小于任一节点的元素放在左子树,大于任一节点的元素放在右子树;最下方的叶子节点是空的。它易于快速递归遍历,二分搜索。

  • 使用字典可以实现高效快速的查找或搜索操作,test.py 脚本演示了在一百万个数中进行一万次判断操作,给出了分别使用列表和字典键存储元素的方式得到的累计 CPU 时间消耗:

    list: 0.810809
    dict: 0.0007580000000000364
  • 二叉树实现:需要一个三元素的元组来表示(LeftSubtree, NodeValue, RightSubtree)。

  • 简单的二叉搜索树实现参见 btree.py,以下是其工作时的运行结果:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值