Stack Data Structure
Stack Data Structure
Stack is a linear data structure which follows a particular order in which the operations are
performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out).
There are many real-life examples of a stack. Consider an example of plates stacked over one
another in the canteen. The plate which is at the top is the first one to be removed, i.e. the plate
which has been placed at the bottommost position remains in the stack for the longest period of
time. So, it can be simply seen to follow LIFO(Last In First Out)/FILO(First In Last Out) order.
Mainly the following three basic operations are performed in the stack:
Push: Adds an item in the stack. If the stack is full, then it is said to be an
Overflow condition.
Pop: Removes an item from the stack. The items are popped in the
reversed order in which they are pushed. If the stack is empty, then it is said to
be an Underflow condition.
Peek or Top: Returns top element of stack.
isEmpty: Returns true if stack is empty, else false.
1. Increment the variable Top so that it can now refere to the next memory location.
2. Add element at the position of incremented top. This is referred to as adding new
element at the top of the stack.
Stack is overflown when we try to insert an element into a completely filled stack
therefore, our main function must always avoid stack overflow condition.
Algorithm:
1. begin
2. [Stack already filled or Overflow condition checking]
if (top == n):
then print OVERFLOW and return;
3. [Increment Top]
top = top + 1
4. [Data Storing]
stack (top) = item;
5. end
The underflow condition occurs when we try to delete an element from an already empty
stack.
Algorithm :
1. begin
2. [UNDERFLOW condition checking]
if top == 0:
then print UNDERFLOW and return;
3. [Assign TOP item to the stack]
item = stack(top);
4. [decrease TOP by 1]
top = top - 1;
5. end;
Time Complexity : o(1)
Visiting each element of the stack (Peek operation)
Peek operation involves returning the element which is present at the top of the stack
without deleting it. Underflow condition can occur if we try to return the top element in
an already empty stack.
Algorithm :
1. Begin
2. if top = -1 then stack empty
3. item = stack[top]
4. return item
5. End
Implementation
There are various ways from which a stack can be implemented in Python. This article covers
the implementation of stack using data structures and modules from Python library.
Stack in Python can be implemented using following ways:
list
collections.deque
queue.LifoQueue
Python’s buil-in data structure list can be used as a stack. Instead of push(), append() is used to
add elements to the top of stack while pop() removes the element in LIFO order.
Unfortunately, list has a few shortcomings. The biggest issue is that it can run into speed issue
as it grows. The items in list are stored next to each other in memory, if the stack grows bigger
than the block of memory that currently hold it, then Python needs to do some memory
allocations. This can lead to some append() calls taking much longer than other ones.
# Python program to
# demonstrate stack implementation
# using list
stack = []
# append() function to push
# element in the stack
stack.append('a')
stack.append('b')
stack.append('c')
print('Initial stack')
print(stack)
# pop() fucntion to pop
# element from stack in
# LIFO order
print('\nElements poped from stack:')
print(stack.pop())
print(stack.pop())
print(stack.pop())
print('\nStack after elements are poped:')
print(stack)
# uncommenting print(stack.pop())
# will cause an IndexError
# as the stack is now empty
Output:
Initial stack
['a', 'b', 'c']
Python stack can be implemented using deque class from collections module. Deque is
preferred over list in the cases where we need quicker append and pop operations from both the
ends of the container, as deque provides an O(1) time complexity for append and pop
operations as compared to list which provides O(n) time complexity.
The same methods on deque as seen in list are used, append() and pop().
# Python program to
# demonstrate stack implementation
# using collections.deque
from collections import deque
stack = deque()
# append() function to push
stack.append('a')
stack.append('b')
stack.append('c')
print('Initial stack:')
print(stack)
# pop() fucntion to pop
# LIFO order
print(stack.pop())
print(stack.pop())
print(stack.pop())
print('\nStack after elements are poped:')
print(stack)
# uncommenting print(stack.pop())
Output:
Initial stack:
deque(['a', 'b', 'c'])
Queue module also has a LIFO Queue, which is basically a Stack. Data is inserted into Queue
using put() function and get() takes data out from the Queue.
There are various functions available in this module:
maxsize – Number of items allowed in the queue.
empty() – Return True if the queue is empty, False otherwise.
full() – Return True if there are maxsize items in the queue. If the queue was initialized
with maxsize=0 (the default), then full() never returns True.
get() – Remove and return an item from the queue. If queue is empty, wait until an item
is available.
get_nowait() – Return an item if one is immediately available, else raise QueueEmpty.
put(item) – Put an item into the queue. If the queue is full, wait until a free slot is
available before adding the item.
put_nowait(item) – Put an item into the queue without blocking.
qsize() – Return the number of items in the queue. If no free slot is immediately
available, raise QueueFull.
# Python program to
from queue import LifoQueue
# Initializing a stack
stack = LifoQueue(maxsize = 3)
# qsize() show the number of elements
# in the stack
print(stack.qsize())
stack.put('a')
stack.put('b')
stack.put('c')
print("Full: ", stack.full())
# get() fucntion to pop
# LIFO order
print(stack.get())
print(stack.get())
print(stack.get())
print("\nEmpty: ", stack.empty())
Output:
0
Full: True
Size: 3
The linked list has two methods addHead(item) and removeHead() that run in constant time.
These two methods are suitable to implement a stack.
getSize()– Get the number of items in the stack.
isEmpty() – Return True if the stack is empty, False otherwise.
peek() – Return the top item in the stack. If the stack is empty, raise an exception.
push(value) – Push a value into the head of the stack.
pop() – Remove and return a value in the head of the stack. If the stack is empty, raise
an exception.
# node class
class Node:
self.value = value
self.next = None
class Stack:
def __init__(self):
self.head = Node("head")
self.size = 0
# String representation of the stack
def __str__(self):
cur = self.head.next
out = ""
while cur:
cur = cur.next
return out[:-3]
# Get the current size of the stack
def getSize(self):
return self.size
def isEmpty(self):
return self.size == 0
def peek(self):
if self.isEmpty():
return self.head.next.value
# Push a value into the stack.
node = Node(value)
node.next = self.head.next
self.head.next = node
self.size += 1
def pop(self):
if self.isEmpty():
remove = self.head.next
self.head.next = self.head.next.next
self.size -= 1
return remove.value
# Driver Code
if __name__ == "__main__":
stack = Stack()
stack.push(i)
print(f"Stack: {stack}")
for _ in range(1, 6):
remove = stack.pop()
print(f"Pop: {remove}")
print(f"Stack: {stack}")
Output:
Stack: 10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1
Pop: 10
Pop: 9
Pop: 8
Pop: 7
Pop: 6
A queue can be implemented using two stacks. Let queue to be implemented be q and stacks
used to implement q be stack1 and stack2. q can be implemented in two ways:
Method 1 (By making enQueue operation costly) This method makes sure that oldest
entered element is always at the top of stack 1, so that deQueue operation just pops from
stack1. To put the element at top of stack1, stack2 is used.
enQueue(q, x):
While stack1 is not empty, push everything from stack1 to stack2.
Push x to stack1 (assuming size of stacks is unlimited).
Push everything back to stack1.
Here time complexity will be O(n)
deQueue(q):
If stack1 is empty then error
Pop an item from stack1 and return it
Here time complexity will be O(1)
Output:
1
2
3
Complexity Analysis:
Time Complexity:
Push operation: O(N).
In the worst case we have empty whole of stack 1 into stack 2.
Pop operation: O(1).
Same as pop operation in stack.
Auxiliary Space: O(N).
Use of stack for storing values.
Method 2 (By making deQueue operation costly) In this method, in en-queue operation, the
new element is entered at the top of stack1. In de-queue operation, if stack2 is empty then all
the elements are moved to stack2 and finally top of stack2 is returned.
enQueue(q, x)
1) Push x to stack1 (assuming size of stacks is unlimited).
Here time complexity will be O(1)
deQueue(q)
1) If both stacks are empty then error.
2) If stack2 is empty
While stack1 is not empty, push everything from stack1 to
stack2.
3) Pop the element from stack2 and return it.
Here time complexity will be O(n)
# Python3 program to implement Queue using
# two stacks with costly enQueue()
class Queue:
def __init__(self):
self.s1 = []
self.s2 = []
def enQueue(self, x):
# Move all elements from s1 to s2
while len(self.s1) != 0:
self.s2.append(self.s1[-1])
self.s1.pop()
# Push item into self.s1
self.s1.append(x)
# Push everything back to s1
while len(self.s2) != 0:
self.s1.append(self.s2[-1])
self.s2.pop()
# Dequeue an item from the queue
def deQueue(self):
# if first stack is empty
if len(self.s1) == 0:
print("Q is Empty")
# Return top of self.s1
x = self.s1[-1]
self.s1.pop()
return x
# Driver code
if __name__ == '__main__':
q = Queue()
q.enQueue(1)
q.enQueue(2)
q.enQueue(3)
print(q.deQueue())
print(q.deQueue())
print(q.deQueue())
# This code is contributed by PranchalK
Output:
1
2
3
Complexity Analysis:
Time Complexity:
Push operation: O(N).
In the worst case we have empty whole of stack 1 into stack 2.
Pop operation: O(1).
Same as pop operation in stack.
Auxiliary Space: O(N).
Use of stack for storing values.
Queue can also be implemented using one user stack and one Function Call Stack. Below
is modified Method 2 where recursion (or Function Call Stack) is used to implement queue
using only one user defined stack.
enQueue(x)
1) Push x to stack1.
deQueue:
1) If stack1 is empty then error.
2) If stack1 has only one element then return it.
3) Recursively pop everything from the stack1, store the popped item
in a variable res, push the res back to stack1 and return res
The step 3 makes sure that the last popped item is always returned and since the recursion stops
when there is only one item in stack1 (step 2), we get the last element of stack1 in deQueue()
and all other items are pushed back in step
3. Implementation of method 2 using Function Call Stack:
Output:
1 2 3
Complexity Analysis:
Time Complexity:
Push operation : O(1).
Same as pop operation in stack.
Pop operation : O(N).
The difference from above method is that in this method element is
returned and all elements are restored back in a single call.
Auxiliary Space: O(N).
Use of stack for storing values.
Design and Implement Special Stack Data Structure |
Added Space Optimized Version
Question: Design a Data Structure SpecialStack that supports all the stack operations like
push(), pop(), isEmpty(), isFull() and an additional operation getMin() which should return
minimum element from the SpecialStack. All these operations of SpecialStack must be O(1).
To implement SpecialStack, you should only use standard Stack data structure and no other
data structure like arrays, list, .. etc.
Example:
Consider the following SpecialStack
16 --> TOP
15
29
19
18
Solution: Use two stacks: one to store actual stack elements and the other as an auxiliary stack
to store minimum values. The idea is to do push() and pop() operations in such a way that the
top of the auxiliary stack is always the minimum. Let us see how push() and pop() operations
work.
Push(int x) // inserts an element x to Special Stack
1) push x to the first stack (the stack with actual elements)
2) compare x with the top element of the second stack (the auxiliary stack). Let the top element
be y.
…..a) If x is smaller than y then push x to the auxiliary stack.
…..b) If x is greater than y then push y to the auxiliary stack.
int Pop() // removes an element from Special Stack and return the removed element
1) pop the top element from the auxiliary stack.
2) pop the top element from the actual stack and return it.
The step 1 is necessary to make sure that the auxiliary stack is also updated for future
operations.
int getMin() // returns the minimum element from Special Stack
1) Return the top element of the auxiliary stack.
We can see that all the above operations are O(1).
Let us see an example. Let us assume that both stacks are initially empty and 18, 19, 29, 15,
and 16 are inserted to the SpecialStack.
When we insert 18, both stacks change to following.
Actual Stack
18 <--- top
Auxiliary Stack
18 <---- top
A stack can be implemented using two queues. Let stack to be implemented be ‘s’ and queues
used to implement be ‘q1’ and ‘q2’. Stack ‘s’ can be implemented in two ways:
Method 1 (By making push operation costly)
This method makes sure that newly entered element is always at the front of ‘q1’, so that pop
operation just dequeues from ‘q1’. ‘q2’ is used to put every new element at front of ‘q1’.
1. push(s, x) operation’s step are described below:
Enqueue x to q2
One by one dequeue everything from q1 and enqueue to q2.
Swap the names of q1 and q2
2. pop(s) operation’s function are described below:
Dequeue an item from q1 and return it.
Below is the implementation of the above approach:
# Program to implement a stack using
# two queue
from queue import Queue
class Stack:
def __init__(self):
# Two inbuilt queues
self.q1 = Queue()
self.q2 = Queue()
# To maintain current number
# of elements
self.curr_size = 0
def push(self, x):
self.curr_size += 1
# Push x first in empty q2
self.q2.put(x)
# Push all the remaining
# elements in q1 to q2.
while (not self.q1.empty()):
self.q2.put(self.q1.queue[0])
self.q1.get()
# swap the names of two queues
self.q = self.q1
self.q1 = self.q2
self.q2 = self.q
def pop(self):
# if no elements are there in q1
if (self.q1.empty()):
return
self.q1.get()
self.curr_size -= 1
def top(self):
if (self.q1.empty()):
return -1
return self.q1.queue[0]
def size(self):
return self.curr_size
# Driver Code
if __name__ == '__main__':
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print("current size: ", s.size())
print(s.top())
s.pop()
print(s.top())
s.pop()
print(s.top())
print("current size: ", s.size())
# This code is contributed by PranchalK
Output :
current size: 3
3
2
1
current size: 1
Method 2 (By making pop operation costly)
In push operation, the new element is always enqueued to q1. In pop() operation, if q2 is empty
then all the elements except the last, are moved to q2. Finally the last element is dequeued from
q1 and returned.
1. push(s, x) operation:
Enqueue x to q1 (assuming size of q1 is unlimited).
2. pop(s) operation:
One by one dequeue everything except the last element from q1
and enqueue to q2.
Dequeue the last item of q1, the dequeued item is result, store it.
Swap the names of q1 and q2
Return the item stored in step 2.