Python 采用数组、链表实现栈(Stack)

前面我们已经实现了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必须不为空

这个接口的优点在于用户将知道使用哪一个方法,以及这些方法接收什么参数,而不必管选择哪一种栈实现!相同接口的不同实现很可能有不同的性能权衡。


栈的数组实现

AbstractCollection类
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
AbstractStack类
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)
ArrayStack类

这里将数组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实现)这本书的读书笔记!

猜你喜欢

转载自blog.csdn.net/dta0502/article/details/80790721