diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea0a357 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Python DSA Templates + +A compact collection of Python implementations for common data structures and algorithms. + +## What is included + +- Binary search +- Sorting algorithms: + - Bubble sort + - Bucket sort + - Counting sort + - Heap sort + - Insertion sort + - Merge sort + - Quick sort + - Radix sort + - Selection sort +- Graph algorithms: + - Bellman-Ford + - Dijkstra + - Kruskal's algorithm + - Prim's algorithm + - Undirected cycle detection + - BFS + - DFS +- Tree implementations: + - Binary search tree + - AVL tree +- Linked list implementations: + - Singly linked list + - Doubly linked list + - Circular linked list +- Stack and queue implementations: + - Stack + - Queue + - Deque +- Hashing: + - Hash table +- Heap implementations: + - Min heap + - Max heap + +## Repository layout + +- `binary_search.py` - binary search implementation +- `sort/` - sorting algorithm implementations +- `graph/` - graph algorithm implementations +- `tree/` - tree algorithm implementations +- `linked_list/` - linked list implementations +- `stack/` - stack implementation +- `queue/` - queue and deque implementations +- `hashing/` - hash table implementation +- `heap/` - heap implementations + +## Example usage + +Binary search: + +```python +from binary_search import binary_search + +arr = [1, 3, 5, 7, 9] +print(binary_search(arr, 5)) # 2 +``` + +Quick sort: + +```python +from sort.quick_sort import quick_sort + +arr = [3, 6, 8, 10, 1, 2, 1] +quick_sort(arr, 0, len(arr) - 1) +print(arr) +``` + +## Contributing + +Contributions are welcome. If you would like to improve this repository, you can: + +- add missing algorithms or variants +- improve existing implementations +- add tests and examples +- improve documentation + +Please keep changes focused, readable, and consistent with the existing style of the project. diff --git a/graph/__init__.py b/graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graph/bfs.py b/graph/bfs.py new file mode 100644 index 0000000..9c0bac8 --- /dev/null +++ b/graph/bfs.py @@ -0,0 +1,39 @@ +from collections import deque + + +class Graph: + def __init__(self, vertices): + self.vertices = vertices + self.adj_list = [[] for _ in range(vertices)] + + def add_edge(self, source, destination): + self.adj_list[source].append(destination) + self.adj_list[destination].append(source) + + def bfs(self, start): + """Traverse the graph in breadth-first order from the given start node.""" + visited = [False] * self.vertices + traversal = [] + queue = deque([start]) + visited[start] = True + + while queue: + node = queue.popleft() + traversal.append(node) + for neighbor in self.adj_list[node]: + if not visited[neighbor]: + visited[neighbor] = True + queue.append(neighbor) + + return traversal + + +# Example usage + +graph = Graph(6) +graph.add_edge(0, 1) +graph.add_edge(0, 2) +graph.add_edge(1, 3) +graph.add_edge(2, 4) +graph.add_edge(3, 5) +print(graph.bfs(0)) diff --git a/graph/dfs.py b/graph/dfs.py new file mode 100644 index 0000000..b7d0130 --- /dev/null +++ b/graph/dfs.py @@ -0,0 +1,34 @@ +class Graph: + def __init__(self, vertices): + self.vertices = vertices + self.adj_list = [[] for _ in range(vertices)] + + def add_edge(self, source, destination): + self.adj_list[source].append(destination) + self.adj_list[destination].append(source) + + def dfs(self, start): + """Traverse the graph in depth-first order from the given start node.""" + visited = [False] * self.vertices + traversal = [] + + def visit(node): + visited[node] = True + traversal.append(node) + for neighbor in self.adj_list[node]: + if not visited[neighbor]: + visit(neighbor) + + visit(start) + return traversal + + +# Example usage + +graph = Graph(6) +graph.add_edge(0, 1) +graph.add_edge(0, 2) +graph.add_edge(1, 3) +graph.add_edge(2, 4) +graph.add_edge(3, 5) +print(graph.dfs(0)) diff --git a/hashing/__init__.py b/hashing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hashing/hash_table.py b/hashing/hash_table.py new file mode 100644 index 0000000..2a317f6 --- /dev/null +++ b/hashing/hash_table.py @@ -0,0 +1,42 @@ +class HashTable: + def __init__(self, size=10): + self.size = size + self.table = [[] for _ in range(size)] + + def _hash(self, key): + return hash(key) % self.size + + def insert(self, key, value): + """Insert a key-value pair into the hash table. Time: O(1) average, Space: O(1).""" + position = self._hash(key) + for index, (existing_key, _) in enumerate(self.table[position]): + if existing_key == key: + self.table[position][index] = (key, value) + return + self.table[position].append((key, value)) + + def delete(self, key): + """Delete a key from the hash table. Time: O(1) average, Space: O(1).""" + position = self._hash(key) + for index, (existing_key, _) in enumerate(self.table[position]): + if existing_key == key: + del self.table[position][index] + return + + def search(self, key): + """Search for a value by key. Time: O(1) average, Space: O(1).""" + position = self._hash(key) + for existing_key, value in self.table[position]: + if existing_key == key: + return value + return None + + +# Example usage + +hash_table = HashTable() +hash_table.insert("name", "Alice") +hash_table.insert("age", 25) +print(hash_table.search("name")) +hash_table.delete("age") +print(hash_table.search("age")) diff --git a/linked_list/__init__.py b/linked_list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linked_list/circular_linked_list.py b/linked_list/circular_linked_list.py new file mode 100644 index 0000000..c1e664d --- /dev/null +++ b/linked_list/circular_linked_list.py @@ -0,0 +1,96 @@ +class Node: + def __init__(self, data): + self.data = data + self.next = None + + +class CircularLinkedList: + def __init__(self): + self.head = None + + def insert_at_beginning(self, data): + """Insert a node at the beginning. Time: O(1), Space: O(1).""" + new_node = Node(data) + if self.head is None: + self.head = new_node + new_node.next = new_node + return + current = self.head + while current.next != self.head: + current = current.next + current.next = new_node + new_node.next = self.head + self.head = new_node + + def insert_at_end(self, data): + """Insert a node at the end. Time: O(n), Space: O(1).""" + new_node = Node(data) + if self.head is None: + self.head = new_node + new_node.next = new_node + return + current = self.head + while current.next != self.head: + current = current.next + current.next = new_node + new_node.next = self.head + + def delete(self, data): + """Delete the first node matching data. Time: O(n), Space: O(1).""" + if self.head is None: + return + if self.head.data == data and self.head.next == self.head: + self.head = None + return + current = self.head + previous = None + while current.next != self.head and current.data != data: + previous = current + current = current.next + if current.data == data: + if previous is None: + last_node = self.head + while last_node.next != self.head: + last_node = last_node.next + self.head = self.head.next + last_node.next = self.head + else: + previous.next = current.next + + def search(self, data): + """Search for a node containing data. Time: O(n), Space: O(1).""" + if self.head is None: + return False + current = self.head + while True: + if current.data == data: + return True + current = current.next + if current == self.head: + break + return False + + def traverse(self): + """Return a list of values in the linked list. Time: O(n), Space: O(n).""" + if self.head is None: + return [] + values = [] + current = self.head + while True: + values.append(current.data) + current = current.next + if current == self.head: + break + return values + + +# Example usage + +linked_list = CircularLinkedList() +linked_list.insert_at_beginning(10) +linked_list.insert_at_end(20) +linked_list.insert_at_beginning(5) +print(linked_list.traverse()) +print(linked_list.search(20)) +linked_list.delete(10) +print(linked_list.traverse()) diff --git a/linked_list/doubly_linked_list.py b/linked_list/doubly_linked_list.py new file mode 100644 index 0000000..911ce3b --- /dev/null +++ b/linked_list/doubly_linked_list.py @@ -0,0 +1,74 @@ +class Node: + def __init__(self, data): + self.data = data + self.prev = None + self.next = None + + +class DoublyLinkedList: + def __init__(self): + self.head = None + + def insert_at_beginning(self, data): + """Insert a node at the beginning. Time: O(1), Space: O(1).""" + new_node = Node(data) + new_node.next = self.head + if self.head is not None: + self.head.prev = new_node + self.head = new_node + + def insert_at_end(self, data): + """Insert a node at the end. Time: O(n), Space: O(1).""" + new_node = Node(data) + if self.head is None: + self.head = new_node + return + current = self.head + while current.next is not None: + current = current.next + current.next = new_node + new_node.prev = current + + def delete(self, data): + """Delete the first node matching data. Time: O(n), Space: O(1).""" + current = self.head + while current is not None and current.data != data: + current = current.next + if current is None: + return + if current.prev is not None: + current.prev.next = current.next + else: + self.head = current.next + if current.next is not None: + current.next.prev = current.prev + + def search(self, data): + """Search for a node containing data. Time: O(n), Space: O(1).""" + current = self.head + while current is not None: + if current.data == data: + return True + current = current.next + return False + + def traverse(self): + """Return a list of values in the linked list. Time: O(n), Space: O(n).""" + values = [] + current = self.head + while current is not None: + values.append(current.data) + current = current.next + return values + + +# Example usage + +linked_list = DoublyLinkedList() +linked_list.insert_at_beginning(10) +linked_list.insert_at_end(20) +linked_list.insert_at_beginning(5) +print(linked_list.traverse()) +print(linked_list.search(20)) +linked_list.delete(10) +print(linked_list.traverse()) diff --git a/linked_list/singly_linked_list.py b/linked_list/singly_linked_list.py new file mode 100644 index 0000000..490b846 --- /dev/null +++ b/linked_list/singly_linked_list.py @@ -0,0 +1,69 @@ +class Node: + def __init__(self, data): + self.data = data + self.next = None + + +class SinglyLinkedList: + def __init__(self): + self.head = None + + def insert_at_beginning(self, data): + """Insert a node at the beginning. Time: O(1), Space: O(1).""" + new_node = Node(data) + new_node.next = self.head + self.head = new_node + + def insert_at_end(self, data): + """Insert a node at the end. Time: O(n), Space: O(1).""" + new_node = Node(data) + if self.head is None: + self.head = new_node + return + current = self.head + while current.next is not None: + current = current.next + current.next = new_node + + def delete(self, data): + """Delete the first node matching data. Time: O(n), Space: O(1).""" + if self.head is None: + return + if self.head.data == data: + self.head = self.head.next + return + current = self.head + while current.next is not None and current.next.data != data: + current = current.next + if current.next is not None: + current.next = current.next.next + + def search(self, data): + """Search for a node containing data. Time: O(n), Space: O(1).""" + current = self.head + while current is not None: + if current.data == data: + return True + current = current.next + return False + + def traverse(self): + """Return a list of values in the linked list. Time: O(n), Space: O(n).""" + values = [] + current = self.head + while current is not None: + values.append(current.data) + current = current.next + return values + + +# Example usage + +linked_list = SinglyLinkedList() +linked_list.insert_at_beginning(10) +linked_list.insert_at_end(20) +linked_list.insert_at_beginning(5) +print(linked_list.traverse()) +print(linked_list.search(20)) +linked_list.delete(10) +print(linked_list.traverse()) diff --git a/queue/__init__.py b/queue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/queue/deque.py b/queue/deque.py new file mode 100644 index 0000000..46a83a6 --- /dev/null +++ b/queue/deque.py @@ -0,0 +1,36 @@ +class Deque: + def __init__(self): + self.items = [] + + def insert_front(self, item): + """Insert an item at the front. Time: O(1), Space: O(1).""" + self.items.insert(0, item) + + def insert_rear(self, item): + """Insert an item at the rear. Time: O(1), Space: O(1).""" + self.items.append(item) + + def delete_front(self): + """Remove and return the front item. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Deque is empty") + return self.items.pop(0) + + def delete_rear(self): + """Remove and return the rear item. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Deque is empty") + return self.items.pop() + + def is_empty(self): + """Return True if the deque is empty. Time: O(1), Space: O(1).""" + return len(self.items) == 0 + + +# Example usage + +deque = Deque() +deque.insert_front(10) +deque.insert_rear(20) +print(deque.delete_front()) +print(deque.delete_rear()) diff --git a/queue/queue.py b/queue/queue.py new file mode 100644 index 0000000..0a44512 --- /dev/null +++ b/queue/queue.py @@ -0,0 +1,39 @@ +class Queue: + def __init__(self): + self.items = [] + + def enqueue(self, item): + """Add an item to the rear of the queue. Time: O(1), Space: O(1).""" + self.items.append(item) + + def dequeue(self): + """Remove and return the front item. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Queue is empty") + return self.items.pop(0) + + def front(self): + """Return the front item without removing it. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Queue is empty") + return self.items[0] + + def rear(self): + """Return the rear item without removing it. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Queue is empty") + return self.items[-1] + + def is_empty(self): + """Return True if the queue is empty. Time: O(1), Space: O(1).""" + return len(self.items) == 0 + + +# Example usage + +queue = Queue() +queue.enqueue(10) +queue.enqueue(20) +print(queue.front()) +print(queue.rear()) +print(queue.dequeue()) diff --git a/stack/__init__.py b/stack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stack/stack.py b/stack/stack.py new file mode 100644 index 0000000..04eb52b --- /dev/null +++ b/stack/stack.py @@ -0,0 +1,37 @@ +class Stack: + def __init__(self): + self.items = [] + + def push(self, item): + """Push an item onto the stack. Time: O(1), Space: O(1).""" + self.items.append(item) + + def pop(self): + """Remove and return the top item. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self.items.pop() + + def peek(self): + """Return the top item without removing it. Time: O(1), Space: O(1).""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self.items[-1] + + def is_empty(self): + """Return True if the stack is empty. Time: O(1), Space: O(1).""" + return len(self.items) == 0 + + def size(self): + """Return the number of items in the stack. Time: O(1), Space: O(1).""" + return len(self.items) + + +# Example usage + +stack = Stack() +stack.push(10) +stack.push(20) +print(stack.peek()) +print(stack.pop()) +print(stack.size()) diff --git a/tree/__init__.py b/tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tree/avl_tree.py b/tree/avl_tree.py new file mode 100644 index 0000000..bc052eb --- /dev/null +++ b/tree/avl_tree.py @@ -0,0 +1,125 @@ +class Node: + def __init__(self, key): + self.key = key + self.left = None + self.right = None + self.height = 1 + + +class AVLTree: + def __init__(self): + self.root = None + + def insert(self, key): + """Insert a key into the AVL tree. Time: O(log n), Space: O(log n).""" + self.root = self._insert(self.root, key) + + def _insert(self, current, key): + if current is None: + return Node(key) + if key < current.key: + current.left = self._insert(current.left, key) + else: + current.right = self._insert(current.right, key) + current.height = 1 + max(self._height(current.left), self._height(current.right)) + balance = self._balance(current) + if balance > 1: + if key < current.left.key: + return self._right_rotate(current) + return self._left_right_rotate(current) + if balance < -1: + if key > current.right.key: + return self._left_rotate(current) + return self._right_left_rotate(current) + return current + + def delete(self, key): + """Delete a key from the AVL tree. Time: O(log n), Space: O(log n).""" + self.root = self._delete(self.root, key) + + def _delete(self, current, key): + if current is None: + return None + if key < current.key: + current.left = self._delete(current.left, key) + elif key > current.key: + current.right = self._delete(current.right, key) + else: + if current.left is None: + return current.right + if current.right is None: + return current.left + successor = self._min_node(current.right) + current.key = successor.key + current.right = self._delete(current.right, successor.key) + + if current is None: + return None + current.height = 1 + max(self._height(current.left), self._height(current.right)) + balance = self._balance(current) + if balance > 1: + if self._balance(current.left) >= 0: + return self._right_rotate(current) + return self._left_right_rotate(current) + if balance < -1: + if self._balance(current.right) <= 0: + return self._left_rotate(current) + return self._right_left_rotate(current) + return current + + def search(self, key): + """Search for a key in the AVL tree. Time: O(log n), Space: O(1).""" + current = self.root + while current is not None: + if key == current.key: + return current + if key < current.key: + current = current.left + else: + current = current.right + return None + + def _height(self, node): + return node.height if node is not None else 0 + + def _balance(self, node): + return self._height(node.left) - self._height(node.right) if node is not None else 0 + + def _min_node(self, current): + while current.left is not None: + current = current.left + return current + + def _left_rotate(self, node): + right_child = node.right + node.right = right_child.left + right_child.left = node + node.height = 1 + max(self._height(node.left), self._height(node.right)) + right_child.height = 1 + max(self._height(right_child.left), self._height(right_child.right)) + return right_child + + def _right_rotate(self, node): + left_child = node.left + node.left = left_child.right + left_child.right = node + node.height = 1 + max(self._height(node.left), self._height(node.right)) + left_child.height = 1 + max(self._height(left_child.left), self._height(left_child.right)) + return left_child + + def _left_right_rotate(self, node): + node.left = self._left_rotate(node.left) + return self._right_rotate(node) + + def _right_left_rotate(self, node): + node.right = self._right_rotate(node.right) + return self._left_rotate(node) + + +# Example usage + +avl = AVLTree() +for value in [10, 20, 30, 40, 50]: + avl.insert(value) +print(avl.search(30).key) +avl.delete(20) +print(avl.search(20)) diff --git a/tree/binary_search_tree.py b/tree/binary_search_tree.py new file mode 100644 index 0000000..9dc369e --- /dev/null +++ b/tree/binary_search_tree.py @@ -0,0 +1,113 @@ +class Node: + def __init__(self, key): + self.key = key + self.left = None + self.right = None + + +class BinarySearchTree: + def __init__(self): + self.root = None + + def insert(self, key): + """Insert a key into the BST. Time: O(h), Space: O(1).""" + self.root = self._insert_recursive(self.root, key) + + def _insert_recursive(self, current, key): + if current is None: + return Node(key) + if key < current.key: + current.left = self._insert_recursive(current.left, key) + elif key > current.key: + current.right = self._insert_recursive(current.right, key) + return current + + def search(self, key): + """Search for a key in the BST. Time: O(h), Space: O(1).""" + return self._search_recursive(self.root, key) + + def _search_recursive(self, current, key): + if current is None or current.key == key: + return current + if key < current.key: + return self._search_recursive(current.left, key) + return self._search_recursive(current.right, key) + + def delete(self, key): + """Delete a key from the BST. Time: O(h), Space: O(1).""" + self.root = self._delete_recursive(self.root, key) + + def _delete_recursive(self, current, key): + if current is None: + return None + if key < current.key: + current.left = self._delete_recursive(current.left, key) + elif key > current.key: + current.right = self._delete_recursive(current.right, key) + else: + if current.left is None: + return current.right + if current.right is None: + return current.left + successor = self._min_node(current.right) + current.key = successor.key + current.right = self._delete_recursive(current.right, successor.key) + return current + + def _min_node(self, current): + while current.left is not None: + current = current.left + return current + + def inorder_traversal(self): + """Return keys in inorder order. Time: O(n), Space: O(h).""" + result = [] + self._inorder(self.root, result) + return result + + def _inorder(self, current, result): + if current is None: + return + self._inorder(current.left, result) + result.append(current.key) + self._inorder(current.right, result) + + def preorder_traversal(self): + """Return keys in preorder order. Time: O(n), Space: O(h).""" + result = [] + self._preorder(self.root, result) + return result + + def _preorder(self, current, result): + if current is None: + return + result.append(current.key) + self._preorder(current.left, result) + self._preorder(current.right, result) + + def postorder_traversal(self): + """Return keys in postorder order. Time: O(n), Space: O(h).""" + result = [] + self._postorder(self.root, result) + return result + + def _postorder(self, current, result): + if current is None: + return + self._postorder(current.left, result) + self._postorder(current.right, result) + result.append(current.key) + + +# Example usage + +bst = BinarySearchTree() +bst.insert(50) +bst.insert(30) +bst.insert(70) +bst.insert(20) +bst.insert(40) +print(bst.inorder_traversal()) +print(bst.search(30).key) +bst.delete(20) +print(bst.inorder_traversal())