| name | graph-theory-math |
| description | Graph algorithms including shortest paths, network flow, matching, connectivity, coloring, and community detection for network analysis. |
| category | mathematics |
| tags | ["mathematics","graph-theory","shortest-paths","network-flow","matching","connectivity","graph-coloring","algorithms"] |
| difficulty | intermediate |
| author | neuralblitz |
Graph Theory
What I do
I provide comprehensive expertise in graph theory, the mathematical study of networks consisting of vertices connected by edges. I enable you to analyze graph properties, implement traversal algorithms, solve shortest path problems, compute network flows, find matchings, detect communities, and solve graph coloring problems. My knowledge spans from fundamental graph properties to advanced algorithms essential for social network analysis, transportation systems, computer networks, and optimization problems.
When to use me
Use graph theory when you need to: find shortest paths in routing and navigation, solve network flow problems for resource allocation, detect communities in social networks, find maximum matchings in assignment problems, analyze connectivity and reliability of networks, schedule tasks with dependencies, solve puzzles like Sudoku as graph coloring, analyze social network influence, or optimize supply chain and logistics.
Core Concepts
- Graph Representations: Adjacency matrices and lists for storing graphs with different space/time tradeoffs.
- Graph Traversal: BFS and DFS algorithms for systematic exploration of graph vertices.
- Shortest Path Algorithms: Dijkstra's, Bellman-Ford, and Floyd-Warshall for finding optimal paths.
- Network Flow: Max-flow min-cut theorem and Ford-Fulkerson for flow optimization.
- Matching: Pairing vertices without sharing edges for assignment and pairing problems.
- Connectivity: Properties of connected components and techniques for finding them.
- Graph Coloring: Assigning colors to vertices so adjacent vertices have different colors.
- Eulerian and Hamiltonian Paths: Traversing all edges or vertices exactly once.
- Centrality Measures: Degree, betweenness, and closeness for identifying important vertices.
- Community Detection: Clustering algorithms for finding densely connected subgroups.
Code Examples
Graph Representations and Traversal
from collections import deque
import heapq
class Graph:
def __init__(self, directed=False):
self.adjacency = {}
self.directed = directed
def add_vertex(self, v):
if v not in self.adjacency:
self.adjacency[v] = []
def add_edge(self, u, v, weight=1):
self.add_vertex(u)
self.add_vertex(v)
self.adjacency[u].append((v, weight))
if not self.directed:
self.adjacency[v].append((u, weight))
def bfs(self, start):
"""Breadth-first search."""
visited = {start}
queue = deque([start])
order = []
while queue:
vertex = queue.popleft()
order.append(vertex)
for neighbor, _ in self.adjacency[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
def ():
visited = ()
stack = [start]
order = []
stack:
vertex = stack.pop()
vertex visited:
visited.add(vertex)
order.append(vertex)
neighbor, _ .adjacency[vertex]:
neighbor visited:
stack.append(neighbor)
order
():
visited = ()
order = []
():
visited.add(v)
order.append(v)
neighbor, _ .adjacency[v]:
neighbor visited:
dfs(neighbor)
dfs(start)
order
g = Graph()
edges = [(, ), (, ), (, ), (, ), (, ), (, ), (, )]
u, v edges:
g.add_edge(u, v)
()
()
():
visited = ()
components = []
vertex graph.adjacency:
vertex visited:
component = (graph.bfs(vertex))
visited.update(component)
components.append(component)
components
components = connected_components(g)
()
():
color = {}
start graph.adjacency:
start color:
color[start] =
queue = deque([start])
queue:
v = queue.popleft()
neighbor, _ graph.adjacency[v]:
neighbor color:
color[neighbor] = - color[v]
queue.append(neighbor)
color[neighbor] == color[v]:
()
Shortest Path Algorithms
import heapq
import math
def dijkstra(graph, start, end=None):
"""Dijkstra's shortest path algorithm."""
distances = {v: float('inf') for v in graph.adjacency}
distances[start] = 0
predecessors = {start: None}
pq = [(0, start)]
visited = set()
while pq:
dist, vertex = heapq.heappop(pq)
if vertex in visited:
continue
visited.add(vertex)
if vertex == end:
break
for neighbor, weight in graph.adjacency[vertex]:
if neighbor in visited:
continue
new_dist = dist + weight
if new_dist < distances[neighbor]:
distances[neighbor] = new_dist
predecessors[neighbor] = vertex
heapq.heappush(pq, (new_dist, neighbor))
return distances, predecessors
def bellman_ford(graph, start):
"""Bellman-Ford algorithm for graphs with negative weights."""
distances = {v: float('inf') for v in graph.adjacency}
distances[start] = 0
predecessors = {}
n = len(graph.adjacency)
for _ in range(n - 1):
u graph.adjacency:
v, w graph.adjacency[u]:
distances[u] != () distances[u] + w < distances[v]:
distances[v] = distances[u] + w
predecessors[v] = u
u graph.adjacency:
v, w graph.adjacency[u]:
distances[u] != () distances[u] + w < distances[v]:
,
distances, predecessors
():
n = (graph.adjacency)
vertices = (graph.adjacency.keys())
idx = {v: i i, v (vertices)}
dist = [[()] * n _ (n)]
i (n):
dist[i][i] =
u graph.adjacency:
v, w graph.adjacency[u]:
i, j = idx[u], idx[v]
dist[i][j] = (dist[i][j], w)
k (n):
i (n):
j (n):
dist[i][k] + dist[j][k] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
dist, vertices
gw = Graph()
weighted_edges = [(, , ), (, , ), (, , ), (, , ), (, , ), (, , )]
u, v, w weighted_edges:
gw.add_edge(u, v, w)
distances, predecessors = dijkstra(gw, )
()
():
path = []
current = end
current :
path.append(current)
current = predecessors.get(current)
((path))
path = reconstruct_path(predecessors, , )
()
gn = Graph()
gn.add_edge(, , )
gn.add_edge(, , )
gn.add_edge(, , -)
gn.add_edge(, , )
gn.add_edge(, , )
gn.add_edge(, , -)
dist_neg, pred_neg = bellman_ford(gn, )
dist_neg :
()
:
()
Network Flow
import copy
class FlowNetwork:
def __init__(self):
self.graph = {}
self.capacity = {}
self.flow = {}
def add_edge(self, u, v, capacity):
if u not in self.graph:
self.graph[u] = []
if v not in self.graph:
self.graph[v] = []
self.graph[u].append(v)
self.graph[v].append(u)
self.capacity[(u, v)] = capacity
self.capacity[(v, u)] = 0
self.flow[(u, v)] = 0
self.flow[(v, u)] = 0
def bfs_level_graph(self, source, sink):
"""BFS to build level graph."""
level = {source: 0}
queue = [source]
while queue:
u = queue.pop(0)
for v in self.graph[u]:
if v not in level and .capacity[(u, v)] - .flow[(u, v)] > :
level[v] = level[u] +
queue.append(v)
level
():
u == sink:
flow
i (current_flow[u], (.graph[u])):
v = .graph[u][i]
v level .capacity[(u, v)] - .flow[(u, v)] > :
min_cap = (flow, .capacity[(u, v)] - .flow[(u, v)])
pushed = .dfs_blocking_flow(v, sink, min_cap, level, current_flow)
pushed > :
.flow[(u, v)] += pushed
.flow[(v, u)] -= pushed
pushed
current_flow[u] +=
():
max_flow =
:
level = .bfs_level_graph(source, sink)
sink level:
current_flow = {u: u .graph}
:
pushed = .dfs_blocking_flow(source, sink, (), level, current_flow)
pushed == :
max_flow += pushed
max_flow
flow_net = FlowNetwork()
flow_net.add_edge(, , )
flow_net.add_edge(, , )
flow_net.add_edge(, , )
flow_net.add_edge(, , )
flow_net.add_edge(, , )
max_flow = flow_net.max_flow(, )
()
():
visited = {source}
queue = [source]
queue:
u = queue.pop()
v flow_net.graph[u]:
v visited flow_net.capacity[(u, v)] - flow_net.flow[(u, v)] > :
visited.add(v)
queue.append(v)
visited
cut = min_cut(flow_net, )
()
Graph Coloring and Matching
import itertools
def greedy_coloring(graph):
"""Graph coloring using greedy algorithm."""
colors = {}
available_colors = {}
for v in graph.adjacency:
available_colors[v] = set()
for v in graph.adjacency:
used_colors = {colors.get(n) for n in graph.adjacency[v] if n in colors}
color = 0
while color in used_colors:
color += 1
colors[v] = color
return colors
def graph_coloring_backtracking(graph, colors, current_assignment=None):
"""Backtracking graph coloring (exact algorithm)."""
if current_assignment is None:
current_assignment = {}
if len(current_assignment) == len(graph.adjacency):
return current_assignment
uncolored = [v for v in graph.adjacency if v not in current_assignment]
vertex = min(uncolored, key=lambda v: sum(1 n graph.adjacency[v]
n current_assignment))
used_colors = {current_assignment.get(n) n graph.adjacency[vertex]
n current_assignment}
color (colors):
color used_colors:
current_assignment[vertex] = color
result = graph_coloring_backtracking(graph, colors, current_assignment)
result :
result
current_assignment[vertex]
gc = Graph()
gc.add_edge(, )
gc.add_edge(, )
gc.add_edge(, )
gc.add_edge(, )
gc.add_edge(, )
gc.add_edge(, )
greedy_colors = greedy_coloring(gc)
()
exact_colors = graph_coloring_backtracking(gc, )
()
():
= {}
():
parent = {v: v graph.adjacency}
queue = [v v graph.adjacency v ]
start queue:
parent[start] = -
u queue:
.get(u) :
v, _ graph.adjacency[u]:
parent[v] :
parent[v] = u
.get(v) :
parent
queue.append([v])
:
parent = bfs_augmenting_path()
parent :
v = [v v parent .get(v) parent[v] ][]
v parent[v] :
u = parent[v]
prev = .get(u)
[u] = v
[v] = u
v = prev
gm = Graph()
edges = [(, ), (, ), (, ), (, ), (, ), (, )]
u, v edges:
gm.add_edge(u, v)
matching = maximum_matching(gm)
()
Centrality and Community Detection
import numpy as np
from collections import defaultdict
def degree_centrality(graph):
"""Compute degree centrality."""
n = len(graph.adjacency)
max_degree = n - 1
centrality = {}
for v in graph.adjacency:
degree = len(graph.adjacency[v])
centrality[v] = degree / max_degree
return centrality
def betweenness_centrality(graph):
"""Compute betweenness centrality (Brandes algorithm)."""
n = len(graph.adjacency)
betweenness = {v: 0.0 for v in graph.adjacency}
for s in graph.adjacency:
S = []
P = {v: [] for v in graph.adjacency}
sigma = {v: 0 for v in graph.adjacency}
d = {v: -1 for v in graph.adjacency}
sigma[s] = 1
d[s] = 0
queue = [s]
while queue:
v = queue.pop(0)
S.append(v)
for w, _ in graph.adjacency[v]:
if d[w] < 0:
queue.append(w)
d[w] = d[v] + 1
if d[w] == d[v] + :
sigma[w] += sigma[v]
P[w].append(v)
delta = {v: v graph.adjacency}
S:
w = S.pop()
v P[w]:
delta[v] += (sigma[v] / sigma[w]) * ( + delta[w])
w != s:
betweenness[w] += delta[w]
v betweenness:
betweenness[v] /= ((n - ) * (n - ) / ) n >
betweenness
():
distances = {v: () v graph.adjacency}
distances[start] =
queue = [start]
queue:
v = queue.pop()
w, _ graph.adjacency[v]:
distances[w] == ():
distances[w] = distances[v] +
queue.append(w)
reachable = [d d distances.values() d != ()]
reachable:
(reachable) / ((reachable) * (reachable))
():
labels = {v: v v graph.adjacency}
_ (max_iterations):
updated =
order = (graph.adjacency.keys())
np.random.shuffle(order)
v order:
neighbor_labels = [labels[n] n, _ graph.adjacency[v]]
neighbor_labels:
most_common = ((neighbor_labels), key=neighbor_labels.count)
labels[v] != most_common:
labels[v] = most_common
updated =
updated:
communities = defaultdict()
v, label labels.items():
communities[label].append(v)
(communities.values())
gc = Graph()
edges = [(, ), (, ), (, ), (, ), (, ), (, ), (, ), (, )]
u, v edges:
gc.add_edge(u, v)
()
()
communities = label_propagation(gc)
()
Best Practices
- Choose appropriate graph representation: adjacency lists for sparse graphs, matrices for dense graphs.
- When implementing BFS, use collections.deque for O(1) popleft operations.
- Dijkstra's algorithm requires non-negative edge weights; use Bellman-Ford for negative weights.
- Always check for negative cycles in Bellman-Ford before using distances.
- For max-flow, Dinic's algorithm is preferred over Edmonds-Karp for better time complexity.
- Graph coloring is NP-hard; greedy algorithms provide approximate solutions suitable for most applications.
- Use union-find (disjoint set) for efficient connected component detection.
- When dealing with large graphs, consider space-efficient representations and streaming algorithms.
- For betweenness centrality on large graphs, use approximation with random sampling.
- Validate graph inputs for self-loops and parallel edges based on problem requirements.