Algorithm Comparison Analysis
Algorithm Comparison Analysis
& ANALYSIS
A Comprehensive Technical Reference
MST · Shortest Path · String Matching · Greedy Algorithms
TABLE OF CONTENTS
■ Key Insight: Every connected weighted graph has at least one MST. If all edge weights are
distinct, the MST is unique.
■ Step-by-Step Explanation
■ Step 1: Initialize — pick any starting vertex. Set its key (cost) to 0, all others to ∞.
■ Step 2: Insert all vertices into a min-priority queue keyed by their distance to the MST.
■ Step 3: Extract the vertex u with the minimum key from the queue.
■ Step 4: For each neighbour v of u: if v is still in the queue and weight(u,v) < key[v], update key[v] =
weight(u,v) and parent[v] = u.
■ Step 5: Repeat Steps 3–4 until the queue is empty.
■ Step 6: The MST edges are {(parent[v], v)} for all v ≠ source.
■ Pseudocode
PRIM-MST(G, w, r): // G=graph, w=weight fn, r=root
for each u ∈ G.V: // Initialise
key[u] = ∞ // min edge weight to reach u
parent[u] = NIL
inMST[u] = false
key[r] = 0 // source vertex cost = 0
Q = MIN-PRIORITY-QUEUE(G.V) // enqueue all vertices
while Q is not empty:
u = EXTRACT-MIN(Q) // vertex with smallest key
inMST[u] = true
for each v ∈ Adj[u]: // explore neighbours
if inMST[v] == false AND w(u,v) < key[v]:
parent[v] = u
key[v] = w(u,v) // decrease-key in Q
DECREASE-KEY(Q, v, key[v])
return {(parent[v], v) : v ∈ V \ {r}} // MST edge set
■ Exam Tip: For dense graphs (E ≈ V²), the simple O(V²) array implementation outperforms the heap-based
version. For sparse graphs use binary heap → O(E log V).
Works well with adjacency matrix rep. Requires connected graph to start
Only one component grows — simpler tracking Not suitable for disconnected graphs directly
■ When to Use
• Graph is dense (many edges relative to vertices).
• Stored as an adjacency matrix.
• Need the MST rooted at a specific vertex.
• When memory is limited (only one growing component).
■ Real-World Applications
• Network design — laying cables, roads, or pipelines at minimum cost.
• Cluster analysis — grouping data points with minimum intra-cluster distance.
• Circuit design — minimizing wire length on PCBs.
• Image segmentation — graph-cut algorithms.
2 B (key=2) A=0, B=2, C=1, D=4, E=∞ C←B, D←B (A,B) w=2
3 C (key=1) A=0, B=2, C=1, D=4, E=6 D stays B, E←C (B,C) w=1
MST Edges: (A,B)=2, (B,C)=1, (B,D)=4, (C,E)=6 → Total MST Weight = 2+1+4+6 = 13
■ Summary: Prim's grows a single tree from a seed vertex by always adding the cheapest crossing edge.
Complexity: O(V²) with array, O(E log V) with binary heap. Best for dense graphs.
■ Step-by-Step Explanation
■ Step 1: Sort all E edges by weight in ascending order.
■ Step 2: Initialize V disjoint sets (each vertex is its own component).
■ Step 3: For each edge (u, v) in sorted order:
■ If FIND(u) ≠ FIND(v) — endpoints in different sets — add edge to MST and UNION(u, v).
■ Else — skip (would create a cycle).
■ Step 4: Stop when MST has V-1 edges.
■ Pseudocode
KRUSKAL-MST(G, w): // G=graph, w=weight function
MST = {} // empty edge set
for each vertex v ∈ G.V: // DSU init
MAKE-SET(v)
edges = SORT(G.E, key=w) // sort edges ascending
for each edge (u, v) ∈ edges:
if FIND(u) ≠ FIND(v): // no cycle?
MST = MST ∪ {(u, v)}
UNION(u, v)
if |MST| == |G.V| - 1:
break // MST complete
return MST
UNION(x, y):
rx, ry = FIND(x), FIND(y)
if rank[rx] < rank[ry]: parent[rx] = ry
elif rank[rx] > rank[ry]: parent[ry] = rx
else: parent[ry] = rx; rank[rx]++
Operation Complexity
DSU operations (with path compress + union by rank) O(α(V)) ≈ O(1) amortised per op
Works on disconnected graphs (gives a forest) All edges must be known upfront
Easy to parallelize (sort + DSU) DSU with path compression is tricky to code
■ When to Use
• Graph is sparse (E ■ V²).
• Stored as an edge list.
• Graph may be disconnected (produces minimum spanning forest).
• Simpler implementation is preferred.
MST Edges: B-C=1, A-B=2, B-D=4, C-E=6 → Total = 13 (same result as Prim's ✓)
■ Summary: Kruskal's sorts all edges and greedily adds cheapest non-cycle-forming edges using DSU.
Complexity: O(E log E). Best for sparse graphs or when edge list is given.
Data Structure Priority queue + key[] + parent[] Edge list + DSU (Union-Find)
Graph Type Best for dense graphs Best for sparse graphs
Handles Disconnected? No — needs connected graph Yes — gives minimum spanning forest
Cycle Detection Implicit (only connects outside vertices) Explicit via DSU
■ Exam Tip: Both algorithms ALWAYS produce the same total MST weight. The difference is in approach and
efficiency. Prim's = O(E log V) with heap; Kruskal's = O(E log E). Since log E ≤ 2 log V, these are equivalent for
sparse graphs.
■ Step-by-Step Explanation
■ Step 1: Initialise dist[s] = 0 for source s; dist[v] = ∞ for all others.
■ Step 2: Insert all vertices into a min-priority queue keyed by dist[].
■ Step 3: Extract vertex u with minimum dist.
■ Step 4: For each neighbour v of u: if dist[u] + w(u,v) < dist[v] → update dist[v] (relaxation).
■ Step 5: Repeat until queue is empty or destination is reached.
■ Pseudocode
DIJKSTRA(G, w, s): // s = source vertex
for each v ∈ G.V:
dist[v] = ∞
prev[v] = NIL
dist[s] = 0
Q = MIN-PRIORITY-QUEUE(G.V, key=dist) // all vertices
while Q is not empty:
u = EXTRACT-MIN(Q)
for each v ∈ Adj[u]: // RELAX
if dist[u] + w(u,v) < dist[v]:
dist[v] = dist[u] + w(u,v)
prev[v] = u
DECREASE-KEY(Q, v, dist[v])
return dist[], prev[]
■ Complexity Analysis
Implementation Time Complexity Space
■ Limitations
• Cannot handle negative edge weights — correctness breaks.
• Finds single-source shortest paths only (not all-pairs).
• On very dense graphs, O(V²) implementation is needed.
■ Solved Example
Graph: S→A=4, S→B=2, A→C=3, B→A=1, B→C=5, C→D=2. Find shortest paths from S.
Init — 0 ∞ ∞ ∞ ∞
1 S (0) 0 4 2 ∞ ∞
2 B (2) 0 3 2 7 ∞
3 A (3) 0 3 2 6 ∞
4 C (6) 0 3 2 6 8
5 D (8) 0 3 2 6 8
■ Summary: Dijkstra's: greedy SSSP, non-negative weights only. O(E+V log V) with Fibonacci heap.
Optimal for routing in road/network maps.
■ Pseudocode
BELLMAN-FORD(G, w, s): // s = source
for each v ∈ G.V: // init
■ Solved Example
Graph (directed): S→A=6, S→B=7, A→B=8, A→C=-4, B→C=5, B→D=-3, C→S=2, D→C=7, D→A=-2. Source =
S. Run Bellman-Ford with 4 vertices {S,A,B,C,D... actually use 5}.
Simplified 4-node example: Nodes {S, A, B, C}. Edges: S→A=4, S→B=5, A→C=-3, B→C=1. Find shortest
paths from S.
Init — 0 ∞ ∞ ∞
1 S→A=4 0 4 ∞ ∞
1 S→B=5 0 4 5 ∞
1 A→C=-3 0 4 5 1
■ Summary: Bellman-Ford: handles negative weights, detects negative cycles. O(VE). Use when Dijkstra
fails (negative edges). Slower but robust.
■ Pseudocode
FLOYD-WARSHALL(W): // W = weight matrix (V×V)
n = |W| // number of vertices
dist = copy of W // dist[i][j] = direct edge weight
// dist[i][i] = 0; dist[i][j] = ∞ if no direct edge
next = matrix (for path reconstruction), next[i][j] = j if edge exists
// Path Reconstruction
PATH(next, u, v):
if next[u][v] == NIL: return []
path = [u]
while u ≠ v: u = next[u][v]; [Link](u)
return path
■ Complexity
Property Value
■ Solved Example
Graph (4 nodes): 1→2=3, 1→3=∞, 1→4=7, 2→1=8, 2→3=2, 3→4=1, 4→3=6.
1 2 3 4
1 0 3 ∞ 7
2 8 0 2 ∞
3 ∞ ∞ 0 1
4 ∞ ∞ 6 0
Final Matrix:
1 2 3 4
1 0 3 5 6
2 8 0 2 3
3 ∞ ∞ 0 1
4 ∞ ∞ 6 0
■ Summary: Floyd-Warshall: All-pairs shortest path in O(V³). Handles negative edges. Detects negative
cycles via diagonal. Use when you need all pairwise distances.
Dense Graphs O(V²) array — efficient O(V³) — slow O(V³) — natural fit
Sparse Graphs O(E log V) — very fast O(VE) — moderate O(V³) — overkill
Output Dist from source + tree Dist from source Full distance matrix
Path Reconstruction Via prev[] array Via prev[] array Via next[][] matrix
Typical Use Cases GPS navigation, routing Financial arbitrage, OSPF Network analysis, all-pairs routing
■ Exam Tip: Key rules: (1) Negative weights → use Bellman-Ford or Floyd-Warshall. (2) All-pairs → use
Floyd-Warshall if V is small. (3) Non-negative + single source → Dijkstra. (4) Negative cycle detection →
Bellman-Ford (extra pass) or Floyd-Warshall (diagonal).
■ Working Principle
Rabin-Karp uses rolling hashing. It computes a hash of the pattern P, then slides a window of length m across
T, computing the hash of each substring in O(1) using a rolling update. When hashes match, it verifies
character-by-character (to handle hash collisions).
■ Pseudocode
RABIN-KARP(T, P, d, q): // T=text, P=pattern, d=base, q=prime mod
n = len(T); m = len(P)
h = d^(m-1) mod q // precompute d^(m-1) for rolling
p_hash = 0; t_hash = 0 // pattern & text window hashes
for i = 0 to m-1: // compute initial hashes
p_hash = (d * p_hash + P[i]) mod q
t_hash = (d * t_hash + T[i]) mod q
for i = 0 to n-m: // slide window
if p_hash == t_hash:
if T[i..i+m-1] == P: // verify (avoid spurious hits)
print('Match at', i)
if i < n-m: // rolling hash update
t_hash = (d*(t_hash - T[i]*h) + T[i+m]) mod q
if t_hash < 0: t_hash += q
■ Complexity Analysis
Case Time Note
Worst Case O(nm) All windows cause spurious hits (e.g., q=1)
■ Solved Example
Text T = "ABCABDABC", Pattern P = "ABC", d=31, q=101
p_hash = (65·31² + 66·31 + 67) mod 101 = (65·961 + 1986 + 67) mod 101 = (62465 + 2053) mod 101 = 64518
mod 101 = 38
■ Summary: Rabin-Karp: rolling hash reduces substring comparison to O(1) on average. Expected
O(n+m), worst O(nm). Great for multi-pattern search.
■ Working Principle
The Finite Automaton (FA) approach builds a Deterministic Finite Automaton (DFA) from the pattern P. Each
state represents how many characters of P have been matched so far. The automaton reads T character by
character; state m (accepting state) signals a match.
■ Pseudocode
// Phase 1: Preprocess — build transition table δ
COMPUTE-DELTA(P, Sigma): // Sigma = alphabet
m = len(P)
for q = 0 to m:
for each a ∈ Sigma:
k = min(m, q+1)
while k > 0 and P[0..k-1] != (P[0..q-1]+a)[end-k+1..end]:
k = k - 1 // find longest matching prefix
delta[q][a] = k
return delta
// Phase 2: Search
FA-MATCHER(T, delta, m): // m = pattern length
n = len(T); q = 0 // q = current state
for i = 0 to n-1:
q = delta[q][T[i]] // transition
if q == m: // accepting state
print('Match ending at', i)
■ Complexity Analysis
Phase Time Space
Preprocessing (build δ) O(m² · |Σ|) or O(m · |Σ|) with KMP suffix O(m · |Σ|)
■ Solved Example
Pattern P = "ABAB" (m=4), Alphabet Σ = {A, B}
1 1 2 Matched "A"
2 3 0 Matched "AB"
3 1 4 Matched "ABA"
■ Summary: FA Matching: O(n) search after O(m·|Σ|) preprocessing. No backtracking. Table size can be
large for big alphabets (e.g., Unicode).
■ Working Principle
KMP avoids redundant comparisons by precomputing a failure function (π) (also called the prefix function or
partial match table). π[i] = length of the longest proper prefix of P[0..i] that is also a suffix. On mismatch at
position j in P, instead of restarting, we shift to π[j-1] — reusing previously matched characters.
■ Pseudocode
// Phase 1: Compute Failure Function π
COMPUTE-PI(P):
m = len(P)
pi = array of size m, pi[0] = 0
k = 0 // length of current prefix
for q = 1 to m-1:
while k > 0 and P[k] != P[q]:
k = pi[k-1] // fall back
if P[k] == P[q]: k++
pi[q] = k
return pi
■ Complexity Analysis
Phase Time Space
■ Solved Example
Index i 0 1 2 3 4 5 6 7 8
P[i] A B A B C A B A B
π[i] 0 0 1 2 0 1 2 3 4
Explanation: π[2]=1 ("A" is both prefix and suffix of "ABA"); π[3]=2 ("AB" is both prefix/suffix of "ABAB"); π[8]=4
("ABAB" is both prefix/suffix of "ABABCABAB").
0 A 1 Match q=1
2 B 2 Match q=2
3 A 3 Match q=3
4 B 4 Match q=4
5 C 5 Match q=5
6 A 6 Match q=6
7 B 7 Match q=7
8 A 8 Match q=8
■ Summary: KMP: O(n+m) guaranteed via prefix/failure function. No backtracking on text. Best
general-purpose string matching. Widely used in text editors and DNA search.
Technique Brute force Rolling hash DFA transition table Failure/prefix function
Handles Multiple Patterns No (extend needed) Yes (hash all) With multi-DFA No (use Aho-Corasick)
Alphabet Dependency None None (hash) Yes — table size ∝ |Σ| Minimal
Practical Performance Poor (worst case) Very good (avg) Excellent Excellent
Best Use Case Tiny texts Multi-pattern, 2D Repeated search on same pattern
General single-pattern matching
■ Exam Tip: KMP and FA both achieve O(n+m) but KMP uses O(m) space vs O(m·|Σ|) for FA. FA is faster in
practice if the alphabet is small. Rabin-Karp shines for multi-pattern search (hash all patterns into a set, O(1)
lookup).
■ Problem Statement
Given n items, each with a value v[i] and weight w[i], and a knapsack of capacity W, maximize total value.
Unlike the 0/1 Knapsack, here you may take fractions of items (e.g., take 60% of item 3). This crucial
difference makes the greedy approach optimal.
■ Algorithm
■ Step 1: Compute ratio = v[i] / w[i] for each item.
■ Step 2: Sort items in descending order of ratio.
■ Step 3: Greedily fill the knapsack:
■ If current item fits entirely → take all of it (add full v[i]).
■ Else → take the fraction that fills remaining capacity.
■ Step 4: Return total value accumulated.
■ Pseudocode
FRACTIONAL-KNAPSACK(items, W): // items = [(v,w), ...], W = capacity
for each item: ratio[i] = v[i] / w[i] // compute ratios
SORT(items, key=ratio, descending=True) // O(n log n)
total_value = 0.0
remaining_cap = W
for each item (v, w) in sorted order:
if remaining_cap == 0: break // knapsack full
take = min(w, remaining_cap) // take as much as possible
total_value += take * (v / w) // add proportional value
remaining_cap -= take
return total_value
■ Complexity Analysis
Operation Complexity Note
Item 1 60 10 6.00
Item 4 80 40 2.00
Item 5 50 25 2.00
3rd Item 3 120 30 4.00 30+30=60 → over! Take 20/30 = 2/3 fraction
■ Note: If this were a 0/1 Knapsack (no fractions), the greedy solution would fail. For example, taking Item 3
(■120, 30kg) + Item 2 (■100, 20kg) = ■220 is suboptimal vs the DP solution. Always check: is the problem
fractional or 0/1?
■ Summary: Fractional Knapsack: sort by value/weight ratio, greedily fill. O(n log n). Greedy is OPTIMAL
here because items are divisible. 0/1 Knapsack requires O(nW) DP — greedy does NOT work there.
Prim's (heap) O(E log V) O(E log V) O(E log V) O(V) MST
Dijkstra (heap) O(E+V log V) O(E+V log V) O(E+V log V) O(V) SSSP
Fractional Knapsack O(n log n) O(n log n) O(n log n) O(1) Greedy
Find MST, dense graph, adj. matrix Prim's (array — O(V²)) Avoids priority queue overhead
Find MST, sparse graph, edge list Kruskal's Sorting + DSU is optimal for sparse E
SSSP, negative edge weights Bellman-Ford Only SSSP that handles negative weights
Single pattern, small alphabet Finite Automata O(n) search after preprocessing
Multiple patterns in text Rabin-Karp or Aho-Corasick Hash set lookup or multi-pattern DFA
Maximize value (divisible items) Fractional Knapsack (Greedy) Greedy is optimal for fractional
• MST Cut Property: For any cut of G, the minimum-weight crossing edge belongs to some MST.
• MST Cycle Property: For any cycle in G, the maximum-weight edge does NOT belong to any MST (if
unique).
• Dijkstra Correctness: When a vertex is extracted from the min-heap, its dist[] is finalized (holds only for
non-negative weights).
• Bellman-Ford Correctness: After V-1 relaxations, all shortest paths (without negative cycles) are correctly
computed.
• Floyd-Warshall Optimality: The optimal substructure — a shortest path's subpath is also a shortest path —
justifies the DP recurrence.
• KMP Linear Time: The text pointer i never decreases (only j falls back via π), guaranteeing O(n) for the
search phase.
• Greedy Knapsack Optimality: Proven by exchange argument — swapping any other choice for the
highest-ratio item cannot increase total value.
Greedy Make locally optimal choice; never reconsiderPrim's, Kruskal's, Dijkstra's, Fractional Knapsack
Divide & Conquer Split, solve subproblems recursively, combineNot covered here (Merge Sort, FFT)
Hashing Map data to integer fingerprint for O(1) lookupRabin-Karp (rolling hash)