前面我们已经实现了Python 采用列表实现栈(Stack),那时我们将列表的末尾看作是栈的顶,列表方法append就是将元素压入栈中,而列表方法pop会删除并返回栈顶的元素,这种选择的主要缺点是,所有其它的列表操作也都可以操作这个栈,这包括了在任意位置插入、替换和删除元素。这些额外的操作违反了栈作为一种抽象数据类型的本意。
栈方法 | 作用 |
---|---|
s.isEmpty() | 如果是空返回True,否则返回False |
__len__(s) | 等同于len(s),返回s中的项的数目 |
__str__(s) | 等同于str(s),返回s中的字符串表示 |
s.__iter__() | 等同于iter(s)或for item in s,从顶部向顶部,访问s中的每一项 |
s.__contains__(item) | 等同于item in s,如果item在s中,返回True |
s1__add__(s2) | s1 + s2,返回一个新的栈,其中包含了s1和s2的项 |
s.__eq__(anyObject) | 等同于s == anyObject |
s.clear() | 将s清空 |
s.peek() | 返回s顶部的项。先验条件:s必须不为空 |
s.push(item) | 在s的顶部添加一项 |
s.pop() | 在s的顶部删除一项并返回该项。先验条件:s必须不为空 |
这个接口的优点在于用户将知道使用哪一个方法,以及这些方法接收什么参数,而不必管选择哪一种栈实现!相同接口的不同实现很可能有不同的性能权衡。
class AbstractCollection(object):
"""An abstract collection implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
self._size = 0
if sourceCollection:
for item in sourceCollection:
self.add(item)
# Accessor methods
def isEmpty(self):
"""Returns True if len(self) == 0, or False otherwise."""
return len(self) == 0
def __len__(self):
"""Returns the number of items in self."""
return self._size
def __str__(self):
"""Returns the string representation of self."""
return "[" + ", ".join(map(str, self)) + "]"
def __add__(self, other):
"""Returns a new bag containing the contents
of self and other."""
result = type(self)(self)
for item in other:
result.add(item)
return result
def __eq__(self, other):
"""Returns True if self equals other, or False otherwise."""
if self is other: return True
if type(self) != type(other) or \
len(self) != len(other):
return False
otherIter = iter(other)
for item in self:
if item != next(otherIter):
return False
return True
from abstractcollection import AbstractCollection
class AbstractStack(AbstractCollection):
"""An abstract stack implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
AbstractCollection.__init__(self, sourceCollection)
# Mutator methods
def add(self, item):
"""Adds item to self."""
self.push(item)
这里将数组self._items的默认容量(物理大小)设置为10,并且self._size等于0。如果栈顶有项,它总是位于self._size-1的位置。要将一个项压入栈,需要将它存储到self._items[len(self)]的位置并且将self._size增加1。要弹出栈,就需要返回self._items[len(self)-1],并且将self._size减1。
注意:这里可能会有一个数组溢出的问题,我们需要当数组将要溢出的时候创建一个新的数组。按照之前的分析,当push填充数组的时候,我们将数组的容量加倍,当pop使得数组的四分之三为空的时候,我们将其容量减半。
from arrays import Array
from abstractstack import AbstractStack
class ArrayStack(AbstractStack):
"""An array-based stack implementation."""
# Class variable
DEFAULT_CAPACITY = 10
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._items = Array(ArrayStack.DEFAULT_CAPACITY)
AbstractStack.__init__(self, sourceCollection)
# Accessor methods
def __iter__(self):
"""Supports iteration over a view of self.
Visits items from bottom to top of stack."""
cursor = 0
while cursor < len(self):
yield self._items[cursor]
cursor += 1
def peek(self):
"""Returns the item at the top of the stack.
Precondition: the stack is not empty.
Raises: KeyError if stack is empty."""
if self.isEmpty():
raise KeyError("The stack is empty")
return self._items[len(self) - 1]
# Mutator methods
def clear(self):
"""Makes self become empty."""
self._size = 0
self._items = Array(ArrayStack.DEFAULT_CAPACITY)
def push(self, item):
"""Inserts item at top of the stack."""
# Resize array here if necessary
self._items[len(self)] = item
self._size += 1
def pop(self):
"""Removes and returns the item at the top of the stack.
Precondition: the stack is not empty.
Raises: KeyError if stack is empty.
Postcondition: the top item is removed from the stack."""
if self.isEmpty():
raise KeyError("The stack is empty")
oldItem = self._items[len(self) - 1]
self._size -= 1
# Resize the array here if necessary
return oldItem
这里还需要注意一下peek、pop方法的先验条件,一个安全的实现是,当违反这些先验条件的时候,强制引发异常。
由于新添加的项和删除的项是链表结构的一段,所以pop和push方法很容易实现,但是__iter__方法的实现还是很复杂的,因为必须从链表结构的尾部到头部来访问各项。这时候我们可以采用递归,在初次调用这个函数的时候,参数节点是栈的链表结构的头(变量self._items)。如果这个节点不为None,我们使用该节点的next字段递归的调用该函数,从而一直向前直到到达结构的尾部。
from node import Node
from abstractstack import AbstractStack
class LinkedStack(AbstractStack):
"""A link-based stack implementation."""
# Constructor
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present."""
self._items = None
AbstractStack.__init__(self, sourceCollection)
# Accessor methods
def __iter__(self):
"""Supports iteration over a view of self.
Visits items from bottom to top of stack."""
def visitNodes(node):
"""Adds items to tempList from tail to head."""
if not node is None:
visitNodes(node.next)
tempList.append(node.data)
tempList = list()
visitNodes(self._items)
return iter(tempList)
def peek(self):
"""
Returns the item at the top of the stack.
Precondition: the stack is not empty.
Raises: KeyError if the stack is empty."""
if self.isEmpty():
raise KeyError("The stack is empty.")
return self._items.data
# Mutator methods
def clear(self):
"""Makes self become empty."""
self._size = 0
self._items = None
def push(self, item):
"""Adds item to the top of the stack."""
self._items = Node(item, self._items)
self._size += 1
def pop(self):
"""
Removes and returns the item at the top of the stack.
Precondition: the stack is not empty.
Raises: KeyError if the stack is empty.
Postcondition: the top item is removed from the stack."""
if self.isEmpty():
raise KeyError("The stack is empty.")
data = self._items.data
self._items = self._items.next
self._size -= 1
return data
除了__iter__方法外,所有的栈方法都很简单,并且运行时间不大于O(1)。在数组的实现中,当数组容量翻倍的时候,push方法的运行时间也增加到了O(n),但是其它时候,它的运行时间保持在O(1),pop方法也类似。
n个对象的一个集合,需要足够的空间来保存n个对象的引用。n项的一个链表栈,需要n个节点,每个节点包含两个引用,一个引用项,另一个引用用于next节点,此外还有一个变量指向栈顶节点,还有一个变量用于大小,最终总的空间需求是2n+2.
对于一个数组的实现,当栈实例化的时候,其总的空间需求是固定的。这个空间包含了一个给定容量的数组,还有记录栈的大小和引用栈自身的变量,那么,总的空间需求就是数组的容量+2。那么,当数组的装载因子大于二分之一的时候,数组实现比链表实现具有更高的空间效率。
本文是数据结构(用Python实现)这本书的读书笔记!