Spaces:
Sleeping
Sleeping
| GRAPH ALGORITHMS | |
| ================ | |
| A graph has vertices (nodes) connected by edges. Used to model networks, maps, dependencies. | |
| Types: | |
| - Directed / Undirected | |
| - Weighted / Unweighted | |
| - Cyclic / Acyclic (DAG = Directed Acyclic Graph) | |
| Representations: | |
| - Adjacency Matrix: 2D array. O(1) edge lookup, O(V^2) space. | |
| - Adjacency List: dict/list of lists. O(V+E) space. Most common. | |
| # Adjacency List representation | |
| graph = { | |
| 0: [1, 2], | |
| 1: [0, 3, 4], | |
| 2: [0], | |
| 3: [1], | |
| 4: [1] | |
| } | |
| --- BFS (Breadth-First Search) --- | |
| Explore level by level using a queue. Finds shortest path in unweighted graphs. | |
| Time: O(V+E). Space: O(V). | |
| from collections import deque | |
| def bfs(graph, start): | |
| visited = set([start]) | |
| queue = deque([start]) | |
| order = [] | |
| while queue: | |
| node = queue.popleft() | |
| order.append(node) | |
| for neighbor in graph[node]: | |
| if neighbor not in visited: | |
| visited.add(neighbor) | |
| queue.append(neighbor) | |
| return order | |
| graph = {0:[1,2], 1:[0,3,4], 2:[0], 3:[1], 4:[1]} | |
| print(bfs(graph, 0)) # Output: [0, 1, 2, 3, 4] | |
| --- DFS (Depth-First Search) --- | |
| Explore as deep as possible before backtracking. Uses stack (or recursion). | |
| Time: O(V+E). Space: O(V). | |
| # Iterative DFS | |
| def dfs_iterative(graph, start): | |
| visited = set() | |
| stack = [start] | |
| order = [] | |
| while stack: | |
| node = stack.pop() | |
| if node not in visited: | |
| visited.add(node) | |
| order.append(node) | |
| for neighbor in graph[node]: | |
| if neighbor not in visited: | |
| stack.append(neighbor) | |
| return order | |
| # Recursive DFS | |
| def dfs_recursive(graph, node, visited=None): | |
| if visited is None: | |
| visited = set() | |
| visited.add(node) | |
| result = [node] | |
| for neighbor in graph[node]: | |
| if neighbor not in visited: | |
| result += dfs_recursive(graph, neighbor, visited) | |
| return result | |
| print(dfs_iterative(graph, 0)) # Output: [0, 2, 1, 4, 3] | |
| print(dfs_recursive(graph, 0)) # Output: [0, 1, 3, 4, 2] | |
| --- Shortest Path: Dijkstra's Algorithm --- | |
| Find shortest path from source to all nodes in a weighted graph (non-negative weights). | |
| Time: O((V+E) log V) with min-heap. Space: O(V). | |
| import heapq | |
| def dijkstra(graph, start): | |
| # graph: {node: [(neighbor, weight), ...]} | |
| dist = {node: float('inf') for node in graph} | |
| dist[start] = 0 | |
| heap = [(0, start)] # (distance, node) | |
| while heap: | |
| d, u = heapq.heappop(heap) | |
| if d > dist[u]: | |
| continue | |
| for v, w in graph[u]: | |
| if dist[u] + w < dist[v]: | |
| dist[v] = dist[u] + w | |
| heapq.heappush(heap, (dist[v], v)) | |
| return dist | |
| wgraph = { | |
| 'A': [('B', 1), ('C', 4)], | |
| 'B': [('C', 2), ('D', 5)], | |
| 'C': [('D', 1)], | |
| 'D': [] | |
| } | |
| print(dijkstra(wgraph, 'A')) | |
| # Output: {'A': 0, 'B': 1, 'C': 3, 'D': 4} | |
| --- Cycle Detection in Directed Graph --- | |
| Use DFS with a recursion stack to detect back edges. | |
| def has_cycle_directed(graph): | |
| visited = set() | |
| rec_stack = set() | |
| def dfs(node): | |
| visited.add(node) | |
| rec_stack.add(node) | |
| for neighbor in graph.get(node, []): | |
| if neighbor not in visited: | |
| if dfs(neighbor): | |
| return True | |
| elif neighbor in rec_stack: | |
| return True | |
| rec_stack.remove(node) | |
| return False | |
| for node in graph: | |
| if node not in visited: | |
| if dfs(node): | |
| return True | |
| return False | |
| g1 = {0: [1], 1: [2], 2: [0]} # cycle | |
| g2 = {0: [1], 1: [2], 2: []} # no cycle | |
| print(has_cycle_directed(g1)) # True | |
| print(has_cycle_directed(g2)) # False | |
| --- Topological Sort (Kahn's Algorithm) --- | |
| Linear ordering of vertices in a DAG such that for every edge u->v, u comes before v. | |
| Time: O(V+E). Space: O(V). | |
| from collections import deque | |
| def topological_sort(graph, num_nodes): | |
| in_degree = [0] * num_nodes | |
| for u in graph: | |
| for v in graph[u]: | |
| in_degree[v] += 1 | |
| queue = deque([i for i in range(num_nodes) if in_degree[i] == 0]) | |
| order = [] | |
| while queue: | |
| node = queue.popleft() | |
| order.append(node) | |
| for neighbor in graph.get(node, []): | |
| in_degree[neighbor] -= 1 | |
| if in_degree[neighbor] == 0: | |
| queue.append(neighbor) | |
| return order if len(order) == num_nodes else [] # empty = cycle | |
| dag = {0: [1, 2], 1: [3], 2: [3], 3: []} | |
| print(topological_sort(dag, 4)) # Output: [0, 1, 2, 3] or [0, 2, 1, 3] | |
| --- Minimum Spanning Tree: Kruskal's Algorithm --- | |
| Connect all nodes with minimum total edge weight. Uses Union-Find. | |
| Time: O(E log E). Space: O(V). | |
| def kruskal(num_nodes, edges): | |
| # edges: [(weight, u, v), ...] | |
| edges.sort() | |
| parent = list(range(num_nodes)) | |
| def find(x): | |
| while parent[x] != x: | |
| parent[x] = parent[parent[x]] | |
| x = parent[x] | |
| return x | |
| def union(x, y): | |
| px, py = find(x), find(y) | |
| if px == py: | |
| return False | |
| parent[px] = py | |
| return True | |
| mst_cost = 0 | |
| mst_edges = [] | |
| for w, u, v in edges: | |
| if union(u, v): | |
| mst_cost += w | |
| mst_edges.append((u, v, w)) | |
| return mst_cost, mst_edges | |
| edges = [(4,0,1),(2,0,2),(3,1,2),(1,1,3),(5,2,3)] | |
| print(kruskal(4, edges)) | |
| # Output: (6, [(1, 3, 1), (0, 2, 2), (1, 2, 3)]) | |
| --- Bellman-Ford Algorithm --- | |
| Shortest path with negative weights. Detects negative cycles. | |
| Time: O(V*E). Space: O(V). | |
| def bellman_ford(graph, num_nodes, start): | |
| # graph: [(u, v, weight), ...] | |
| dist = [float('inf')] * num_nodes | |
| dist[start] = 0 | |
| for _ in range(num_nodes - 1): | |
| for u, v, w in graph: | |
| if dist[u] + w < dist[v]: | |
| dist[v] = dist[u] + w | |
| # Check for negative cycles | |
| for u, v, w in graph: | |
| if dist[u] + w < dist[v]: | |
| return None # negative cycle | |
| return dist | |
| edges = [(0,1,4),(0,2,5),(1,2,-3),(2,3,4)] | |
| print(bellman_ford(edges, 4, 0)) | |
| # Output: [0, 4, 1, 5] | |