简要介绍
列表是三种主要的线性集合中的一种(另两种是栈和队列)。列表所支持的操作,比栈和队列还要广泛,因此其应用也更广,并且也更难实现。尽管Python包含了一个内建的列表操作,但是还有几种可能的实现。为了让列表的大量操作更有意义,我们可以将其分为3类:基于索引的操作、基于内容的操作、基于位置的操作。
注意:尽管列表中的项在逻辑上总是连续的,但是它们在物理内存中不一定是连续的。列表的数组实现了使用物理位置来表示逻辑顺序,但是链表的实现并没有那么做。
列表的操作分类
基于索引的操作
基于索引的操作会操作列表中的指定索引的项,在基于数组实现的例子中,这些操作也提供了方便的随机访问。
| 列表方法 | 作用 |
|---|---|
| L.insert(i,item) | 在将各项向右移动一个位置之后,在索引i添加项item |
| L.pop(i = None) | 删除并返回位于索引i的项,如果没有参数i,删除并返回最后一项;先验条件:0 <= i <= len(L) |
| L.[i] | 返回位于索引i的项,先验条件:0 <= i <= len(L) |
| L.[i] = item | 用item替代位于索引i的项,先验条件:0 <= i <= len(L) |
从这个视角来查看的时候,列表有时候叫做向量或序列,并且由于它们使用索引,会令人想起数组。然而,数组是一种具体的数据结构,拥有基于单个的物理内存块的一种特定的、不变的实现,而列表是一种数据类型,可以以各种方式表示,其中只有一种方式使用了数组。此外,列表所拥有的基本操作的范围,要比数组的大得多,即使合适的数组序列可以模拟所有的列表操作。
基于内容的操作
| 列表方法 | 作用 |
|---|---|
| L.add(item) | 在列表的尾部添加一项 |
| L.remove(item) | 从列表中删除一项。先验条件:该项位于列表之中 |
| L.index(item) | 返回列表中的第一个实例的位置。先验条件:该项位于列表之中。 |
基于位置的操作
基于位置的操作是相对于当前建立的名为游标的位置而执行的。这个操作允许程序员通过移动游标在列表中导航。尽管在Python中列表已经支持迭代器了,它允许程序员使用for循环来访问列表的项,但列表迭代器要更为强大一些。与简单的迭代器不同,列表迭代器支持移动到之前的位置,直接移动到第一个位置以及直接移动到最后一个位置。除了这些导航操作以后,列表迭代器还支持从游标位置插入、替代和删除项。
这里,我们可以通过在列表上运行listIterator方法(列表类ArrayList中含有一个listIterator方法,这个方法调用了列表迭代器类ArrayListIterator)来创建列表迭代器,如下所示:
listIterator = aList.listIterator()一开始,在列表迭代器第一次实例化的时候,其游标是未定义的。在将项插入到列表中之后,用户可以通过将游标移动到列表的开始或末尾,从而建立游标的位置。从这些位置开始,用户可以以某种方式导航到另一个位置。
| 操作 | 目标 |
|---|---|
| LI.hasNext() | 如果在游标之后有项,返回True;如果游标未定义或位于最后一项之后,返回False |
| LI.next() | 返回下一项并且将游标向右移动一个位置。先验条件:hasNext返回True,在最近一次next或previous操作之后,列表上没有干扰性修改 |
| LI.hasPrevious() | 如果在游标之前有项,返回True;如果游标未定义或位于第一项之前,返回False |
| LI.previous() | 返回之前的一项,并且将游标向右移动一个位置。先验条件:hasPrevious返回True,在最近一次next或previous操作之后,列表上没有干扰性修改 |
| LI.first() | 将游标移动到第一项之前(如果有第一项的话) |
| LI.last() | 将游标移动到最后一项之后(如果有最后一项的话) |
| 操作 | 作用 |
|---|---|
| LI.insert(item) | 如果定义了游标的话,在其后插入项;否则,在列表的尾部插入项 |
| LI.remove() | 删除最近的next或previous操作所返回的项。先验条件:在最近一次next或previous之后,列表上没有干扰性修改 |
| LI.replace(item) | 替代最近一次next或previous操作调用所返回的项。先验条件:在最近一次next或previous之后,列表上没有干扰性修改 |
下表列出了在列表迭代器上的一系列操作:
| 操作 | 操作之后的位置 | 操作后的列表 | 返回值 | 说明 |
|---|---|---|---|---|
| 实例化 | 未定义 | 空 | 一个新的列表迭代器 | |
| insert(a) | 未定义 | a | 当游标未定义的时候,插入的每一项都位于列表的末尾 | |
| insert(b) | 未定义 | ab | ||
| hasNext() | 未定义 | False | 当游标未定义时,没有下一项或者前一项 | |
| first() | 0 | ,ab | 将游标放在第一项之前(如果有第一项的话) | |
| hasNext() | 0 | True | 在游标的右边有一个项 | |
| next() | 1 | a,b | a | 返回a并且将游标向右移动 |
| replace(c) | 1 | c,b | 用c替换a,后者是next最近返回的项 | |
| next() | 2 | c b, | b | 返回b并且游标向右移动 |
| next() | 2 | c b, | 异常 | 游标位于列表的末尾,因此,无法移动到下一项 |
| hasNext() | 2 | c b, | False | 游标位于列表末尾 |
| hasPrevious() | 2 | c b, | True | 游标左边有一个项 |
| previous() | 1 | c,b | b | 返回b并且将游标向左移动 |
| insert(e) | 1 | c,eb | 在游标位置的右边插入e | |
| remove() | 1 | c,eb | 异常 | 自最近的next或previous之后,发生了一次insert |
| previous() | 0 | ,c eb | c | 返回c并且将游标向左移动 |
| remove() | 0 | , eb | 删除c,这是最近的previous返回的项 |
注意:
(1)除非列表中至少有一项,并且已经运行过first或last方法,否则是没有当前位置的。
(2)我们知道remove和replace在一次成功的next或previous操作所返回的最后一项运行,前提是这里没有干扰性的insert或remove操作。
下面代码也展示了列表迭代器的用法。可以假设已经定义了ArrayList类,而该类支持前面介绍的操作。
from arraylist import ArrayList
def test(listType):
"""Expects a list type as an argument and runs some tests
on objects of that type."""
print("Create a list with 1-9")
lyst = listType(range(1, 10))
print("Length:", len(lyst))
print("Items (first to last):", lyst)
# Create and use a list iterator
listIterator = lyst.listIterator()
print("Forward traversal: ", end="")
listIterator.first()
while listIterator.hasNext():
print(listIterator.next(), end = " ")
print("\nBackward traversal: ", end="")
listIterator.last()
while listIterator.hasPrevious():
print(listIterator.previous(), end=" ")
print("\nInserting 10 before 3: ", end="")
listIterator.first()
for count in range(3):
listIterator.next()
listIterator.insert(10)
print(lyst)
print("Removing 2: ", end="")
listIterator.first()
for count in range(2):
listIterator.next()
listIterator.remove()
print(lyst)
print("Removing all items")
listIterator.first()
while listIterator.hasNext():
listIterator.next()
listIterator.remove()
print("Length:", len(lyst))
test(ArrayList)代码输出:
Create a list with 1-9
Length: 9
Items (first to last): [1, 2, 3, 4, 5, 6, 7, 8, 9]
Forward traversal: 1 2 3 4 5 6 7 8 9
Backward traversal: 9 8 7 6 5 4 3 2 1
Inserting 10 before 3: [1, 2, 10, 3, 4, 5, 6, 7, 8, 9]
Removing 2: [1, 10, 3, 4, 5, 6, 7, 8, 9]
Removing all items
Length: 0列表的接口
根据前面我们对列表操作的介绍,假设你要将这些操作划分到两个接口中。第一个接口包括了基于索引的操作和基于内容的操作,它们类似于Python的列表类中的那些操作。第二个接口包含了用于列表迭代器的操作。

列表的实现
AbstractList类
每个具体的列表类都是在一个名为AbstractList的抽象类之下进行子类化的,而不是从头开始。由于这个类是AbstractCollection的一个子类,列表类也继承了常用的集合方法以及self._size变量。上文列表的接口处的框图可以看出这点。
基于内容的方法index是针对给定的项的一次搜索。由于该搜索可以使用简单的for循环,你可以在AbstractList中定义该方法,此外,基于内容的方法remove和add,分别可以调用基于索引的方法pop和insert,以便在调用index找到位置之后,删除或添加一项。因此,也可以在AbstractList类中定义这两个基于内容的方法。
from abstractcollection import AbstractCollection
class AbstractList(AbstractCollection):
"""Represents an abstract list."""
def __init__(self, sourceCollection):
"""Maintains a count of modifications to the list."""
self._modCount = 0
AbstractCollection.__init__(self, sourceCollection)
def getModCount(self):
"""Returns the count of modifications to the list."""
return self._modCount
def incModCount(self):
"""Increments the count of modifications to the list."""
self._modCount += 1
def index(self, item):
"""Precondition: item is in the list.
Returns the position of item.
Raises: ValueError if the item is not in the list."""
position = 0
for data in self:
if data == item:
return position
else:
position += 1
if position == len(self):
raise ValueError(str(item) + " not in list.")
def remove(self, item):
"""Precondition: item is in self.
Raises: ValueError if item in not in self.
Postcondition: item is removed from self."""
position = self.index(item)
self.pop(position)
def add(self, item):
"""Adds the item to the end of the list."""
self.insert(len(self), item)
def append(self, item):
"""Adds the item to the end of the list."""
self.add(item)基于数组的实现
列表接口的基于数组的实现是一个名为ArrayList的类。ArrayList将其数据项保存在Array类的一个实例中。
from arrays import Array
from abstractlist import AbstractList
from arraylistiterator import ArrayListIterator
class ArrayList(AbstractList):
"""An array-based list implementation."""
DEFAULT_CAPACITY = 10
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
self._items = Array(ArrayList.DEFAULT_CAPACITY)
AbstractList.__init__(self, sourceCollection)
def _resize(self, array, logicalSize):
"""If the array needs resizing, resizes and returns the new array. Otherwise, returns the olf array."""
temp = None
# If array is full
if len(array) == logicalSize:
temp = Array(2 * len(array))
# If array is wasting space
elif logicalSize <= .25 * len(array) and \
len(array) >= ArrayList.DEFAULT_CAPACITY:
temp = Array(round(.5 * len(array)))
# None of the above
else:
return array
# Copy items to new array
for i in range(logicalSize):
temp[i] = array[i]
return temp
# Accessor methods
def __iter__(self):
"""Supports iteration over a view of self."""
cursor = 0
while cursor < len(self):
yield self._items[cursor]
cursor += 1
def __getitem__(self, i):
"""Precondition: 0 <= i < len(self)
Returns the item at position i.
Raises: IndexError."""
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
return self._items[i]
# Mutator methods
def clear(self):
"""Makes self become empty."""
self._size = 0
self._items = Array(ArrayList.DEFAULT_CAPACITY)
def __setitem__(self, i, item):
"""Precondition: 0 <= i < len(self)
Replaces the item at position i.
Raises: IndexError."""
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
self._items[i] = item
def insert(self, i, item):
"""Inserts the item at position i."""
self._items = self._resize(self._items, len(self))
if i < 0: i = 0
elif i > len(self): i = len(self)
if i < len(self):
for j in range(len(self), i, -1):
self._items[j] = self._items[j - 1]
self._items[i] = item
self._size += 1
self.incModCount()
def pop(self, i = None):
"""Precondition: 0 <= i < len(self).
Removes and returns the item at position i.
If i is None, i is given a default of len(self) - 1.
Raises: IndexError."""
if i == None: i = len(self) - 1
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
item = self._items[i]
for j in range(i, len(self) - 1):
self._items[j] = self._items[j + 1]
self._size -= 1
self.incModCount()
self._items = self._resize(self._items, len(self))
return item
def listIterator(self):
"""Returns a list iterator."""
return ArrayListIterator(self)我们注意到,这个列表类里面包含了listIterator方法,这个方法返回了一个列表迭代器。
基于链表的实现
单链表结构对于列表迭代器的支持并不理想。列表迭代器允许游标在任何一个方向上移动,但是,单链表结构只支持朝着下一个节点移动,可以使用双链表结构来解决这个问题,其中,每个节点都有一个指针指向前一个节点,还有一个指针指向下一个节点。
如果在该结构的头部添加一个额外的节点的话,实现双链表结构所需的代码可以得到简化。这个节点成为哨兵节点(sentinel node),它向前指向第一个数据节点,向后指向最后一个节点。头指针指向哨兵节点。需要注意的是,哨兵节点并不包括列表项,并且当列表为空的时候,仍然保留哨兵节点。

一个双向链表结构的基本构建模块是带有两个指针的一个节点,next指针指向右边,而previous指针指向左边。节点的类型为TwoWayNode,这是Node类的一个子类(相比于Node类多了一个self.previous属性)。
from node import TwoWayNode
from abstractlist import AbstractList
class LinkedList(AbstractList):
"""A link-based list implementation."""
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
# Uses a circular linked structure with a dummy header node
self._head = TwoWayNode()
self._head.previous = self._head.next = self._head
AbstractList.__init__(self, sourceCollection)注意:__init__方法创建了一个没有数据的节点,这是一个哨兵节点。
然后就是开发基于索引的方法:__getitem__、__setitem__、insert和pop。这些方法都需要从头节点开始,直到到达第i个节点。由于搜索第i个节点是所有这4个方法都要执行的操作,我们构造了一个名为_getNode的辅助方法,由它来完成这一工作。这个方法接收目标节点的索引位置作为一个参数,并且返回指向第i个节点的一个指针。4个调用方法随后使用该指针来对链表结构进行相应的操作。
下面是辅助函数_getNode
# Helper method returns node at position i
def _getNode(self, i):
"""Helper method: returns a pointer to the node at position i."""
if i == len(self):
return self._head
elif i == len(self) - 1:
return self._head.previous
probe = self._head.next
while i > 0:
probe = probe.next
i -= 1
return probe下面4个方法
def __getitem__(self, i):
"""Precondition: 0 <= i < len(self)
Returns the item at position i.
Raises: IndexError."""
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
return self._getNode(i).data
def __setitem__(self, i, item):
"""Precondition: 0 <= i < len(self)
Replaces the item at position i.
Raises: IndexError."""
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
self._getNode(i).data = item
def insert(self, i, item):
"""Inserts the item at position i."""
if i < 0: i = 0
elif i > len(self): i = len(self)
theNode = self._getNode(i)
newNode = TwoWayNode(item, theNode.previous, theNode)
theNode.previous.next = newNode
theNode.previous = newNode
self._size += 1
self.incModCount()
def pop(self, i = None):
"""Precondition: 0 <= i < len(self).
Removes and returns the item at position i.
If i is None, i is given a default of len(self) - 1.
Raises: IndexError."""
if i == None: i = len(self) - 1
if i < 0 or i >= len(self):
raise IndexError("List index out of range")
theNode = self._getNode(i)
item = theNode.data
theNode.previous.next = theNode.next
theNode.next.previous = theNode.previous
self._size -= 1
self.incModCount()
return item两种实现的时间和空间分析
在访问方法和替换方法中,也就是__getitem__和__setitem__方法中,数组实现只需要直接以常数时间运行数组的下标操作就可以了,链表实现需要在链表结构中执行对第i个节点的线性搜索。
另外两个基于索引的方法,insert和pop,它们的运行时间在两种情况下都是线性的,数组实现中以常数时间找到目标项的位置,但是却需要线性时间移动项来完成该过程。相反,链表实现需要线性时间来找到目标项,但只需要常数时间就可以插入或删除一个节点。
两种实现的基于内容的方法index的时间复杂度都是O(n),而基于内容的方法remove运行了index方法,然后是pop方法,因此时间复杂度也为O(n)。
这里需要注意的是,add方法是直接添加到末尾的,所以是常数时间复杂度。
列表实现的空间分析,和之前栈、队列的类似。唯一不同之处在于这里的链表实现中,每个节点包含3个引用(next、previous、data),所以当数组实现的装载因子大于三分之一的时候,数组实现的内存使用比链表实现更为高效。
实现列表迭代器
列表迭代器是附加到列表上的一个对象,它提供在列表上的位置性的操作,这些操作前文就已经指出,它们允许程序员通过移动游标来查看和修改列表。
当我们在列表上运行listIteration方法的时候,这个方法会返回列表迭代器类的一个新的实例。列表迭代器对象依赖于相关联的列表,因为前者需要访问后者以定位项、替换项、插入项和删除项。因此,列表迭代器将保持对其列表或后备存储的一个引用,而这个列表是创建列表迭代器时所接收的。
列表迭代器除了支持其基本的操作,还必须遵守其先验条件,有3种类型的先验条件:
(1)如果hasNext或hasPrevious操作分别返回False的话,程序员不能运行next或previous操作
(2)程序员不能在列表迭代器上运行连续性的修改器方法。在每一次修改前,必须运行一个next或previous操作
(3)在列表上使用列表迭代器的时候,程序员不能使用列表的修改器方法在列表自身之上进行修改
为了帮助判断这些先验条件,列表迭代器有两个额外的变量。
第一个变量是自身的modCount变量,当创建列表迭代器的时候,这个变量设置为列表的modCount,因此列表和列表迭代器都有自己的modCount。无论何时,当运行列表自己的修改器的方法的时候,会递增列表的modCount以记录对列表的修改(前面的ArrayList代码中可以看出来)。每当列表迭代器上运行某个方法时(例如next和previous),列表迭代器会将自己的modCount和列表的modCount进行比较,如果两个值不同,说明已经有人在错误的环境中运行了列表的修改器方法,随后会引发异常。每当使用列表迭代器方法修改列表的时候,列表迭代器会增加自己的modCount,以便让两个modCount保持一致。
第二个变量记录了列表迭代器能够在列表上执行一次修改的位置。在基于数组的实现中,无论何时,当该位置还没有确立的时候,这个变量是-1,任何时候,当程序员在列表迭代器上成功地运行一个next或previous操作的时候,其值变为了列表中的一个索引。因此,列表迭代器中的修改器方法insert和remove能够通过检查这个变量以确保其先验条件,并且在它们成功修改了列表之后将其重置为-1。
设置和实例化一个列表迭代器类
数组列表的列表迭代器类名为ArrayListIterator。这个类包含了如下的实例变量:
self._backingStore——在其上打开迭代器的列表;
self._modCount——modCount的迭代器表示;
self._cursor——迭代器的导航方法first、last、hasNext、next、hasPrevious和previous所维护的游标位置;
self._lastItemPos——迭代器的修改器方法insert、remove和replace所使用的游标位置。这个位置通过运行next或previous而建立,并且在运行了insert或remove之后是未定义的。
ArrayList方法listIterator在其实例化的过程中,将后备存储(self)传递给列表迭代器。随后,列表迭代器可以在该对象上运行列表方法以操作它。
class ArrayListIterator(object):
"""Represents the list iterator for an array list."""
def __init__(self, backingStore):
self._backingStore = backingStore
self._modCount = backingStore.getModCount()
self.first()
def first(self):
"""Returns the cursor to the beginning of the backing store."""
self._cursor = 0
self._lastItemPos = -1列表迭代器中的导航方法
在向后移动游标并且从后备存储返回项之前,next方法必须检查两个先验条件。第一个是hasNext方法必须返回True。第二个是两个modCount(一个属于列表迭代器,另一个属于后备存储)必须相等。如果这两个先验条件都满足,方法next将self._lastItemPos设为self._cursor,将后者增加1,并且返回后备存储中位于self._lastItemPos的项。
def hasNext(self):
"""Returns True if the iterator has a next item or False otherwise."""
return self._cursor < len(self._backingStore)
def next(self):
"""Preconditions: hasNext returns True
The list has not been modified except by this iterator's mutators.
Returns the current item and advances the cursor to the next item.
Postcondition: lastItemPos is now defined.
Raises: ValueError if no next item.
AttributeError if illegal mutation of backing store."""
if not self.hasNext():
raise ValueError("No next item in list iterator")
if self._modCount != self._backingStore.getModCount():
raise AttributeError("Illegal modification of backing store")
self._lastItemPos = self._cursor
self._cursor += 1
return self._backingStore[self._lastItemPos]last、hasPrevious和previous方法使用游标从基于数组的列表的末尾向其开头移动。previous方法检查和next方法相同的两个先验条件,然后,它将游标减小1,将self._lastItemPos设置为self._cursor,并且返回后备存储中位于self._lastItemPos的项。
注意:这里游标先减1和之前不冲突,因为修改器方法要修改的是next、previous返回的项(self._lastItemPos存储的就是能够执行修改的位置)!!
def last(self):
"""Moves the cursor to the end of the backing store."""
self._cursor = len(self._backingStore)
self._lastItemPos = -1
def hasPrevious(self):
"""Returns True if the iterator has a previous item or False otherwise."""
return self._cursor > 0
def previous(self):
"""Preconditions: hasPrevious returns True
The list has not been modified except by this iterator's mutators.
Returns the current item and moves the cursor to the previous item.
Postcondition: lastItemPos is now defined.
Raises: ValueError if no next item.
AttributeError if illegal mutation of backing store."""
if not self.hasPrevious():
raise ValueError("No previous item in list iterator")
if self._modCount != self._backingStore.getModCount():
raise AttributeError("Illegal modification of backing store")
self._cursor -= 1
self._lastItemPos = self._cursor
return self._backingStore[self._lastItemPos]列表修改器中的修改器方法
修改器方法remove、replace必须检查两个先验条件。首先必须建立游标,这意味着变量self._lastItemPos必须不等于-1,其次,两个modCount必须相等。其中insert方法只用检查modCount这一个先验条件。
(1)replace方法的任务最简单。将后备存储中当前位置的项替换,并且将self._lastItemPos重新设置为-1,在替换操作过程中,列表迭代器的modCount不再递增。
(2)如果游标已经定义了,insert方法在当前位置向后备存储中插入该项,并且将self._lastItemPos重新设置为-1。否则,将该项添加到后备存储的末尾。在两种情况下,列表迭代器的modCount都将递增。
(3)remove方法从后备存储的当前位置弹出该项,并且将列表迭代器的modCount递增1。如果self._lastItemPos小于self._cursor,这意味着remove将会在一个next操作之后运行,因此,游标会减1。最后,将self._lastItemPos重新设置为-1。
def replace(self, item):
"""Preconditions: the current position is defined.
The list has not been modified except by this iterator's mutators.
Replaces the items at the current position with item.
Raises: AttibuteError if position is not defined.
AttributeError if illegal mutation of backing store."""
if self._lastItemPos == -1:
raise AttributeError("The current position is undefined.")
if self._modCount != self._backingStore.getModCount():
raise AttributeError("List has been modified illegally.")
self._backingStore[self._lastItemPos] = item
self._lastItemPos = -1
def insert(self, item):
"""Preconditions:
The list has not been modified except by this iterator's mutators.
Adds item to the end if the current position is undefined, or
inserts it at that position.
Raises: AttributeError if illegal mutation of backing store."""
if self._modCount != self._backingStore.getModCount():
raise AttributeError("List has been modified illegally.")
if self._lastItemPos == -1:
self._backingStore.add(item)
else:
self._backingStore.insert(self._lastItemPos, item)
self._lastItemPos = -1
self._modCount += 1
def remove(self):
"""Preconditions: the current position is defined.
The list has not been modified except by this iterator's mutators.
Pops the item at the current position.
Raises: AttibuteError if position is not defined.
AttributeError if illegal mutation of backing store."""
if self._lastItemPos == -1:
raise AttributeError("The current position is undefined.")
if self._modCount != self._backingStore.getModCount():
raise AttributeError("List has been modified illegally.")
item = self._backingStore.pop(self._lastItemPos)
# If the item removed was obtained via next, move cursor back
if self._lastItemPos < self._cursor:
self._cursor -= 1
self._modCount += 1
self._lastItemPos = -1本文是数据结构(用Python实现)这本书的读书笔记!
本文详细介绍了Python列表的分类和接口,包括基于索引、内容和位置的操作。讲解了列表的AbstractList类、基于数组和链表的实现,以及如何实现列表迭代器。列表迭代器支持更强大的导航和修改功能,提供了first、last、next、previous等方法。文章还分析了不同实现的时间和空间复杂度,并探讨了列表迭代器的先验条件和实例化过程。
5742

被折叠的 条评论
为什么被折叠?



