【问题标题】:Is a Python list the same list as defined by my data structures and algorithms textbook?Python 列表是否与我的数据结构和算法教科书中定义的列表相同?
【发布时间】:2019-12-28 22:04:02
【问题描述】:

我正在阅读一本关于数据结构和基本算法的书,以更好地掌握编程。我正在阅读的书定义了一些数据结构,例如列表、数组等,并指定它们的接口,以及如何使用不同语言的现有数据结构来实现(或构造)它们。例如,它讨论了如何将列表构造为数组或链接单元格。

然而,当我开始研究不同语言的这些数据类型时,我发现它们并不总是遵循书中定义的接口。

例如,这本书说,在一个列表中,您应该能够一次遍历不同的元素,仅从一端开始。例如,如果我想要第三个元素,我必须从第一个元素开始并使用“下一个”操作,直到找到我想要的元素。但是 Python 中的列表具有整数索引,我可以使用相应的索引直接访问任何元素。在本书中,索引类型只需为具有相等比较操作的有序类型。 (我认为这本书试图通过抽象事物来保持一般性)。

这是否意味着 Python 的列表不是“真正意义上的”列表?还是书中定义的数据结构只是对这些接口的外观的建议,实际上可能会有很多变化?

我知道这本书的想法是为了更好地理解如何思考数据结构,并介绍一些典型的,所以我倾向于认为后一个答案是正确的。

【问题讨论】:

  • Python 列表比链表更接近具有自动调整大小机制的数组。正如所教授的那样,数据结构是创建许多变体和扩展的基础
  • 好的,所以列表(或者可能是链表)的定义很笼统,然后有一些变化?我想我可能选择了一个不好的例子,因为我似乎被这个名字误导了。虽然我见过一些调用通常的 Python 列表(例如[1, 2, 'hat'])数组。但是,这也不符合本书对数组的定义,因为它们应该是静态的。我认为您的评论以及@Gelineau 的回复回答了我的问题。
  • 将 Python 列表视为对数组的抽象。当您定义一个列表时,解释器实际上会创建一个指向 Python 对象的指针数组并允许您执行基于索引的查找,抽象会维护列表的当前长度,当它增长到大于最初分配的数组时,它会重新分配数组以适应任何未来的增长

标签: python data-structures


【解决方案1】:

许多数据结构有多个名称(想到hash[map/table]/dictionary/associative 数组)。您指的是两个不相关的数据结构,除了它们都存储线性项目序列这一事实。名称相似性是令人遗憾的混淆来源,但 TL;DR 是 Python 术语中的 lists 与数组非常相似,并且是基于数组构建的,而您的文本称为“列表”的 linked lists 不是不。

将实现与接口分开很重要:队列背后的底层数据结构,例如,可以是数组或链表,但这应该是客户端的实现细节接口不知道也不关心。该接口应强制保证各种操作的time complexity。数据结构文本通常使用数组或链表等各种底层“原始”数据结构来实现abstract data types,其中许多可能会影响各种操作的复杂性(提示学生分析)。

链表是简单的动态(可扩展)数据结构,可在两端提供快速的 O(1) 添加和删除操作。它们的节点可能是doubly-linked,并且通常是内存中的结构或对象,具有指向next 和(如果双重链接)previous 元素的指针或引用,以及每个节点的data 属性。链表没有随机访问——您必须从端点遍历链表才能通过跟踪指针定位单个元素。

链表通常用于实现queuesstacks 和更复杂的数据结构,如Java 的LinkedHashMap,它结合了双向链表和哈希映射。 Python 提供了collections.dequeQueue,它们是基于链表的数据结构,可以快速访问前后元素。其他库,如 Java 的 ArrayDeque,使用 circular arrays 来实现相同的双端队列接口。

树节点是单链表节点的变体,具有两个子指针,而不是单个 next 指针。与链表(及其节点)一样,树节点和树通常是接口背后的实现细节,例如 C++ map。例如,您可以使用数组实现树,heaps 就是这种情况。

Python 的 lists(也称为 ArrayLists (Java)、数组 (JS/Ruby)、向量 (C++/Haskell) 和 List (C#))是对原语的动态抽象数组,它们的大小是固定的。列表抽象添加了length 属性和许多用于操作底层数组的函数,例如append/push/push_backpopshiftsplice 等(名称取决于语言) .底层数组将自动调整大小以适应其包含的元素数量。在列表的前面或中间插入或删除元素是一个 O(n) 操作,因为需要移动尽可能多的元素以适应对数组的调整。这种转变是抽象的一部分,对客户是隐藏的。

列表的优点与数组相同:快速随机访问任何元素。对列表末尾的添加也是快速且恒定时间的,因为底层数组的偶尔重新分配会在多个追加操作中分摊。

列表与原始数组的一个重要考虑因素是内存方案。 Python 列表由对象组成,并且存在链表的许多问题,因为需要引用指向堆分配数据的指针,并且可能具有较差的locality。使用numpy array 提供了 C 数组的优势,因为您可以获得大量顺序内存,这得益于快速访问(在数据上扫描内存对齐的偏移量)和局部性。增加的开销是列表等抽象的典型成本,它会对某些应用程序产生重大的性能影响。

为了增加混乱,至少有两种高级语言,C++ 和 Haskell,有一个 "list" data structure,但它们实际上是链表而不是动态数组(列表)。

无论您使用哪种语言,重要的是区分您正在使用的数据结构真正是什么(主要是典型操作的时间复杂度)以避免无意的误用和为工作选择正确的工具。

【讨论】:

  • 好的,谢谢!这说明了很多!这本书确实在谈论 linked 列表(单独和双重)。另外,我现在更清楚地看到了研究这个主题的意义(你在上一段中写的)。我也终于明白了从链表开始(构建结构,例如队列,这将在本书后面完成)。
  • 澄清一下,您也可以使用数组创建队列(例如,查看ArrayDeque 源),因此两种方式都值得实现。正如您所提到的,有时文本会说“列表”,您必须根据上下文推断它们是在谈论链表还是动态数组。对于基础 DS&A 教科书,列表可能是指您提到的链表。
【解决方案2】:

Python 列表不是链表。它们是动态数组 (https://en.wikipedia.org/wiki/Dynamic_array#Language_support)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 2018-10-04
    相关资源
    最近更新 更多