Graph Theory
Definitions
Formulas
Python Implementations
Graph Representations
import numpy as np
from collections import defaultdict
# Adjacency Matrix
class AdjMatrixGraph:
def __init__(self, n):
self.n = n
self.matrix = np.zeros((n, n), dtype=int)
def add_edge(self, u, v, weight=1):
self.matrix[u][v] = weight
self.matrix[v][u] = weight # undirected
def has_edge(self, u, v):
return self.matrix[u][v] != 0
def degree(self, v):
return int(self.matrix[v].sum())
# Adjacency List
class AdjListGraph:
def __init__(self):
self.graph = defaultdict(list)
def add_edge(self, u, v, weight=1):
self.graph[u].append((v, weight))
self.graph[v].append((u, weight))
def neighbors(self, v):
return self.graph[v]
def degree(self, v):
return len(self.graph[v])
# Example usage
g = AdjListGraph()
edges = [(0,1), (0,2), (1,2), (2,3), (3,4)]
for u, v in edges:
g.add_edge(u, v)
print(f"Degree of node 2: {g.degree(2)}")
print(f"Neighbors of 2: {g.neighbors(2)}")
BFS and DFS Traversal
from collections import defaultdict, deque
class Graph:
def __init__(self):
self.graph = defaultdict(list)
def add_edge(self, u, v):
self.graph[u].append(v)
self.graph[v].append(u)
def bfs(self, start):
visited = set()
queue = deque([start])
visited.add(start)
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in self.graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
def dfs(self, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
order = [start]
for neighbor in self.graph[start]:
if neighbor not in visited:
order.extend(self.dfs(neighbor, visited))
return order
def is_connected(self, n_vertices):
visited = self.bfs(0)
return len(visited) == n_vertices
g = Graph()
for u, v in [(0,1), (0,2), (1,3), (2,3), (3,4)]:
g.add_edge(u, v)
print("BFS:", g.bfs(0))
print("DFS:", g.dfs(0))
print("Connected:", g.is_connected(5))
Euler Path Detection
def has_euler_path(graph, n_vertices):
odd_degree = [v for v in range(n_vertices) if graph.degree(v) % 2 != 0]
return len(odd_degree) == 0 or len(odd_degree) == 2
def euler_circuit_exists(graph, n_vertices):
return all(graph.degree(v) % 2 == 0 for v in range(n_vertices))
Graph Coloring
def greedy_coloring(graph, n_vertices):
result = [-1] * n_vertices
result[0] = 0
for u in range(1, n_vertices):
available = set(range(n_vertices))
for neighbor in graph[u]:
if result[neighbor] != -1:
available.discard(result[neighbor])
result[u] = min(available)
return result
def chromatic_number_upper_bound(n_vertices):
return n_vertices # Worst case: complete graph
Planarity
Applications in AI/ML
Common Mistakes
| Mistake | Correction |
|---|---|
| Assuming all graphs are simple | A graph may have loops (edge from vertex to itself) and multiple edges between the same pair |
| Confusing directed and undirected degrees | In directed graphs, track in-degree and out-degree separately |
| Ignoring isolated vertices | Vertices with degree 0 still count toward |
| Misapplying Euler's formula | Euler's formula only applies to connected planar graphs |
| Assuming planarity for dense graphs | has only 5 vertices but is non-planar |
| Forgetting the Handshaking Lemma implies even sum | The sum of all degrees is always even, so the number of odd-degree vertices is always even |
| Using BFS when DFS is needed | BFS finds shortest paths; DFS is better for cycle detection and topological sorting |
| Confusing isomorphism with same degree sequence | Same degree sequence does not guarantee isomorphism |
Interview Questions
-
How would you detect a cycle in an undirected graph? Use DFS and check for back edges, or use Union-Find to detect if adding an edge connects two already-connected vertices.
-
What is the difference between BFS and DFS? BFS explores level by level using a queue, finding shortest paths in unweighted graphs. DFS explores as deep as possible using a stack/recursion, useful for topological sort and cycle detection.
-
How do you check if a graph is bipartite? Use BFS or DFS with 2-coloring. If we can color the graph with 2 colors such that no adjacent vertices share a color, it is bipartite.
-
What is the time complexity of graph traversal? BFS and DFS both run in time using adjacency lists.
-
How do you find connected components? Run BFS/DFS from each unvisited vertex. Each traversal marks one connected component.
-
What is a topological sort and when is it used? A topological sort orders vertices in a DAG such that all edges go from earlier to later. Used in task scheduling and dependency resolution.
-
How do you find the shortest path in a weighted graph? Use Dijkstra's algorithm for non-negative weights, Bellman-Ford for graphs with negative weights, or Floyd-Warshall for all-pairs shortest paths.
-
What is graph coloring used for? Map coloring, register allocation in compilers, scheduling exams without conflicts, and frequency assignment in wireless networks.
Practice Problems
Quick Reference
| Concept | Formula/Description |
|---|---|
| Edges in | |
| Handshaking Lemma | |
| Euler's Formula | (connected planar) |
| Planar edge bound | for |
| Bipartite edges | |
| BFS time complexity | |
| DFS time complexity | |
| Chromatic number | Minimum colors for proper coloring |
| Euler path | Exists iff 0 or 2 odd-degree vertices |
| Hamilton cycle | NP-complete to determine existence |
Cross-References
- Trees -> 075-discrete-trees.mdx: Trees are connected acyclic graphs; a special case of graph theory
- Recurrence Relations -> 076-discrete-recurrence.mdx: Graph traversal algorithms have recursive definitions
- Number Theory -> 077-discrete-number-theory.mdx: Modular arithmetic used in graph hashing
- Boolean Algebra -> 078-discrete-boolean.mdx: Boolean functions on graph properties
- Automata Theory -> 079-discrete-automata.mdx: State diagrams are directed graphs
- Applications -> 080-discrete-applications.mdx: Network flow, cryptography, and scheduling use graphs