Skip to content

Commit 28ab3ae

Browse files
authored
Create README.md
1 parent 104ca74 commit 28ab3ae

File tree

1 file changed

+257
-0
lines changed
  • 23 - Graph Data Structure Problems/17 - Critical Connections in a Network

1 file changed

+257
-0
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
<h1 align='center'>Critical - Connections - in a - Network</h1>
2+
3+
## Problem Statement
4+
5+
**Problem URL :** [Critical Connections in a Network](https://leetcode.com/problems/critical-connections-in-a-network/)
6+
7+
![image](https://github.com/user-attachments/assets/a56e7cdf-b7ae-4876-b18f-f57401571f3d)
8+
![image](https://github.com/user-attachments/assets/de20f0db-44b2-495c-84e1-87858eb0b290)
9+
10+
## Problem Explanation
11+
The problem **Critical Connections in a Network** asks us to find all the critical connections in a given network of servers represented as an undirected graph. A **critical connection** (also known as a **bridge**) is an edge in the network whose removal will increase the number of connected components. In simpler terms, if we remove that edge, the network will be split into two or more disconnected parts.
12+
13+
#### Example:
14+
15+
Consider the following network of servers:
16+
17+
- `n = 4` (number of servers)
18+
- Connections (edges): `[[0,1], [1,2], [2,0], [1,3]]`
19+
20+
This means:
21+
- Server `0` is connected to Server `1` and Server `2`
22+
- Server `1` is connected to Server `0`, Server `2`, and Server `3`
23+
- Server `2` is connected to Server `1` and Server `0`
24+
- Server `3` is connected to Server `1`
25+
26+
The graph representation looks like this:
27+
28+
```
29+
0 - 1 - 3
30+
| |
31+
2 -
32+
```
33+
34+
#### Objective:
35+
We need to identify the critical connections in the network. Let's analyze the connections:
36+
37+
- If we remove the edge `(1, 3)`, we disconnect Server `3` from the rest of the network. So, `(1, 3)` is a critical connection.
38+
- If we remove the edge `(0, 1)`, the network will still remain connected through Server `2`. So, this is not a critical connection.
39+
40+
Therefore, the only critical connection in this case is `[1, 3]`.
41+
42+
#### Approach:
43+
44+
1. **Understanding Bridges**: A bridge is an edge in an undirected graph that, when removed, increases the number of connected components.
45+
46+
2. **DFS Traversal**:
47+
- We can use **Depth-First Search (DFS)** to explore the graph. During the DFS traversal, we keep track of:
48+
- **Discovery Time (`disc`)**: The time when a node is first visited.
49+
- **Lowest Point (`low`)**: The lowest discovery time reachable from a node, which includes its neighbors and any back edges.
50+
51+
3. **Bridge Condition**:
52+
- For each edge `(u, v)`, if `low[v] > disc[u]`, then the edge `(u, v)` is a **bridge**. This is because there is no way to reach a node earlier than `u` from `v` through a back edge.
53+
54+
4. **DFS Tree**:
55+
- We maintain a DFS tree and check for each edge if it's a bridge based on the `low` and `disc` values.
56+
57+
## Problem Solution
58+
```cpp
59+
class Solution {
60+
public:
61+
// Helper function to perform DFS traversal and find critical connections
62+
void DFS(int server, int timer, int parentServer, vector<int>& low, vector<int>& disc, vector<int>& visited, vector<vector<int>>& criticalEdge, vector<vector<int>>& connections){
63+
64+
// Mark the current server as visited
65+
visited[server] = true;
66+
67+
// Set discovery time and lowest reachable time for the current server
68+
disc[server] = low[server] = timer++; // timer is incremented after setting the discovery time
69+
70+
// Explore all neighboring servers (connections)
71+
for(int & neighbourServer : connections[server]){
72+
73+
// Skip the parent server to avoid going back to the parent in the DFS tree
74+
if(neighbourServer == parentServer) continue;
75+
76+
// If the neighboring server is not visited, explore it
77+
if(!visited[neighbourServer]){
78+
DFS(neighbourServer, timer, server, low, disc, visited, criticalEdge, connections);
79+
80+
// After visiting the neighbor, update the low time for the current server
81+
low[server] = min(low[server], low[neighbourServer]);
82+
83+
// If the lowest time of the neighbor is greater than the discovery time of the current server,
84+
// it indicates that the edge between current server and the neighbor is a critical connection.
85+
if(low[neighbourServer] > disc[server])
86+
criticalEdge.push_back({server, neighbourServer});
87+
}else {
88+
// If the neighboring server was already visited, update the low time
89+
low[server] = min(low[server], low[neighbourServer]);
90+
}
91+
}
92+
}
93+
94+
// Main function to find all critical connections in the network
95+
vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
96+
// Arrays to store the lowest time reachable from a server and the discovery time of each server
97+
vector<int> low(n, -1);
98+
vector<int> disc(n, -1);
99+
100+
// Array to track if a server has been visited
101+
vector<int> visited(n, false);
102+
103+
// To store the critical connections (edges)
104+
vector<vector<int>> criticalEdge;
105+
106+
// Adjacency list to represent the network of servers
107+
vector<vector<int>> adjacencyList(n);
108+
109+
// Build the adjacency list from the connections
110+
for(auto& edge : connections){
111+
adjacencyList[edge[0]].push_back(edge[1]);
112+
adjacencyList[edge[1]].push_back(edge[0]);
113+
}
114+
115+
// Start DFS from each server that hasn't been visited
116+
for(int i = 0; i < n; i++)
117+
if(!visited[i])
118+
DFS(i, 0, -1, low, disc, visited, criticalEdge, adjacencyList);
119+
120+
// Return the list of critical connections (edges)
121+
return criticalEdge;
122+
}
123+
};
124+
125+
```
126+
127+
## Problem Solution Explanation
128+
129+
Now, let's go through the code to understand how it works.
130+
131+
#### `DFS` function:
132+
133+
```cpp
134+
void DFS(int server, int timer, int parentServer, vector<int>& low, vector<int>& disc, vector<int>& visited, vector<vector<int>>& criticalEdge, vector<vector<int>>& connections) {
135+
```
136+
- This is the DFS function. It takes the following parameters:
137+
- `server`: The current server (node) being visited.
138+
- `timer`: A variable to keep track of the discovery time (increments with each visit).
139+
- `parentServer`: The parent of the current node, used to avoid revisiting the parent in DFS.
140+
- `low`: Stores the lowest discovery time reachable from a server.
141+
- `disc`: Stores the discovery time of each server.
142+
- `visited`: Tracks whether a server has been visited.
143+
- `criticalEdge`: Stores the critical connections (bridges).
144+
- `connections`: The adjacency list of the graph.
145+
146+
```cpp
147+
visited[server] = true; // Mark the current server as visited
148+
disc[server] = low[server] = timer++; // Set the discovery and low time of the current server
149+
```
150+
- The `server` is marked as visited.
151+
- We set the discovery time `disc[server]` and low time `low[server]` for this server to the current `timer` and then increment `timer`.
152+
153+
```cpp
154+
for(int & neighbourServer : connections[server]) {
155+
```
156+
- Loop through all the neighbors (connections) of the current server.
157+
158+
```cpp
159+
if(neighbourServer == parentServer) continue; // Skip the parent node to avoid going backward in the DFS
160+
```
161+
- If the neighbor is the parent of the current server, skip it to avoid a backward traversal in the DFS tree.
162+
163+
```cpp
164+
if(!visited[neighbourServer]) {
165+
```
166+
- If the neighbor has not been visited, perform DFS on it.
167+
168+
```cpp
169+
DFS(neighbourServer, timer, server, low, disc, visited, criticalEdge, connections);
170+
low[server] = min(low[server], low[neighbourServer]);
171+
```
172+
- Recursively call DFS for the unvisited neighbor.
173+
- After returning from the DFS call, update `low[server]` to the minimum of `low[server]` and `low[neighbourServer]`.
174+
175+
```cpp
176+
if(low[neighbourServer] > disc[server]) criticalEdge.push_back({server, neighbourServer});
177+
```
178+
- If `low[neighbourServer] > disc[server]`, this means there is no back edge from `neighbourServer` to any ancestor of `server`, indicating that `(server, neighbourServer)` is a critical connection (bridge).
179+
180+
```cpp
181+
} else low[server] = min(low[server], disc[neighbourServer]);
182+
}
183+
}
184+
```
185+
- If the neighbor is already visited, update `low[server]` to the minimum of its current value and the discovery time of the neighbor.
186+
187+
#### `criticalConnections` function:
188+
189+
```cpp
190+
vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
191+
vector<int> low(n, -1); // Low time for each server, initially set to -1
192+
vector<int> disc(n, -1); // Discovery time for each server, initially set to -1
193+
vector<int> visited(n, false); // Visited array to track visited servers
194+
vector<vector<int>> criticalEdge; // Stores critical connections (bridges)
195+
vector<vector<int>> adjacencyList(n); // Adjacency list of the graph
196+
```
197+
- Initialize necessary arrays for `low`, `disc`, `visited`, and the result list `criticalEdge`.
198+
199+
```cpp
200+
for(auto& edge : connections) {
201+
adjacencyList[edge[0]].push_back(edge[1]);
202+
adjacencyList[edge[1]].push_back(edge[0]);
203+
}
204+
```
205+
- Convert the list of connections into an adjacency list representation of the graph.
206+
207+
```cpp
208+
for(int i = 0; i < n; i++)
209+
if(!visited[i]) DFS(i, 0, -1, low, disc, visited, criticalEdge, adjacencyList);
210+
```
211+
- Run DFS from every unvisited server to ensure all servers are explored.
212+
213+
```cpp
214+
return criticalEdge; // Return the list of critical connections (bridges)
215+
}
216+
```
217+
- After DFS completes, return the list of critical connections.
218+
219+
### Step 3: Example Walkthrough
220+
221+
Let's use an example to demonstrate how the code works.
222+
223+
#### Example 1:
224+
```
225+
n = 4
226+
connections = [[0,1], [1,2], [2,0], [1,3]]
227+
```
228+
229+
- Convert `connections` into an adjacency list:
230+
```
231+
adjacencyList = [[1, 2], [0, 2, 3], [0, 1], [1]]
232+
```
233+
234+
- Run DFS starting from node `0`:
235+
- Visit node `0`, discovery time is `0`, low time is `0`.
236+
- Visit node `1`, discovery time is `1`, low time is `1`.
237+
- Visit node `2`, discovery time is `2`, low time is `2`.
238+
- Visit node `3`, discovery time is `3`, low time is `3`.
239+
- Backtrack and update low values.
240+
- Edge `(1, 3)` is identified as a critical connection because `low[3] > disc[1]`.
241+
242+
Output: `[[1, 3]]`
243+
244+
### Step 4: Time and Space Complexity
245+
246+
- **Time Complexity**:
247+
- The DFS traversal visits each node and edge once. Therefore, the time complexity is O(V + E), where V is the number of vertices (servers) and E is the number of edges (connections).
248+
249+
- **Space Complexity**:
250+
- We use several arrays to store the `disc`, `low`, and `visited` information, each of size O(V). The adjacency list also takes O(E) space.
251+
- Therefore, the space complexity is O(V + E).
252+
253+
### Step 5: Additional Recommendations
254+
255+
- Understand the concept of **DFS** and **low/discovery times** thoroughly, as they are key to identifying bridges.
256+
- Practice more problems related to graph traversal (DFS/BFS) and bridges to strengthen your problem-solving skills.
257+
- Try solving similar problems using different graph traversal techniques like BFS to get a better understanding.

0 commit comments

Comments
 (0)