DSA DSA In Depth
0%
lesson bridgesarticulation-pointstarjan

Undirected Connectivity: Tarjan's tin/low Algorithm for Bridges, Cut Vertices & BCCs

7 min read Module 5.1: Graph Representations, Traversals, Connectivity & Topological Order Updated Oct 23, 2026
📄 Cheat Sheet
Bridges and Articulation Points Algorithm | Graph Theory
William Fiset Bridges and Articulation Points Algorithm | Graph Theory Watch on YouTube ↗

Visualizes DFS discovery timestamps (tin) and low-link values (low) to isolate single points of failure in undirected networks.

0:00 / --:--

The Low-Link Function & Cut Conditions

During a DFS of an undirected graph $G = (V, E)$, let $\text{tin}[u]$ be the discovery timestamp of vertex $u$. We define the low-link value $\text{low}[u]$ as the minimum discovery time $\text{tin}[w]$ of any vertex $w$ reachable from $u$ using zero or more DFS tree edges downward followed by at most one back edge upward:

$$\text{low}[u] = \min \begin{cases} \text{tin}[u] \\ \text{tin}[p] & \text{for all back edges } (u, p) \text{ (excluding the parent tree edge)} \\ \text{low}[v] & \text{for all DFS tree children } v \text{ of } u \end{cases}$$
Formal Cut Theorems — Bridges vs. Articulation Points

Let $(u, v)$ be a DFS tree edge where $u$ is the parent of $v$ ($\text{tin}[u] < \text{tin}[v]$):

  1. Bridge Condition (Strict Inequality): Tree edge $(u, v)$ is a bridge if and only if: $$\text{low}[v] > \text{tin}[u]$$ If $\text{low}[v] == \text{tin}[u]$, a back edge from $v$'s subtree reaches $u$ itself, forming a cycle that protects edge $(u, v)$ from being a bridge.
  2. Articulation Point Condition (Weak Inequality + Root Rule): A non-root vertex $u$ is an articulation point if and only if it has at least one DFS tree child $v$ satisfying: $$\text{low}[v] \ge \text{tin}[u]$$ Even if a back edge from $v$'s subtree reaches $u$ ($\text{low}[v] == \text{tin}[u]$), deleting vertex $u$ destroys both the tree edge $(u, v)$ and that back edge endpoint, isolating $v$'s subtree! Meanwhile, a DFS root $r$ is an articulation point if and only if $r$ has $\ge 2$ children in the DFS tree.

Biconnected Components (BCCs) & Block-Cut Trees

A maximal subgraph with no articulation points is called a Biconnected Component (2-vertex-connected component), and a maximal subgraph with no bridges is a 2-Edge-Connected Component. By pushing traversed edges onto an auxiliary stack during DFS and popping all edges down to $(u, v)$ whenever $\text{low}[v] \ge \text{tin}[u]$, Tarjan's algorithm partitions the edge set $E$ into biconnected components in $O(V + E)$ time. Contracting each 2-edge-connected component into a supernode yields the Bridge-Block Tree, turning cyclic graphs into trees where path queries can be answered via Lowest Common Ancestor (LCA).

Bridge (low[v] > tin[u]) vs. Articulation Point (low[v] ≥ tin[u]) BRIDGE (low[3]=4 > tin[2]=3) 0(1/1) 1(2/1) 2(3/1) 3(4/4) 4(5/4) 5(6/4) Label: u(tin/low) Cut Vertices: 2, 3 Bridge Edge: (2, 3)
flowchart TD Visit["DFS at u: tin[u] = low[u] = ++timer"] --> Edge["Inspect incident edge e = (u, v)"] Edge --> IsParent{"Is e the parent edge?"} IsParent -- "Yes" --> Skip["Ignore parent edge (handle multigraphs by edge ID!)"] IsParent -- "No" --> Visited{"Is v already visited?"} Visited -- "Yes (Back Edge)" --> Back["low[u] = min(low[u], tin[v])"] Visited -- "No (Tree Edge)" --> Recurse["DFS(v); low[u] = min(low[u], low[v])"] Recurse --> CutCheck["Bridge if low[v] > tin[u]; Cut-Vertex if low[v] >= tin[u]"]
0-1 BFS Deque, DFS Edge Classification & Tarjan tin/low Bridges & SCCs
O(V + E) Single-Pass Traversal

Step through 0-1 BFS deque relaxation (push_front for w=0, push_back for w=1), Tarjan’s undirected bridge/articulation criterion (low[v] > tin[u] / low[v] ≥ tin[u]), and Tarjan’s single-pass SCC condensation.

Step 1 / 9
Custom Input:
Timeline

Invariant & State

DFS reaches sink v5 (tin=6, low=6). Since low[v5] == tin[v5], pop v5 as SCC #1 = {v5} in reverse topological order.
back cross-SCC back v0 1/1 v1 2/1 v2 3/1 v3 4/4 v4 5/4 v5 6/6 ROOT
Active Tarjan Stack
[v0, v1, v2, v3, v4]
Extracted SCCs
SCC #1: {v5}
Root Invariant
low[u] == tin[u]
[Step 1] DFS reaches sink v5 (tin=6, low=6). Since low[v5] == tin[v5], pop v5 as SCC #1 = {v5} in reverse topological order.
Hardware Reality — Single-Pass Post-Order Aggregation

Because $\text{low}[u]$ depends only on $\text{tin}[v]$ of back-edge neighbors and $\text{low}[v]$ of tree children, both arrays $\text{tin}[0..V-1]$ and $\text{low}[0..V-1]$ occupy just $8V$ bytes ($8\text{ MB}$ for $V = 10^6$) and update in-place during DFS return without auxiliary heap structures.

Complexity Matrix

Problem Naive Deletion Tarjan's DFS Auxiliary Space
All Bridges (Cut-Edges) O(E · (V + E)) O(V + E) O(V)
All Articulation Points O(V · (V + E)) O(V + E) O(V)
Biconnected Components (BCC) O(V · (V + E)) O(V + E) O(V + E)

Production Implementation: Multigraph-Safe Tarjan Bridges & Cut Vertices

#include <vector>
#include <algorithm>
#include <utility>

struct UndirectedConnectivity {
    struct Edge { int to; int id; };
    int n, timer = 0;
    std::vector<std::vector<Edge>> adj;
    std::vector<int> tin, low;
    std::vector<uint8_t> isCutVertex;
    std::vector<std::pair<int, int>> bridges;

    explicit UndirectedConnectivity(int vertices)
        : n(vertices), adj(vertices), tin(vertices, 0), low(vertices, 0), isCutVertex(vertices, 0) {}

    void addEdge(int u, int v, int id) {
        adj[u].push_back({v, id});
        adj[v].push_back({u, id});
    }

    void dfs(int u, int parentEdgeId) {
        tin[u] = low[u] = ++timer;
        int treeChildren = 0;

        for (const auto& e : adj[u]) {
            if (e.id == parentEdgeId) continue; // Safe even with parallel edges!
            if (tin[e.to] != 0) {
                // Back edge to ancestor
                low[u] = std::min(low[u], tin[e.to]);
            } else {
                // Tree edge
                ++treeChildren;
                dfs(e.to, e.id);
                low[u] = std::min(low[u], low[e.to]);

                if (low[e.to] > tin[u]) {
                    bridges.emplace_back(u, e.to);
                }
                if (parentEdgeId != -1 && low[e.to] >= tin[u]) {
                    isCutVertex[u] = 1;
                }
            }
        }
        if (parentEdgeId == -1 && treeChildren > 1) {
            isCutVertex[u] = 1;
        }
    }

    void solve() {
        for (int i = 0; i < n; ++i) {
            if (tin[i] == 0) dfs(i, -1);
        }
    }
};
Failure Mode — Parallel Edges & Using low[v] Instead of tin[v] on Back Edges

Two subtle bugs plague Tarjan implementations: (1) Skipping the parent via if (v == parentVertex) continue; fails on multigraphs where two parallel edges connect $u$ and $v$—neither parallel edge is a bridge, so always pass parentEdgeId rather than parentVertex! (2) On a back edge $(u, v)$, updating low[u] = min(low[u], low[v]) instead of tin[v] breaks articulation point detection because $\text{low}$ is defined using at most one back edge without bypassing $v$ through a second back edge.

Check yourself Question 1 of 4
Concept Check Bridge vs Articulation Point Inequality recall
Section: Cut Conditions

In Tarjan's undirected DFS algorithm where $(u, v)$ is a DFS tree edge ($u$ is the parent of $v$), what are the exact inequalities for $(u, v)$ being a **bridge** vs. a non-root $u$ being an **articulation point**?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

When DFS at vertex $u$ encounters a back edge $(u, v)$ to an ancestor $v$, suppose you write `low[u] = min(low[u], low[v])` instead of `low[u] = min(low[u], tin[v])`. Which algorithm breaks on a figure-eight graph (two cycles sharing a single cut vertex $v$)?

Key Takeaways

low[u] tracks the minimum discovery timestamp tin[w] reachable from u's DFS subtree using at most one back edge.
A DFS tree edge (u, v) is a bridge if and only if low[v] > tin[u]; a non-root vertex u is an articulation point if and only if it has a tree child v with low[v] >= tin[u].
The root of a DFS tree is an articulation point if and only if it has at least two DFS tree children.
Always filter out the immediate parent transition by edge ID (e.id == parentEdgeId) rather than vertex ID so parallel edges in multigraphs are handled accurately.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue