Data Structures and Algorithms using Python
Copyright © 2023 by Rance Necaise
Table Of Contents
Data Structures and Algorithms using Python
Copyright © 2023
by Rance Necaise

9.2 Implementing the Stack

The Stack ADT can be implemented in several ways. The two most common approaches in Python include the use of a Python list and a linked list. The choice depends on the type of application involved.

Using a Python List

The Python list-based implementation of the Stack ADT is the easiest to implement. The first decision we have to make when using the list for the Stack ADT is which end of the list to use as the top and which as the base. For the most efficient ordering, we let the end of the list represent the top of the stack and the front represent the base. As the stack grows, items are appended to the end of the list and when items are popped, they are removed from the same end. The complete implementation of the Stack ADT using a Python list is provided in \xref{lst:stack:pylist}.

Program Listing
Program: pyliststack.py
  1. # Implementation of the Stack ADT using a Python list.
  2. class Stack :
  3.    # Creates an empty stack.
  4.   def __init__(self) :
  5.     self._theItems = list()
  6.  
  7.    # Returns True if the stack is empty or False otherwise.
  8.   def isEmpty(self):
  9.     return len(self) == 0
  10.  
  11.    # Returns the number of items in the stack.
  12.   def __len__ (self) :
  13.     return len(self._theItems)
  14.    
  15.    # Returns the top item on the stack without removing it.
  16.   def peek(self) :
  17.     assert not self.isEmpty(), "Cannot peek at an empty stack"
  18.     return self._theItems[-1]
  19.        
  20.    # Removes and returns the top item on the stack.
  21.   def pop(self) :
  22.     assert not self.isEmpty(), "Cannot pop from an empty stack"
  23.     return self._theItems.pop()
  24.  
  25.    # Push an item onto the top of the stack.
  26.   def push(self, item) :
  27.     self._theItems.append(item)

The peek and pop operations can only be used with a non-empty stack since you cannot remove or peek at something that is not there. To enforce this requirement, we first assert the stack is not empty before performing the given operation. The peek method simply returns a reference to the last item in the list. To implement the pop method, we call the pop method of the list structure, which actually performs the same operation that we are trying to implement. That is, it saves a copy of the last item in the list, removes the item from the list, and then returns the saved copy. The push method simply appends new items to the end of the list since that represents the top of our stack.

The individual stack operations are easy to evaluate for the Python list-based implementation (see Table 9.2.1). isEmpty, __length__, and peek only require O(1) time. The pop and push methods both require O(n) time in the worst case since the underlying array used to implement the Python list may have to be reallocated to accommodate the addition or removal of the top stack item. When used in sequence, both operations have an amortized cost of O(1).

OperationWorst Case
s = Stack() O(1)
len(s) O(1)
isEmpty(s) O(1)
s.push(x) O(n)
x = s.pop() O(n)
x = s.peek() O(1)
Table 9.2.1: Worst case time-complexities for the Stack ADT implemented using a Python list.

Using a Linked List

The Python list-based implementation may not be the best choice for stacks with a large number of push and pop operations. Remember, each append and pop list operation may require a reallocation of the underlying array used to implement the list. A singly linked list can be used to implement the Stack ADT, alleviating the concern over array reallocations.

To use a linked list, we again must decide how to represent the stack structure. With the Python list implementation of the stack, it was most efficient to use the end of the list as the top of the stack. With a linked list, however, the front of the list provides the most efficient representation for the top of the stack. In Chapter 7, we saw how to easily prepend nodes to the linked list as well as remove the first node. The Stack ADT implemented using a linked list is provided in the listing below.

Program Listing
Program: lliststack.py
  1. # Implementation of the Stack ADT using a singly linked list.
  2. class Stack :
  3.    # Creates an empty stack.
  4.   def __init__(self) :
  5.     self._top = None
  6.     self._size = 0
  7.      
  8.    # Returns True if the stack is empty or False otherwise.
  9.   def isEmpty(self) :
  10.     return self._top is None
  11.      
  12.    # Returns the number of items in the stack.
  13.   def __len__(self) :
  14.     return self._size
  15.      
  16.    # Returns the top item on the stack without removing it.
  17.   def peek(self) :
  18.     assert not self.isEmpty(), "Cannot peek at an empty stack"
  19.     return self._top.item
  20.      
  21.    # Removes and returns the top item on the stack.
  22.   def pop(self) :
  23.     assert not self.isEmpty(), "Cannot pop from an empty stack"
  24.     node = self._top
  25.     self.top = self._top.next
  26.     self._size -= 1
  27.     return node.item
  28.      
  29.    # Pushes an item onto the top of the stack.
  30.   def push(self, item) :
  31.     self._top = StackNode(item, self._top)
  32.     self._size += 1
  33.    
  34. # The private storage class for creating stack nodes.    
  35. class StackNode :
  36.   def __init__(self, item, link) :
  37.     self.item = item
  38.     self.next = link

The class constructor creates two instance variables for each Stack. The _top field is the head reference for maintaining the linked list while _size is an integer value for keeping track of the number of items on the stack. The latter has to be adjusted when items are pushed onto or popped off the stack. Figure 9.2.1 illustrates a sample Stack object for the stack from Figure 9.1.1.

Figure 9.2.1: Sample object of the Stack ADT implemented as a linked list.

The StackNode class is used to create the linked list nodes. Note the inclusion of the link argument in the constructor, which is used to initialize the _next field of the new node. By including this argument, we can simplify the prepend operation of the push method. The two steps required to prepend a node to a linked list are combined by passing the head reference top as the second argument of the StackNode constructor and assigning a reference to the new node back to _top.

The peek method simply returns a reference to the data item in the first node after verifying the stack is not empty. If the method were used on the stack represented by the linked list in Figure 9.2.1, a reference to 19 would be returned. The peek operation is only meant to examine the item on top of the stack. It should not be used to modify the top item as this would violate the definition of the Stack ADT.

The pop method always removes the first node in the list. This operation is illustrated in Figure 9.2.2 (a). This is easy to implement and does not require a search to find the node containing a specific item. The result of the linked list after popping the top item from the stack is illustrated in Figure 9.2.2 (b).

Figure 9.2.2: Popping an item from the stack: (a) the required link modifications, and

(b) the result after popping the top item.

The linked list implementation of the Stack ADT is more efficient than the Python-list based implementation. All of the operations are O(1) in the worst case, the proof of which is left as an exercise.

Page last modified on August 28, 2023, at 07:51 PM