11/**
2- * Finds all articulation points on an undirected graph.
2+ * Articulation Points (Cut Vertices) — Adjacency List
33 *
4- * <p>Tested against HackerEarth online judge at:
4+ * An articulation point is a vertex whose removal disconnects the graph
5+ * (or increases the number of connected components). This implementation
6+ * uses Tarjan's DFS-based algorithm with low-link values.
7+ *
8+ * For each DFS tree rooted at node r, a non-root node u is an articulation
9+ * point if it has a child v such that no vertex in the subtree rooted at v
10+ * has a back edge to an ancestor of u:
11+ *
12+ * ids[u] <= low[v]
13+ *
14+ * The root node r is an articulation point if it has more than one child
15+ * in the DFS tree.
16+ *
17+ * Works on disconnected graphs by running DFS from every unvisited node.
18+ *
19+ * See also: {@link BridgesAdjacencyList} for finding bridge edges.
20+ *
21+ * Tested against HackerEarth online judge at:
522 * https://www.hackerearth.com/practice/algorithms/graphs/articulation-points-and-bridges/tutorial
623 *
24+ * Time: O(V + E)
25+ * Space: O(V)
26+ *
727 * @author William Fiset, william.alexandre.fiset@gmail.com
828 */
929package com .williamfiset .algorithms .graphtheory ;
1535
1636public class ArticulationPointsAdjacencyList {
1737
18- private int n , id , rootNodeOutgoingEdgeCount ;
38+ private final int n ;
39+ private final List <List <Integer >> graph ;
1940 private boolean solved ;
41+ private int id , rootNodeOutgoingEdgeCount ;
2042 private int [] low , ids ;
2143 private boolean [] visited , isArticulationPoint ;
22- private List <List <Integer >> graph ;
2344
2445 public ArticulationPointsAdjacencyList (List <List <Integer >> graph , int n ) {
2546 if (graph == null || n <= 0 || graph .size () != n ) throw new IllegalArgumentException ();
2647 this .graph = graph ;
2748 this .n = n ;
2849 }
2950
30- // Returns the indexes for all articulation points in the graph even if the
31- // graph is not fully connected.
51+ /**
52+ * Returns a boolean array where index i is true if node i is an articulation point.
53+ * Works even if the graph is not fully connected.
54+ */
3255 public boolean [] findArticulationPoints () {
3356 if (solved ) return isArticulationPoint ;
3457
3558 id = 0 ;
36- low = new int [n ]; // Low link values
37- ids = new int [n ]; // Nodes ids
59+ low = new int [n ];
60+ ids = new int [n ];
3861 visited = new boolean [n ];
3962 isArticulationPoint = new boolean [n ];
4063
64+ // Run DFS from each unvisited node to handle disconnected components.
4165 for (int i = 0 ; i < n ; i ++) {
4266 if (!visited [i ]) {
4367 rootNodeOutgoingEdgeCount = 0 ;
4468 dfs (i , i , -1 );
69+ // Root is an articulation point only if it has 2+ children in the DFS tree.
4570 isArticulationPoint [i ] = (rootNodeOutgoingEdgeCount > 1 );
4671 }
4772 }
@@ -51,48 +76,59 @@ public boolean[] findArticulationPoints() {
5176 }
5277
5378 private void dfs (int root , int at , int parent ) {
54-
5579 if (parent == root ) rootNodeOutgoingEdgeCount ++;
5680
5781 visited [at ] = true ;
5882 low [at ] = ids [at ] = id ++;
5983
60- List <Integer > edges = graph .get (at );
61- for (Integer to : edges ) {
84+ for (int to : graph .get (at )) {
6285 if (to == parent ) continue ;
6386 if (!visited [to ]) {
6487 dfs (root , to , at );
6588 low [at ] = min (low [at ], low [to ]);
89+ // If no vertex in the subtree rooted at 'to' can reach above 'at',
90+ // then removing 'at' would disconnect 'to's subtree.
6691 if (ids [at ] <= low [to ]) {
6792 isArticulationPoint [at ] = true ;
6893 }
6994 } else {
95+ // Back edge: update low-link to the earliest reachable ancestor.
7096 low [at ] = min (low [at ], ids [to ]);
7197 }
7298 }
7399 }
74100
75101 /* Graph helpers */
76102
77- // Initialize a graph with 'n' nodes.
78103 public static List <List <Integer >> createGraph (int n ) {
79104 List <List <Integer >> graph = new ArrayList <>(n );
80105 for (int i = 0 ; i < n ; i ++) graph .add (new ArrayList <>());
81106 return graph ;
82107 }
83108
84- // Add an undirected edge to a graph.
85109 public static void addEdge (List <List <Integer >> graph , int from , int to ) {
86110 graph .get (from ).add (to );
87111 graph .get (to ).add (from );
88112 }
89113
90- /* Example usage: */
114+ // ==================== Main ====================
91115
92116 public static void main (String [] args ) {
117+ testExample1 ();
93118 testExample2 ();
94119 }
95120
121+ //
122+ // 0 --- 1
123+ // | /
124+ // 2 -------- 3 --- 4
125+ // |
126+ // 5 --- 6
127+ // | |
128+ // 8 --- 7
129+ //
130+ // Articulation points: 2, 3, 5
131+ //
96132 private static void testExample1 () {
97133 int n = 9 ;
98134 List <List <Integer >> graph = createGraph (n );
@@ -111,16 +147,18 @@ private static void testExample1() {
111147 ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList (graph , n );
112148 boolean [] isArticulationPoint = solver .findArticulationPoints ();
113149
114- // Prints:
115150 // Node 2 is an articulation
116151 // Node 3 is an articulation
117152 // Node 5 is an articulation
118153 for (int i = 0 ; i < n ; i ++)
119154 if (isArticulationPoint [i ]) System .out .printf ("Node %d is an articulation\n " , i );
120155 }
121156
122- // Tests a graph with 3 nodes in a line: A - B - C
123- // Only node 'B' should be an articulation point.
157+ //
158+ // 0 --- 1 --- 2
159+ //
160+ // Articulation point: 1
161+ //
124162 private static void testExample2 () {
125163 int n = 3 ;
126164 List <List <Integer >> graph = createGraph (n );
@@ -131,7 +169,6 @@ private static void testExample2() {
131169 ArticulationPointsAdjacencyList solver = new ArticulationPointsAdjacencyList (graph , n );
132170 boolean [] isArticulationPoint = solver .findArticulationPoints ();
133171
134- // Prints:
135172 // Node 1 is an articulation
136173 for (int i = 0 ; i < n ; i ++)
137174 if (isArticulationPoint [i ]) System .out .printf ("Node %d is an articulation\n " , i );
0 commit comments