Algorithmics study and revision notes
2026-08-22
A graph represents entities and relationships without requiring a hierarchy or linear order. Roads connect places, links connect web pages, reactions connect molecules, and dependencies connect tasks. The same abstract model supports very different questions: Is a target reachable? What is the cheapest route? Which links connect every vertex cheaply? Which groups circulate strongly among themselves? How much material can a network carry?
Graph algorithms are only as meaningful as the model. Direction, weights, parallel edges, self-loops, and the meaning of an absent edge must be settled before selecting an algorithm.
After studying this note, you should be able to:
The note proceeds in layers. First fix the graph model and representation; then use frontier order to derive traversal and structural algorithms. Weighted optimisation adds safe-edge or relaxation arguments. The final sections reinterpret edges as stochastic flow or residual capacity. Keeping those proof ideas separate makes the large catalogue easier to navigate.
For a first pass, treat representations, BFS, DFS, SCCs, MSTs, shortest paths, and maximum flow as the core chain. Then return to PageRank, MCL, distance indexes, and community methods as extensions that introduce additional models and objectives.
A graph is , with vertices and edges .
A walk follows adjacent edges and may repeat vertices. A path is normally taken to have no repeated vertices. A cycle returns to its start. In an undirected graph, a connected component is a maximal mutually reachable vertex set. In a directed graph:
The degree of an undirected vertex counts incident edges. A directed vertex has indegree and outdegree. State whether an undirected edge is stored once or twice and whether a self-loop contributes one or two to degree.
An adjacency matrix uses cells and tests an edge in . It is suitable for dense graphs and matrix algorithms. A distinguished value must separate “no edge” from a legitimate zero-weight edge.
An adjacency list stores one neighbour record per directed edge, or two per undirected edge. It uses space and enumerates neighbours of in time. Hashing or sorting a neighbour list can accelerate edge tests at additional cost.
An edge list uses records and is convenient when algorithms scan or sort all edges, as Kruskal’s algorithm does.
Choose a representation according to the operations, not merely the input format. With adjacency lists,
which is why a complete adjacency scan is .
Many searches maintain discovered but not fully processed vertices in a frontier:
The container changes the order, but three correctness rules recur:
Priority searches distinguish discovered from settled. Dijkstra and A* may improve a discovered vertex’s key before it is permanently settled.
BFS explores an unweighted graph in nondecreasing number of edges from a source.
BFS(G, s):
for each v: distance[v] = infinity; parent[v] = none
distance[s] = 0
Q = FIFO queue containing s
while Q is not empty:
u = Q.pop_front()
for each v in Adj[u]:
if distance[v] == infinity:
distance[v] = distance[u] + 1
parent[v] = u
Q.push_back(v)
Layer invariant. When a vertex of distance is removed, all vertices already removed have distance at most , and every queued vertex has distance or . Any path to an undiscovered neighbour through has length . A shorter path would have discovered from an earlier layer. Thus first discovery assigns the shortest unweighted distance.
Parent pointers form a BFS tree for the reachable component and reconstruct a shortest path by walking backward from a target. To traverse a disconnected graph, start another BFS from every still-undiscovered vertex.
With adjacency lists, each vertex enters once and every stored edge record is inspected once, giving time and auxiliary space.
DFS fully explores one branch before returning. Colour vertices white (unseen), gray (active), and black (finished).
DFS_visit(u):
colour[u] = gray
discovery_time[u] = next_time()
for each v in Adj[u]:
if colour[v] == white:
parent[v] = u
DFS_visit(v)
else:
classify or process edge (u, v)
colour[u] = black
finish_time[u] = next_time()
Run this visit from every remaining white vertex to obtain a DFS forest. Recursive calls are nested, so discovery/finish intervals are either disjoint or one contains the other.
In a directed graph, an edge to a gray ancestor is a back edge and proves a directed cycle. Conversely, if a directed cycle exists, DFS encounters a back edge when it follows the cycle from its first discovered vertex.
In a simple undirected graph, ignore the edge back to the immediate parent; an edge to any other visited vertex proves a cycle. In a multigraph, track edge identities and ignore only the exact tree edge: a second parallel edge to the parent forms a two-edge cycle. A self-loop is a cycle as soon as it is seen.
A directed acyclic graph (DAG) admits a topological ordering in which every edge places before . Reverse DFS finish order gives one:
topological_sort(G):
run DFS, rejecting any back edge
return vertices in decreasing finish time
For any DAG edge , DFS either visits below , so finishes first, or has already finished. It cannot be gray because that would be a cycle. Hence and reversed finish order is topological. Kahn’s alternative repeatedly removes an indegree-zero vertex; processing fewer than vertices reveals a cycle.
Contracting every SCC to one vertex produces the condensation graph. It must be a DAG: a directed cycle among components would make all vertices on that cycle mutually reachable and therefore one SCC.
Kosaraju’s algorithm computes SCCs in linear time:
SCC(G):
order = decreasing_finish_order(DFS(G))
return DFS_forest(transpose(G), order)
The first pass orders source/sink relationships between components. A component with latest remaining finish time is a source in the remaining condensation graph of and therefore a sink after transposition. Starting there reaches its own component but cannot escape to an unprocessed one. Induction gives one SCC per second-pass tree.
Both passes and transposition take . Tarjan’s algorithm obtains the same partition in one DFS using discovery indices, a stack, and low-link values.
For a connected undirected weighted graph, a spanning tree connects all vertices with exactly edges. A minimum spanning tree (MST) minimises their total weight. It need not minimise path distances from a source, and equal weights may permit several MSTs.
The core correctness fact is the cut property:
If a cut respects the already chosen forest, then a lightest edge crossing that cut is safe: it belongs to some MST containing the chosen edges.
To prove it, take an MST containing the chosen forest. If already contains the light edge , nothing is needed. Otherwise add to , creating a cycle. That cycle contains another crossing edge . Replacing by preserves a spanning tree and cannot increase weight, so the new tree is also minimum and contains .
Kruskal grows a forest:
Kruskal(G):
make_set(v) for every vertex v
A = empty set
for each edge (u, v) in nondecreasing weight:
if find(u) != find(v):
add (u, v) to A
union(u, v)
return A
Each chosen edge is the lightest crossing between two current components. Sorting dominates at ; for a simple graph this is . Union-find adds near-linear time.
Prim grows one tree. For every outside vertex , maintain the lightest known edge from the current tree to as its priority-queue key. Extracting the smallest key selects a light edge across the current tree cut; relax incident edges with decrease-key. A binary heap gives , commonly written for a connected graph. A dense matrix implementation takes .
On a disconnected graph these algorithms produce a minimum spanning forest. Negative edge weights cause no problem; the cut proof does not require nonnegative weights.
The weight of a walk or path is the sum of its edge weights. Shortest-path algorithms conventionally minimise over source-to-target walks, although a finite optimum can be represented by a path: any repeated cycle that does not lower the cost can be removed.
Let be the infimum weight of an -to- walk, or infinity when is unreachable. If no relevant negative cycle exists, this infimum is attained by a path. If a negative-weight cycle is reachable from and can reach , then : traversing the cycle repeatedly makes walks arbitrarily cheap.
Shortest-path algorithms maintain estimates , initially and all others infinity. Relaxing tests
If true, set and . Every finite estimate is the weight of an actual discovered walk, so it is an upper bound on the true shortest distance. Different algorithms arrange enough relaxations in an order that makes those bounds final.
Topologically order a DAG and relax every outgoing edge in that order. Every predecessor of a vertex is processed first, so its final distance is known when needed. This permits negative edges and takes .
For nonnegative edges, store unsettled vertices in a min-priority queue keyed by :
Dijkstra(G, s):
initialise d and parent; put s in a min-priority queue
while the queue is not empty:
u = extract_min()
if u was already settled: continue
mark u settled
for each edge (u, v):
if d[u] + w(u, v) < d[v]:
update d[v] and parent[v]
insert or decrease_key(v)
Settled invariant. When is extracted, . If a cheaper path existed, consider the first unsettled vertex on that path and its settled predecessor . Relaxing would give no larger than the path prefix, and nonnegative remaining edges make that at most the supposed cheaper distance to . Then could not have the minimum queue key.
A binary heap gives ; a Fibonacci heap gives amortised. Dijkstra is invalid with negative edges because an extracted distance may later improve.
Bellman-Ford makes complete passes over the edge list. Any simple shortest path has at most edges, so after pass , every shortest path using at most edges has been propagated. A further improving relaxation identifies a negative cycle reachable from the source.
The time is and space is . Stop early if one full pass changes nothing. Bellman-Ford detects only negative cycles reachable from the chosen source unless an artificial source connects to every vertex.
A* searches for one target in a finite graph with nonnegative edge costs using
where is the best known source-to- cost and estimates the remaining cost. It expands the smallest .
A heuristic is admissible if , the true remaining cost; in particular, at a target . It is consistent if every edge satisfies
If A* tests for a goal when that state is removed from the priority queue, admissibility gives an optimal result when distinct paths are retained as distinct tree-search nodes. On a cyclic graph, such tree search also needs a termination condition, such as all edge costs being bounded below by a positive constant.
In the common graph-search implementation that merges equal states and permanently closes expanded vertices, consistency makes nondecreasing along paths and ensures closed distances are final. With an admissible but inconsistent heuristic, optimal graph search must be prepared to reopen a vertex whose improves. Setting reduces A* to Dijkstra.
An adjacency matrix can encode reachability over the Boolean semiring: multiplication becomes AND and addition becomes OR. Warshall’s transitive-closure algorithm asks whether a path exists using only the first vertices as internal vertices.
Floyd-Warshall applies the same intermediate-vertex idea to all-pairs shortest paths. Initialise with edge weights, zero on the diagonal, and infinity for absent edges. Then
Either an optimal permitted path avoids , or it reaches once and splits into two smaller permitted paths. The algorithm uses time and space, permits negative edges, and reveals a negative cycle if some final diagonal entry is negative.
Let be a row-stochastic transition matrix: is the probability of moving from to . A dangling vertex with no outgoing edges has no probability distribution, so first replace its row with a chosen distribution , often uniform.
For damping and a positive teleport distribution , PageRank is the probability vector satisfying
Power iteration repeatedly applies the right-hand side and renormalises. Dangling-row repair defines every transition; teleportation then prevents closed regions from trapping all mass and, under the usual positive choice, gives a unique well-behaved stationary ranking. State the row/column convention when implementing the equation.
The Markov Cluster Algorithm (MCL) uses random-walk flow for clustering. Commonly, first add self-loops to a nonnegative adjacency matrix and normalise every column, obtaining a column-stochastic matrix . Then alternate:
expansion: matrix powering, commonly , to propagate multi-step flow;
inflation: apply
independently in every column.
Inflation strengthens dominant within-region flow and suppresses weak cross-region flow. Repeat expansion and inflation until a stated convergence tolerance is met; implementations extract clusters from the resulting attractor/support pattern and prune tiny entries to keep matrices sparse. The parameter controls granularity. MCL means Markov clustering, not Monte Carlo clustering. Its column-stochastic convention is separate from the row-stochastic PageRank convention above.
A flow network is a directed graph with source , sink , and capacities . A feasible flow satisfies
and flow conservation at every vertex other than . Its value is net flow leaving .
The residual network records allowed changes. A used forward edge has residual capacity ; its reverse residual edge has capacity and permits undoing a previous decision.
Ford-Fulkerson repeats three steps: find an - residual path ; let be the minimum residual capacity on ; then augment units along , updating both forward and reverse residual edges.
With integral capacities, every augmentation increases flow value by at least one, so the method terminates; its running time can depend on the numeric maximum-flow value. With irrational capacities and arbitrary path choices, it need not terminate. Edmonds-Karp always chooses a shortest residual path by BFS and runs in .
For any cut with and , flow value is at most the capacity of edges from to . When no residual path exists, let be the vertices reachable from in the residual graph. Every forward cut edge is saturated and every reverse contribution is zero, so the current flow value equals that cut capacity. The flow and cut therefore certify each other’s optimality: this is the max-flow/min-cut theorem.
Contracting vertices can change internal bottlenecks. In particular, summing all capacities between two SCCs does not generally preserve maximum flow; a component’s entrances and exits may be connected through limited internal capacity.
For repeated distance queries in a large undirected graph, precompute distances to selected landmarks. Triangle inequalities give lower bounds such as
and upper bounds through . Landmark bounds can guide A* or avoid a full search for approximate queries. Preprocessing time, storage, landmark selection, updates, and approximation error must all be measured.
Community detection asks for densely related groups, but “community” needs an objective. Girvan-Newman removes high-betweenness edges; modularity-based methods compare within-group edges with a degree-preserving null model; Louvain greedily aggregates modularity-improving groups. These are modelling and optimisation methods, not replacements for the exact definition of an SCC.
Consider directed edges
Initial estimates are and infinity elsewhere.
The direct-looking route weighs 6. Dijkstra commits vertices in increasing final distance, not by the number of edges or by the locally smallest outgoing edge.
| Problem | Method | Time with usual representation | Required condition |
|---|---|---|---|
| Unweighted distances | BFS | unit edge cost | |
| DFS forest / cycle test | DFS | none | |
| Topological order | DFS or Kahn | graph must be a DAG | |
| SCCs | Kosaraju or Tarjan | directed graph | |
| MST | Kruskal / Prim | / | undirected weighted graph |
| Nonnegative SSSP | Dijkstra | nonnegative edges | |
| Goal-directed SSSP | A* | worst case with a binary heap | nonnegative edges; consistent heuristic for permanent closing |
| General SSSP | Bellman-Ford | reports reachable negative cycle | |
| All-pairs shortest paths | Floyd-Warshall | no negative cycle for finite answers | |
| PageRank | power iteration | per sparse iteration | repaired transitions, damping, and tolerance |
| MCL | sparse expansion and inflation | data-dependent; fill-in may dominate | nonnegative matrix, pruning, and tolerance |
| Maximum flow | Edmonds-Karp | nonnegative capacities |
Graph may shift indices; use
StableGraph when external indices must survive
deletion.