Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Graph Connectivity Analysis: Articulation Points, Bridges, and Biconnected Components

Tech May 10 4

Fundamental Definitions

In graph theory, certain vertices and edges play a critical role in maintaining connectivity. This article explores four key concepts that help analyze network robustness.

Articulation Points (Cut Vertices)

An articulation point is a vertex whose removal (including all incident edges) increases the number of connceted components in the graph.

Bridges

A bridge is an edge whose removal disconnects the graph, splitting a connected component into two separate components.

Edge-Biconnected Components

An edge-biconnected component represents a maximal subgraph where no edge acts as a bridge. Within such a component, any two vertices remain connected even after removing any single edge.

Vertex-Biconnected Components

A vertex-biconnected component is a maximal subgraph that remains connected after removing any single vertex (and its incident edges). Unlike edge-biconnected components, these may share articulation points between them.

Algorithmic Approach Using Tarjan's Method

All four concepts can be efficiently identified using Tarjan's depth-first search algorithm, which tracks discovery times and low-link values for each vertex.

Identifying Articulation Points

The algorithm traverses the DFS tree, maintaining two key arrays:

  • discovery[v]: The timestamp when vertex v is first visited
  • lowlink[v]: The smallest discovery time reachable from v through zero or more tree edges followed by at most one back edge

The crucial insight: For a vertex u with child v in the DFS tree, if lowlink[v] >= discovery[u], then u is an articulation point. This condition indicates that v cannot reach any ancestor of u without passing through u itself.

Special handling applies to the root node: it becomes an articulation point only if it has at least two children in the DFS tree.

void findArticulation(int node, int parent) {
    discovery[node] = lowlink[node] = ++currentTimestamp;
    int childCount = 0;
    
    for (int neighbor : adjacency[node]) {
        if (discovery[neighbor] == 0) {
            childCount++;
            findArticulation(neighbor, node);
            lowlink[node] = min(lowlink[node], lowlink[neighbor]);
            
            if (parent != -1 && lowlink[neighbor] >= discovery[node]) {
                isArticulation[node] = true;
            }
        } else if (neighbor != parent) {
            lowlink[node] = min(lowlink[node], discovery[neighbor]);
        }
    }
    
    if (parent == -1 && childCount >= 2) {
        isArticulation[node] = true;
    }
}

Finding Bridges

The bridge detection algorithm follows a similar pattern. An edge (u, v) is a bridge if and only if lowlink[v] > discovery[u], meaning vertex v cannot reach any ancestor of u through alternative paths.

struct Edge {
    int source, destination;
};

void detectBridges(int current, int parent) {
    discovery[current] = lowlink[current] = ++timer;
    
    for (int next : graph[current]) {
        if (next == parent) continue;
        
        if (discovery[next] == 0) {
            detectBridges(next, current);
            
            if (discovery[current] < lowlink[next]) {
                bridges.push_back({min(current, next), max(current, next)});
            }
            
            lowlink[current] = min(lowlink[current], lowlink[next]);
        } else {
            lowlink[current] = min(lowlink[current], discovery[next]);
        }
    }
}

Computing Edge-Biconnected Components

Edge-biconnected components can be extracted by modifying the standard Tarjan algorithm. When lowlink[u] == discovery[u], all vertices above u in the recursion stack form one component. The algorithm marks bridge edges during traversal and later groups vertices avoiding these edges.

void findEdgeBiconnected(int vertex, int parentEdge) {
    discovery[vertex] = lowlink[vertex] = ++timestamp;
    componentStack.push(vertex);
    
    for (auto &[to, edgeId] : connections[vertex]) {
        if (edgeId == parentEdge) continue;
        
        if (discovery[to] == 0) {
            findEdgeBiconnected(to, edgeId);
            lowlink[vertex] = min(lowlink[vertex], lowlink[to]);
            
            if (lowlink[to] > discovery[vertex]) {
                isBridgeEdge[edgeId] = true;
            }
        } else {
            lowlink[vertex] = min(lowlink[vertex], discovery[to]);
        }
    }
    
    if (lowlink[vertex] == discovery[vertex]) {
        componentCount++;
        while (true) {
            int member = componentStack.top();
            componentStack.pop();
            edgeBCC[componentCount].push_back(member);
            if (member == vertex) break;
        }
    }
}

Extracting Vertex-Biconnected Components

Vertex-biconnected components require more careful stack management. When encountering an articulation point condition (lowlink[v] >= discovery[u]), we pop vertices from the stack until v is reached, creating a new component that includes both the popped vertices and the articulation point u itself. The articulation point remains on the stack as it may belong to multiple comopnents.

void findVertexBiconnected(int node) {
    discovery[node] = lowlink[node] = ++timeCounter;
    dfsStack.push(node);
    
    if (node == root && adjacency[node].empty()) {
        componentId++;
        vertexBCC[componentId].push_back(node);
        return;
    }
    
    int childNodes = 0;
    for (int adjacent : adjacency[node]) {
        if (discovery[adjacent] == 0) {
            childNodes++;
            findVertexBiconnected(adjacent);
            lowlink[node] = min(lowlink[node], lowlink[adjacent]);
            
            if (lowlink[adjacent] >= discovery[node]) {
                if (node != root || childNodes > 1) {
                    isCutVertex[node] = true;
                }
                
                componentId++;
                int stackMember;
                do {
                    stackMember = dfsStack.top();
                    dfsStack.pop();
                    vertexBCC[componentId].push_back(stackMember);
                } while (stackMember != adjacent);
                vertexBCC[componentId].push_back(node);
            }
        } else {
            lowlink[node] = min(lowlink[node], discovery[adjacent]);
        }
    }
}

Related Articles

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.