diff --git a/bfs.py b/bfs.py new file mode 100644 index 00000000..9807bf47 --- /dev/null +++ b/bfs.py @@ -0,0 +1,154 @@ +""" +Breadth First Search (BFS) - Graph traversal algorithm +Uses queue to explore all vertices at current depth before moving to next level +Time Complexity: O(V + E) where V is vertices and E is edges +""" + +from collections import deque, defaultdict + + +class Graph: + """Graph class for BFS implementation""" + + def __init__(self): + """Initialize graph using adjacency list""" + self.graph = defaultdict(list) + + def add_edge(self, u, v): + """ + Add an edge to the graph + + Args: + u: Source vertex + v: Destination vertex + """ + self.graph[u].append(v) + + def bfs(self, start): + """ + Perform BFS traversal from starting vertex + + Args: + start: Starting vertex + + Returns: + List of vertices in BFS order + """ + visited = set() + queue = deque([start]) + visited.add(start) + result = [] + + while queue: + vertex = queue.popleft() + result.append(vertex) + + # Visit all adjacent vertices + for neighbor in self.graph[vertex]: + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + return result + + def bfs_with_levels(self, start): + """ + BFS that tracks level of each vertex + + Args: + start: Starting vertex + + Returns: + Dictionary with vertices as keys and levels as values + """ + visited = {start: 0} + queue = deque([start]) + + while queue: + vertex = queue.popleft() + current_level = visited[vertex] + + for neighbor in self.graph[vertex]: + if neighbor not in visited: + visited[neighbor] = current_level + 1 + queue.append(neighbor) + + return visited + + def shortest_path(self, start, end): + """ + Find shortest path between two vertices using BFS + + Args: + start: Starting vertex + end: Ending vertex + + Returns: + List representing shortest path, or None if no path exists + """ + if start == end: + return [start] + + visited = {start} + queue = deque([(start, [start])]) + + while queue: + vertex, path = queue.popleft() + + for neighbor in self.graph[vertex]: + if neighbor == end: + return path + [neighbor] + + if neighbor not in visited: + visited.add(neighbor) + queue.append((neighbor, path + [neighbor])) + + return None + + +if __name__ == "__main__": + print("=" * 50) + print("BREADTH FIRST SEARCH (BFS)") + print("=" * 50) + + # Create a sample graph + g = Graph() + g.add_edge(0, 1) + g.add_edge(0, 2) + g.add_edge(1, 2) + g.add_edge(2, 0) + g.add_edge(2, 3) + g.add_edge(3, 3) + + print("\nGraph edges:") + for vertex, neighbors in g.graph.items(): + print(f"{vertex} -> {neighbors}") + + start_vertex = 2 + print(f"\nBFS starting from vertex {start_vertex}:") + bfs_result = g.bfs(start_vertex) + print(f"Traversal order: {bfs_result}") + + print(f"\nBFS with levels from vertex {start_vertex}:") + levels = g.bfs_with_levels(start_vertex) + for vertex, level in sorted(levels.items()): + print(f"Vertex {vertex}: Level {level}") + + # Shortest path example + print("\n" + "=" * 50) + print("SHORTEST PATH EXAMPLE") + print("=" * 50) + + g2 = Graph() + g2.add_edge('A', 'B') + g2.add_edge('A', 'C') + g2.add_edge('B', 'D') + g2.add_edge('C', 'D') + g2.add_edge('C', 'E') + g2.add_edge('D', 'E') + g2.add_edge('D', 'F') + g2.add_edge('E', 'F') + + start, end = 'A', 'F' + path = g2.shortest_path(start, end) + print(f"\nShortest path from {start} to {end}: {' -> '.join(path)}") diff --git a/queue_implementation.py b/queue_implementation.py new file mode 100644 index 00000000..6009e4d2 --- /dev/null +++ b/queue_implementation.py @@ -0,0 +1,293 @@ +""" +Queue Implementation - FIFO (First In First Out) data structure +Common operations: enqueue, dequeue, peek, is_empty, size +""" + +class Queue: + """Queue implementation using Python list""" + + def __init__(self): + """Initialize an empty queue""" + self.items = [] + + def enqueue(self, item): + """ + Add an item to the rear of the queue + + Args: + item: Item to add to queue + """ + self.items.append(item) + + def dequeue(self): + """ + Remove and return the front item from the queue + + Returns: + Front item, or None if queue is empty + """ + if self.is_empty(): + return None + return self.items.pop(0) + + def peek(self): + """ + Return the front item without removing it + + Returns: + Front item, or None if queue is empty + """ + if self.is_empty(): + return None + return self.items[0] + + def is_empty(self): + """ + Check if queue is empty + + Returns: + True if empty, False otherwise + """ + return len(self.items) == 0 + + def size(self): + """ + Get the number of items in queue + + Returns: + Number of items + """ + return len(self.items) + + def display(self): + """Display all items in the queue""" + if self.is_empty(): + print("Queue is empty") + else: + print(f"Queue (front to rear): {self.items}") + + +class CircularQueue: + """Circular Queue implementation with fixed size""" + + def __init__(self, capacity): + """ + Initialize circular queue with given capacity + + Args: + capacity: Maximum number of items + """ + self.capacity = capacity + self.queue = [None] * capacity + self.front = -1 + self.rear = -1 + self.count = 0 + + def is_empty(self): + """Check if queue is empty""" + return self.count == 0 + + def is_full(self): + """Check if queue is full""" + return self.count == self.capacity + + def enqueue(self, item): + """Add item to circular queue""" + if self.is_full(): + print("Queue is full!") + return False + + if self.front == -1: + self.front = 0 + + self.rear = (self.rear + 1) % self.capacity + self.queue[self.rear] = item + self.count += 1 + return True + + def dequeue(self): + """Remove and return front item""" + if self.is_empty(): + return None + + item = self.queue[self.front] + self.queue[self.front] = None + + if self.front == self.rear: + self.front = -1 + self.rear = -1 + else: + self.front = (self.front + 1) % self.capacity + + self.count -= 1 + return item + + def display(self): + """Display queue contents""" + if self.is_empty(): + print("Circular Queue is empty") + else: + print(f"Circular Queue: {[x for x in self.queue if x is not None]}") + + +class PriorityQueue: + """Priority Queue implementation (min-heap based)""" + + def __init__(self): + """Initialize an empty priority queue""" + self.items = [] + + def enqueue(self, item, priority): + """ + Add item with priority + + Args: + item: Item to add + priority: Priority (lower number = higher priority) + """ + self.items.append((priority, item)) + self.items.sort() + + def dequeue(self): + """Remove and return highest priority item""" + if self.is_empty(): + return None + return self.items.pop(0)[1] + + def is_empty(self): + """Check if queue is empty""" + return len(self.items) == 0 + + def display(self): + """Display queue with priorities""" + if self.is_empty(): + print("Priority Queue is empty") + else: + print("Priority Queue (priority, item):") + for priority, item in self.items: + print(f" ({priority}, {item})") + + +if __name__ == "__main__": + print("=" * 50) + print("QUEUE DATA STRUCTURE DEMONSTRATION") + print("=" * 50) + + # 1. Basic Queue + print("\n1. Basic Queue (FIFO):") + q = Queue() + + q.enqueue("First") + q.enqueue("Second") + q.enqueue("Third") + q.enqueue("Fourth") + + q.display() + print(f"Front item: {q.peek()}") + print(f"Size: {q.size()}") + + print(f"\nDequeued: {q.dequeue()}") + print(f"Dequeued: {q.dequeue()}") + q.display() + + # 2. Circular Queue + print("\n" + "=" * 50) + print("2. Circular Queue:") + cq = CircularQueue(5) + + print("\nEnqueuing 1, 2, 3, 4, 5:") + for i in range(1, 6): + cq.enqueue(i) + cq.display() + + print(f"\nQueue full? {cq.is_full()}") + cq.enqueue(6) # Should fail + + print(f"\nDequeued: {cq.dequeue()}") + print(f"Dequeued: {cq.dequeue()}") + cq.display() + + print("\nEnqueuing 6, 7:") + cq.enqueue(6) + cq.enqueue(7) + cq.display() + + # 3. Priority Queue + print("\n" + "=" * 50) + print("3. Priority Queue:") + pq = PriorityQueue() + + pq.enqueue("Low priority task", 5) + pq.enqueue("High priority task", 1) + pq.enqueue("Medium priority task", 3) + pq.enqueue("Critical task", 0) + + pq.display() + + print("\nProcessing tasks by priority:") + while not pq.is_empty(): + print(f" Processing: {pq.dequeue()}") + + # 4. Real-world example: Print Queue + print("\n" + "=" * 50) + print("4. Print Queue Simulation:") + + print_queue = Queue() + print_queue.enqueue("Document1.pdf") + print_queue.enqueue("Image.png") + print_queue.enqueue("Report.docx") + + print("\nPrint queue:") + print_queue.display() + + print("\nPrinting documents:") + while not print_queue.is_empty(): + doc = print_queue.dequeue() + print(f" Printing: {doc}") + + print("\nAll documents printed!") + print_queue.display() + + # Interactive menu + print("\n" + "=" * 50) + print("5. Interactive Queue:") + user_queue = Queue() + + while True: + print("\nOptions:") + print("1. Enqueue") + print("2. Dequeue") + print("3. Peek") + print("4. Display") + print("5. Exit") + + try: + choice = input("\nEnter choice (1-5): ") + + if choice == '1': + item = input("Enter item to enqueue: ") + user_queue.enqueue(item) + print(f"Enqueued: {item}") + elif choice == '2': + item = user_queue.dequeue() + if item: + print(f"Dequeued: {item}") + else: + print("Queue is empty!") + elif choice == '3': + item = user_queue.peek() + if item: + print(f"Front item: {item}") + else: + print("Queue is empty!") + elif choice == '4': + user_queue.display() + elif choice == '5': + print("Goodbye!") + break + else: + print("Invalid choice!") + + except KeyboardInterrupt: + print("\n\nGoodbye!") + break diff --git a/sieve_of_eratosthenes.py b/sieve_of_eratosthenes.py new file mode 100644 index 00000000..837de974 --- /dev/null +++ b/sieve_of_eratosthenes.py @@ -0,0 +1,140 @@ +""" +Sieve of Eratosthenes - Efficient algorithm to find all primes up to n +Time Complexity: O(n log log n) +Space Complexity: O(n) +""" + +def sieve_of_eratosthenes(n): + """ + Find all prime numbers up to n using Sieve of Eratosthenes + + Args: + n: Upper limit (inclusive) + + Returns: + List of all prime numbers up to n + """ + if n < 2: + return [] + + # Create a boolean array "prime[0..n]" and initialize all entries as true + prime = [True] * (n + 1) + prime[0] = prime[1] = False + + p = 2 + while p * p <= n: + # If prime[p] is not changed, then it's a prime + if prime[p]: + # Mark all multiples of p as not prime + for i in range(p * p, n + 1, p): + prime[i] = False + p += 1 + + # Collect all prime numbers + primes = [i for i in range(n + 1) if prime[i]] + return primes + + +def segmented_sieve(n): + """ + Memory-efficient segmented sieve for very large n + + Args: + n: Upper limit + + Returns: + List of prime numbers + """ + if n < 2: + return [] + + # Find all primes up to sqrt(n) + limit = int(n ** 0.5) + 1 + base_primes = sieve_of_eratosthenes(limit) + + # For small n, just return the result + if n <= limit: + return [p for p in base_primes if p <= n] + + # Use segmented approach for larger numbers + primes = base_primes.copy() + segment_size = limit + + for low in range(limit + 1, n + 1, segment_size): + high = min(low + segment_size - 1, n) + + # Create a segment + segment = [True] * (high - low + 1) + + # Use base primes to mark multiples in this segment + for prime in base_primes: + # Find the minimum number in [low, high] that is a multiple of prime + start = max(prime * prime, ((low + prime - 1) // prime) * prime) + + for j in range(start, high + 1, prime): + segment[j - low] = False + + # Add primes from this segment + for i in range(len(segment)): + if segment[i]: + primes.append(low + i) + + return primes + + +def count_primes(n): + """ + Count number of primes up to n + + Args: + n: Upper limit + + Returns: + Count of primes + """ + return len(sieve_of_eratosthenes(n)) + + +if __name__ == "__main__": + print("=" * 50) + print("SIEVE OF ERATOSTHENES") + print("=" * 50) + + # Example 1: Find primes up to 30 + n1 = 30 + primes1 = sieve_of_eratosthenes(n1) + print(f"\nPrime numbers up to {n1}:") + print(primes1) + print(f"Count: {len(primes1)}") + + # Example 2: Find primes up to 100 + n2 = 100 + primes2 = sieve_of_eratosthenes(n2) + print(f"\nPrime numbers up to {n2}:") + print(primes2) + print(f"Count: {len(primes2)}") + + # User input + print("\n" + "=" * 50) + try: + n = int(input("\nEnter a number to find all primes up to it: ")) + + if n < 2: + print("No prime numbers exist below 2.") + else: + primes = sieve_of_eratosthenes(n) + print(f"\nPrime numbers up to {n}:") + + # Print in rows of 10 for better readability + for i in range(0, len(primes), 10): + print(primes[i:i+10]) + + print(f"\nTotal count: {len(primes)}") + + # Show efficiency comparison + if n >= 1000: + print(f"\nNote: Found {len(primes)} primes efficiently!") + print("Sieve of Eratosthenes is much faster than checking each number individually.") + + except ValueError: + print("Invalid input! Please enter a valid number.") diff --git a/stack_implementation.py b/stack_implementation.py new file mode 100644 index 00000000..51bfc940 --- /dev/null +++ b/stack_implementation.py @@ -0,0 +1,243 @@ +""" +Stack Implementation - LIFO (Last In First Out) data structure +Common operations: push, pop, peek, is_empty, size +""" + +class Stack: + """Stack implementation using Python list""" + + def __init__(self): + """Initialize an empty stack""" + self.items = [] + + def push(self, item): + """ + Add an item to the top of the stack + + Args: + item: Item to push onto stack + """ + self.items.append(item) + + def pop(self): + """ + Remove and return the top item from the stack + + Returns: + Top item, or None if stack is empty + """ + if self.is_empty(): + return None + return self.items.pop() + + def peek(self): + """ + Return the top item without removing it + + Returns: + Top item, or None if stack is empty + """ + if self.is_empty(): + return None + return self.items[-1] + + def is_empty(self): + """ + Check if stack is empty + + Returns: + True if empty, False otherwise + """ + return len(self.items) == 0 + + def size(self): + """ + Get the number of items in stack + + Returns: + Number of items + """ + return len(self.items) + + def display(self): + """Display all items in the stack""" + if self.is_empty(): + print("Stack is empty") + else: + print("Stack (top to bottom):") + for item in reversed(self.items): + print(f" {item}") + + +def reverse_string(text): + """ + Reverse a string using stack + + Args: + text: String to reverse + + Returns: + Reversed string + """ + stack = Stack() + for char in text: + stack.push(char) + + reversed_text = "" + while not stack.is_empty(): + reversed_text += stack.pop() + + return reversed_text + + +def is_balanced_parentheses(expression): + """ + Check if parentheses in expression are balanced + + Args: + expression: String containing parentheses + + Returns: + True if balanced, False otherwise + """ + stack = Stack() + opening = "({[" + closing = ")}]" + matches = {"(": ")", "{": "}", "[": "]"} + + for char in expression: + if char in opening: + stack.push(char) + elif char in closing: + if stack.is_empty(): + return False + if matches[stack.pop()] != char: + return False + + return stack.is_empty() + + +def decimal_to_binary(number): + """ + Convert decimal to binary using stack + + Args: + number: Decimal number + + Returns: + Binary representation as string + """ + if number == 0: + return "0" + + stack = Stack() + while number > 0: + stack.push(number % 2) + number //= 2 + + binary = "" + while not stack.is_empty(): + binary += str(stack.pop()) + + return binary + + +if __name__ == "__main__": + print("=" * 50) + print("STACK DATA STRUCTURE DEMONSTRATION") + print("=" * 50) + + # Basic stack operations + stack = Stack() + + print("\n1. Basic Operations:") + print(f"Is empty? {stack.is_empty()}") + + stack.push(10) + stack.push(20) + stack.push(30) + stack.push(40) + + print(f"\nAfter pushing 10, 20, 30, 40:") + stack.display() + + print(f"\nPeek: {stack.peek()}") + print(f"Size: {stack.size()}") + + print(f"\nPopped: {stack.pop()}") + stack.display() + + # String reversal + print("\n" + "=" * 50) + print("2. String Reversal Using Stack:") + text = "Hello, World!" + reversed_text = reverse_string(text) + print(f"Original: {text}") + print(f"Reversed: {reversed_text}") + + # Balanced parentheses + print("\n" + "=" * 50) + print("3. Balanced Parentheses Checker:") + expressions = [ + "{[()]}", + "{[(])}", + "((()))", + "((())", + "{[}]" + ] + + for expr in expressions: + result = "Balanced" if is_balanced_parentheses(expr) else "Not Balanced" + print(f"{expr}: {result}") + + # Decimal to binary + print("\n" + "=" * 50) + print("4. Decimal to Binary Conversion:") + numbers = [10, 25, 42, 100] + for num in numbers: + binary = decimal_to_binary(num) + print(f"{num} = {binary}") + + # Interactive menu + print("\n" + "=" * 50) + print("5. Interactive Stack:") + user_stack = Stack() + + while True: + print("\nOptions:") + print("1. Push") + print("2. Pop") + print("3. Peek") + print("4. Display") + print("5. Exit") + + try: + choice = input("\nEnter choice (1-5): ") + + if choice == '1': + item = input("Enter item to push: ") + user_stack.push(item) + print(f"Pushed: {item}") + elif choice == '2': + item = user_stack.pop() + if item: + print(f"Popped: {item}") + else: + print("Stack is empty!") + elif choice == '3': + item = user_stack.peek() + if item: + print(f"Top item: {item}") + else: + print("Stack is empty!") + elif choice == '4': + user_stack.display() + elif choice == '5': + print("Goodbye!") + break + else: + print("Invalid choice!") + + except KeyboardInterrupt: + print("\n\nGoodbye!") + break diff --git a/tower_of_hanoi.py b/tower_of_hanoi.py new file mode 100644 index 00000000..8c60fc78 --- /dev/null +++ b/tower_of_hanoi.py @@ -0,0 +1,67 @@ +""" +Tower of Hanoi - Classic recursive problem +Move n disks from source to destination using auxiliary rod +Rules: Only one disk can be moved at a time, and larger disk cannot be on smaller disk +""" + +def tower_of_hanoi(n, source, destination, auxiliary, moves=None): + """ + Solve Tower of Hanoi puzzle + + Args: + n: Number of disks + source: Source rod name + destination: Destination rod name + auxiliary: Auxiliary rod name + moves: List to store moves (optional) + """ + if moves is None: + moves = [] + + if n == 1: + move = f"Move disk 1 from {source} to {destination}" + print(move) + moves.append(move) + return moves + + # Move n-1 disks from source to auxiliary + tower_of_hanoi(n - 1, source, auxiliary, destination, moves) + + # Move the largest disk from source to destination + move = f"Move disk {n} from {source} to {destination}" + print(move) + moves.append(move) + + # Move n-1 disks from auxiliary to destination + tower_of_hanoi(n - 1, auxiliary, destination, source, moves) + + return moves + + +def calculate_minimum_moves(n): + """Calculate minimum number of moves required""" + return 2**n - 1 + + +if __name__ == "__main__": + print("=" * 50) + print("TOWER OF HANOI SOLVER") + print("=" * 50) + + try: + n = int(input("\nEnter number of disks: ")) + + if n <= 0: + print("Please enter a positive number!") + else: + print(f"\nMinimum moves required: {calculate_minimum_moves(n)}") + print("\nSolution:") + print("-" * 50) + + moves = tower_of_hanoi(n, 'A', 'C', 'B') + + print("-" * 50) + print(f"\nTotal moves: {len(moves)}") + + except ValueError: + print("Invalid input! Please enter a valid number.")