Day 11: Introduction to Stacks
Table of Contents
Introduction to Stacks #
Welcome to Day 11 of our 60 Days of Coding Algorithm Challenge! Today, we’ll explore stacks, a fundamental data structure in computer science that follows the Last-In-First-Out (LIFO) principle.
What is a Stack? #
A stack is a linear data structure that follows a particular order in which operations are performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out). Real-life examples include a stack of plates or a pile of books.
Basic Operations of a Stack #
- Push: Adds an element to the top of the stack
- Pop: Removes the top element from the stack
- Peek or Top: Returns the top element of the stack without removing it
- isEmpty: Returns true if the stack is empty, else false
Implementing a Stack in Python #
We can implement a stack using a Python list or create a custom class. Let’s implement both:
Using a Python List #
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
def push(self, item):
self.items.append(item)
def pop(self):
if not self.is_empty():
return self.items.pop()
else:
raise IndexError("Stack is empty")
def peek(self):
if not self.is_empty():
return self.items[-1]
else:
raise IndexError("Stack is empty")
def size(self):
return len(self.items)
Using a Custom Node Class #
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Stack:
def __init__(self):
self.top = None
def is_empty(self):
return self.top is None
def push(self, item):
new_node = Node(item)
new_node.next = self.top
self.top = new_node
def pop(self):
if self.is_empty():
raise IndexError("Stack is empty")
popped = self.top.data
self.top = self.top.next
return popped
def peek(self):
if self.is_empty():
raise IndexError("Stack is empty")
return self.top.data
def size(self):
count = 0
current = self.top
while current:
count += 1
current = current.next
return count
Time Complexity #
- Push operation: O(1)
- Pop operation: O(1)
- Peek operation: O(1)
- Search operation: O(n) in the worst case
Applications of Stacks #
- Function Call Stack: Used by compilers to manage function calls
- Expression Evaluation: Used to evaluate prefix, infix, and postfix expressions
- Backtracking Algorithms: Used in backtracking problems like finding maze paths
- Undo Mechanism: Implementing undo functionality in text editors
- Balanced Parentheses: Checking for balanced parentheses in expressions
Example: Balanced Parentheses #
Let’s implement a function to check if parentheses in an expression are balanced:
def is_balanced(expression):
stack = []
opening = "({["
closing = ")}]"
pairs = {")": "(", "}": "{", "]": "["}
for char in expression:
if char in opening:
stack.append(char)
elif char in closing:
if not stack or stack.pop() != pairs[char]:
return False
return len(stack) == 0
# Test the function
print(is_balanced("({[]})")) # True
print(is_balanced("([)]")) # False
print(is_balanced("((")) # False
Exercise #
- Implement a function to reverse a string using a stack.
- Create a
MinStack
class that supports push, pop, top, and retrieving the minimum element in constant time. - Implement the
Queue
data structure using two stacks.
Summary #
Today, we explored the stack data structure, its implementation, and a practical application. Stacks are crucial in many algorithms and real-world applications due to their LIFO nature. Understanding stacks will help you solve various problems efficiently and is essential for topics like depth-first search and expression parsing.
Tomorrow, we’ll dive into queues, another fundamental data structure that follows the First-In-First-Out (FIFO) principle. Stay tuned!