0% completed
In any network, certain points or connections are crucial for maintaining its overall connectivity. These points, known as articulation points or cut vertices, are locations that, if removed, cause the network to break into disconnected parts. Basically, articulation points is a single point of failure in any given network. Identifying these points is essential for building resilient systems, whether it be computer networks, transportation grids, or communication systems.
Tarjan’s Algorithm provides an efficient way to find articulation points in a network, ensuring that potential single points of failure are addressed.
In an undirected graph, an articulation point (or cut vertex) is a node that, if removed, causes the graph to break into more disconnected parts than it had before. This means that the node plays a crucial role in holding the network together.
To better understand this, let's first define what a connected component is. A connected component is a subset of a graph where every node is reachable from any other node in that subset by following a sequence of edges. In simple terms, it's a cluster where there is a path between every pair of nodes.
A node becomes an articulation point if, after its removal, the number of these connected components increases. In other words, removing that node causes the graph to split into more isolated clusters.
Let's look at a simple example to illustrate this concept.
Consider a graph with six nodes connected as follows:
Initially, all the nodes are part of a single connected component, meaning every node can be reached from any other node.
If we remove node 6 from this graph, it splits into two disconnected parts:
Since the removal of node 1 results in two separate groups, node 1 is an articulation point.
Now, let's remove node 3. After removing node 5, the graph divides into three disconnected parts:
Again, the removal of node 3 increases the number of connected components from one to three, making node 3 another articulation point.
To identify articulation points in a graph, a simple, straightforward approach is to examine each node one by one, removing it temporarily and checking whether its removal increases the number of connected components in the graph. If the number of connected components increases after removing a node, that node is identified as an articulation point.
Here's the rewritten pseudocode: 1. Compute the number of connected components in graph G. 2. For each vertex V in G: - Temporarily remove vertex V from G to create a modified graph G'. - Perform a DFS traversal on G' to determine the number of connected components in G'. - If the number of connected components in G' is greater than in G, then V is an articulation point. - Restore vertex V back into the graph.
V
nodes and E
edges in the graph:
This naive method can become quite slow for large graphs due to its high time complexity. Therefore, to handle larger inputs efficiently, we need a more optimized algorithm, such as Tarjan's Algorithm, which reduces the time complexity significantly.
Tarjan's Algorithm is an efficient method to find all the articulation points in a graph using Depth-First Search (DFS). The algorithm leverages the discovery time of each node and the lowest point reachable from its subtree to determine whether a node is a critical connection.
In a DFS traversal, the root node is the starting point of the search within a connected component of a graph. A root node can be an articulation point if it has multiple children in the DFS tree that are not interconnected. This means that if the root node is removed, it will split the graph into multiple disconnected components.
Let’s consider a graph where we start the DFS traversal from vertex 4. Here, vertex 4 becomes the root of our DFS tree.
In the above Figure 1, vertex 4 has three children: vertices 1, 2, and 3. Notice that the only way to travel between these child nodes is through vertex 4. If vertex 4 is removed, the graph splits into multiple disconnected parts (e.g., you can't reach vertices 3 and 1 from vertex 2 anymore). Therefore, vertex 4 is an articulation point because removing it increases the number of connected components in the graph.
In the above Figure 2, vertex 4 is again the root of the DFS tree. However, unlike Figure 1, all child nodes of vertex 4 are directly connected to each other. Even if we remove vertex 4, there are still paths available between nodes 2, 3, and 1. Hence, vertex 4 does not act as an articulation point in this scenario.
To summarize, the root node in a DFS traversal is an articulation point if it has more than one child that belongs to different subgraphs. The removal of this node would increase the number of disconnected components.
Initialize DFS Traversal:
-1
to signify it has no parent.Count Children of Root Node:
childCount
to 0.Traverse Through Each Child:
childCount
by 1.Recursive DFS Call:
Check for Articulation Point:
parent[currentNode] == -1
) and childCount > 1
, mark it as an articulation point.void dfsForRootArticulation(int currentNode, vector<int> &parent) { int childCount = 0; // Initialize child count of the root node as 0 for each adjacentNode of currentNode { if (adjacentNode is not visited) { childCount++; // Increase the count of children for the root parent[adjacentNode] = currentNode; // Set the parent of adjacentNode dfsForRootArticulation(adjacentNode, parent); // Recursive DFS call // Check if the current node is the root // and if it has more than one child if (parent[currentNode] == -1 && childCount > 1) { // Mark the current node as an articulation point markArticulationPoint(currentNode); } } } }
For a non-root node U
in a DFS traversal, it can be an articulation point if it has a child V
such that the subtree rooted at V
does not have a back-edge to any of the ancestors of U
. Let's understand this with the concept of back-edges.
A back-edge is an edge that connects a node to one of its ancestors in a DFS tree. For example, in the above diagram, vertex 6
is the root and during DFS, vertex 2
points back to vertex 6
, this edge (2 → 6) is a back-edge. Similarly, an edge from node 4
to node 5
(4 → 5) is also a back-edge as node 5
is an ancestor of node 4
.
Yes, an edge from a child node to its immediate parent is technically a back-edge, but for finding articulation points, such edges are not considered. This is because when a node is removed, its direct connection to its parent does not exist in the modified graph.
To efficiently determine if a node is an articulation point, we use two arrays:
discovery_time[]
: This array keeps track of the time at which each node is first visited during the DFS traversal. The "time" here represents the order in which nodes are visited.low[]
: This array stores the earliest (smallest) discovery time that can be reached from a given node using any combination of its own edges and back-edges.How to Update the low[]
Array
For a Tree Edge (an edge from a parent to a child in the DFS tree):
low
value of the parent node by taking the minimum of its current low
value and the low
value of its child. This update ensures that the parent node is aware of any back-edges reachable through its children.low[node] = min(low[node], low[child_node])
For a Back-Edge (an edge that points back to an ancestor of the current node):
low
value of the current node to be the minimum of its current low
value and the discovery_time
of the node that the back-edge points to. This captures the earliest reachable ancestor node using the back-edge.low[node] = min(low[node], discovery_time[child_node])
A non-root node U
is an articulation point if there exists at least one child V
such that:
low[V] >= discovery_time[U]
This condition means that there is no back-edge from any node in the subtree rooted at V
to any ancestor of U
. If this is true, removing U
would separate V
(and its subtree) from the rest of the graph, making U
a critical connection point or articulation point.
Initialize Discovery and Low Arrays:
discovery_time
and low
arrays to -1
.Set Discovery and Low Time for Current Node:
discovery_time[current_node]
and low[current_node]
to the current DFS time.Increment Time for the Next Node Visit:
Traverse Each Child Node:
low[current_node]
to the minimum of low[current_node]
and low[child]
.current_node
is not the root and low[child] >= discovery_time[current_node]
.current_node
as an articulation point.Handle Back-Edges:
low[current_node]
with discovery_time[child]
.// Initialize discovery_time and low arrays with -1 void dfsForNonRootArticulation(int currentNode, vector<int>& discovery_time, vector<int>& low, int parent, int time) { // Step 2: Set discovery and low time for the current node discovery_time[currentNode] = low[currentNode] = time; time++; // Step 3: Increment the DFS time // Step 4: Traverse each child of the current node for each (adjacentNode of currentNode) { if (discovery_time[adjacentNode] == -1) { // If the child is not visited dfsForNonRootArticulation(adjacentNode, discovery_time, low, currentNode, time); // Update low value for the current node low[currentNode] = min(low[currentNode], low[adjacentNode]); // Check if the current node is an articulation point if (parent != -1 && low[adjacentNode] >= discovery_time[currentNode]) { markArticulationPoint(currentNode); // Mark as articulation point } } // Step 5: Handle back-edge, ignore edge to parent else if (adjacentNode != parent) { low[currentNode] = min(low[currentNode], discovery_time[adjacentNode]); } } }
Now, let's combine the logic from both cases to create a complete algorithm to find all articulation points in an undirected graph using Tarjan's Algorithm. The combined algorithm will check both scenarios:
Initialize the Graph:
V
vertices.adjList
) to store the connections between vertices.addEdge(u, v)
) to add edges between nodes u
and v
.Prepare Data Structures for DFS Traversal:
discovery_time[]
: Stores the time at which each vertex is first visited.low[]
: Stores the smallest discovery time reachable from a vertex.parent[]
: Keeps track of each vertex's parent in the DFS tree.articulation_point[]
: A boolean array that marks articulation points in the graph.-1
for discovery_time
, low
, and parent
, false
for articulation_point
).Start DFS Traversal for Each Node:
discovery_time[node] == -1
), start a DFS from that node.DFS Function (Depth-First Search):
discovery_time[currentNode]
and low[currentNode]
to the current time.time
variable.currentNode
and increase the child count.adjacentNode
as currentNode
.adjacentNode
.low[currentNode]
to the minimum of low[currentNode]
and low[adjacentNode]
to ensure that the current node knows the earliest visited node reachable from its subtree.currentNode
is the root (its parent is -1
) and it has more than one child, mark it as an articulation point.currentNode
is not the root (its parent is not -1
) and low[adjacentNode] >= discovery_time[currentNode]
, mark it as an articulation point.low[currentNode]
to the minimum of low[currentNode]
and discovery_time[adjacentNode]
to account for the back-edge.Output the Articulation Points:
articulation_point[]
array and print all the marked articulation points.Given graph has 6 vertices and the following edges:
(0, 1)
(1, 2)
(1, 3)
(3, 4)
(4, 5)
V = 6
adjList = {0: [1], 1: [0, 2, 3], 2: [1], 3: [1, 4], 4: [3, 5], 5: [4]}
discovery_time[] = [-1, -1, -1, -1, -1, -1]
low[] = [-1, -1, -1, -1, -1, -1]
parent[] = [-1, -1, -1, -1, -1, -1]
articulation_point[] = [false, false, false, false, false, false]
time = 0
currentNode = 0
discovery_time[0] = low[0] = 1
(time = 1
)parent[0] = -1
children = 1
(increment child count)currentNode = 1
discovery_time[1] = low[1] = 2
(time = 2
)children = 1
(increment child count)currentNode = 2
discovery_time[2] = low[2] = 3
(time = 3
)children = 2
(increment child count)parent[3] = 1
(set parent of 3)dfs(3)
currentNode = 3
discovery_time[3] = low[3] = 4
(time = 4
)currentNode = 4
discovery_time[4] = low[4] = 5
(time = 5
)currentNode = 5
discovery_time[5] = low[5] = 6
(time = 6
)Continue DFS from Node 3:
low[1] = min(low[1], low[3]) = min(2, 4) = 2
low[3] >= discovery_time[1]
(4 >= 2), so node 1
is already marked as an articulation point.End DFS for Remaining Nodes:
1
, 3
, 4
Time Complexity:
The algorithm runs in O(V + E) time, where V
is the number of vertices and E
is the number of edges. This is because each node and edge is visited only once during the DFS traversal.
Space Complexity:
The space complexity is O(V), mainly due to the storage required for arrays like discovery_time
, low
, parent
, and articulation_point
, and the adjacency list representation of the graph.
Articulation points are crucial in many real-world scenarios where network connectivity is vital:
Network Reliability: In computer networks, identifying articulation points helps prevent failures by ensuring backup paths are available.
Transportation Networks: In road systems, articulation points highlight critical intersections or cities; their disruption can cause major traffic problems.
Social Networks: Identifying key users or groups as articulation points helps understand influence and detect communities or clusters.
Biological Networks: In protein interaction or metabolic networks, articulation points can identify critical proteins or enzymes for drug targeting.
Power and Telecom Grids: In power grids and telecom networks, articulation points indicate critical infrastructure whose failure could cause widespread outages or service disruptions.
Now, let's start solving the problem on the Articulation Point and Bridge
pattern.
.....
.....
.....