Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree
You are given a weighted, undirected, connected graph of n nodes and an array edges where edgesi = [ai, bi, weighti] represents an edge, identified by its index i in the array. A critical edge is one whose removal increases the minimum spanning tree weight (or disconnects the graph). A pseudo-critical edge is one that isn't critical, but can still be part of some MST of minimum weight. Return [critical, pseudo-critical], both lists of edge indices, in any order within each list. For every edge, run Kruskal's algorithm twice more: once excluding that edge, and once forcing it in before sorting the rest.
Constraints
- 2 ≤ n ≤ 100
- 1 ≤ edges.length ≤ min(200, n * (n - 1) / 2)
- edgesi.length == 3
- 0 ≤ ai < bi < n
- 1 ≤ weighti ≤ 1000
- All pairs (ai, bi) are distinct
Example
n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]][[0,1],[2,3,4,5]]Explanation The base MST weighs 7. Removing edge 0 or edge 1 forces a more expensive tree, so both are critical. Edges 2, 3, 4, and 5 can each be forced into a weight-7 tree without being required.
In plain terms
- Critical edge
- An edge that every minimum spanning tree must use — remove it and the cheapest possible tree gets more expensive, or the graph falls apart.
- Pseudo-critical edge
- An edge that isn't required, but there exists at least one minimum-weight spanning tree that uses it.
For each edge: rerun Kruskal excluding it, then rerun forcing it in first
Edge 0 (0–1, weight 1): exclude it and rerun Kruskal — the cheapest tree now costs 8. Critical.
What happens in this step
exclude edge 0 (0–1, w=1); rerun Kruskal on the rest
sorted remaining edges: (1,2,w=1), (2,3,w=2), (0,3,w=2), (0,4,w=3), (3,4,w=3), (1,4,w=6)
accept (1,2,w=1) → weight=1
accept (2,3,w=2) → weight=3, group {1,2,3}
(0,3,w=2): find(0)=0, find(3)=root{1,2,3} → different → accept → weight=5, group {0,1,2,3}
(0,4,w=3): different roots → accept → weight=8, 4 edges used (n-1) → MST complete
8 > base MST (7): removing edge 0 forces a costlier tree, so it is critical.Steps to visualize
- Run Kruskal normally once to find the base minimum spanning tree weight.
- For each edge, remove it from consideration and rerun Kruskal — a higher weight (or a disconnected graph) means it is critical.
- For every non-critical edge, force it in first, union it, then run Kruskal on the rest — matching the base weight means it is pseudo-critical.
- Every other edge is neither: it is never required, and never part of a minimum-weight tree either.
Walk through the code
Same walkthrough, now with the code. Press Next to move one step and watch which lines run.
Edge 0 (0–1, weight 1): exclude it and rerun Kruskal — the cheapest tree now costs 8. Critical.
What happens in this step
exclude edge 0 (0–1, w=1); rerun Kruskal on the rest
sorted remaining edges: (1,2,w=1), (2,3,w=2), (0,3,w=2), (0,4,w=3), (3,4,w=3), (1,4,w=6)
accept (1,2,w=1) → weight=1
accept (2,3,w=2) → weight=3, group {1,2,3}
(0,3,w=2): find(0)=0, find(3)=root{1,2,3} → different → accept → weight=5, group {0,1,2,3}
(0,4,w=3): different roots → accept → weight=8, 4 edges used (n-1) → MST complete
8 > base MST (7): removing edge 0 forces a costlier tree, so it is critical.Solution
function findCriticalAndPseudoCriticalEdges(n, edges) {
const indexed = edges.map((e, i) => [e[0], e[1], e[2], i]);
function mstWeight(skipIndex, forceIndex) {
const parent = Array.from({ length: n }, (_, i) => i);
function find(x) {
while (parent[x] !== x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
function union(a, b) {
const rootA = find(a);
const rootB = find(b);
if (rootA === rootB) return false;
parent[rootA] = rootB;
return true;
}
let weight = 0;
let count = 0;
if (forceIndex !== -1) {
const [a, b, w] = indexed[forceIndex];
union(a, b);
weight += w;
count++;
}
const sorted = [...indexed].sort((x, y) => x[2] - y[2]);
for (const [a, b, w, idx] of sorted) {
if (idx === skipIndex || idx === forceIndex) continue;
if (union(a, b)) {
weight += w;
count++;
}
}
if (count !== n - 1) return Infinity;
return weight;
}
const baseWeight = mstWeight(-1, -1);
const critical = [];
const pseudoCritical = [];
for (let i = 0; i < edges.length; i++) {
if (mstWeight(i, -1) > baseWeight) {
critical.push(i);
} else if (mstWeight(-1, i) === baseWeight) {
pseudoCritical.push(i);
}
}
return [critical, pseudoCritical];
}- Time
- O(m² α(n)) — m reruns of Kruskal, each O(m log m)
- Space
- O(n + m)
Test cases
| Input | Expected | Covers |
|---|---|---|
n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]] | [[0,1],[2,3,4,5]] | example from the docstring |
n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]] | [[],[0,1,2,3]] | a 4-cycle of equal weights — no edge is required, but every edge could be used |
n = 3, edges = [[0,1,1],[1,2,1],[0,2,1]] | [[],[0,1,2]] | a triangle of equal weights — any two edges form a minimum tree |
n = 4, edges = [[0,1,5],[2,3,5],[1,2,1]] | [[0,1,2],[]] | already a tree with no redundancy — every edge is required |
n = 2, edges = [[0,1,10]] | [[0],[]] | smallest valid input, the only edge is necessarily critical |
n = 4, edges = [[0,1,1],[0,2,1],[0,3,1],[1,2,2]] | [[0,1,2],[]] | an extra pricier edge that never belongs to any minimum tree, critical or not |