Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Empty file added graph/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions graph/bfs.py
Original file line number Diff line number Diff line change
@@ -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))
34 changes: 34 additions & 0 deletions graph/dfs.py
Original file line number Diff line number Diff line change
@@ -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))
Empty file added hashing/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions hashing/hash_table.py
Original file line number Diff line number Diff line change
@@ -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"))
Empty file added linked_list/__init__.py
Empty file.
96 changes: 96 additions & 0 deletions linked_list/circular_linked_list.py
Original file line number Diff line number Diff line change
@@ -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())
74 changes: 74 additions & 0 deletions linked_list/doubly_linked_list.py
Original file line number Diff line number Diff line change
@@ -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())
Loading