Skip to content

Commit 69df8e4

Browse files
authored
Merge pull request #170 from BrianLusina/feat/datastructures-graphs-clone-graph
feat(datastructures, graphs): clone graph
2 parents 88ceada + 00e45ee commit 69df8e4

12 files changed

Lines changed: 145 additions & 0 deletions

DIRECTORY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,9 @@
441441
* [Ordered Dict](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/dicts/ordered_dict.py)
442442
* Graphs
443443
* [Edge](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge.py)
444+
* Undirected
445+
* Clone Graph
446+
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/undirected/clone_graph/node.py)
444447
* [Vertex](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/vertex.py)
445448
* Hashmap
446449
* [Test Hashmap](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/hashmap/test_hashmap.py)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Clone Graph
2+
3+
You are given a reference to a single node in an undirected, connected graph. Your task is to create a deep copy of the
4+
graph starting from the given node. A deep copy means creating a new instance of every node in the graph with the same
5+
data and edges as the original graph, such that changes in the copied graph do not affect the original graph.
6+
7+
Each node in the graph contains two properties:
8+
9+
- `data`: The value of the node, which is the same as its index in the adjacency list.
10+
- `neighbors`: A list of connected nodes, representing all the nodes directly linked to this node.
11+
12+
However, in the test cases, a graph is represented as an adjacency list to understand node relationships, where each
13+
index in the list represents a node (using 1-based indexing). For example, for [[2,3],[1,3],[1,2]], there are three
14+
nodes in the graph:
15+
16+
1st node (data = 1): Neighbors are 2nd node (data = 2) and 3rd node (data = 3).
17+
2nd node (data = 2): Neighbors are 1st node (data = 1) and 3rd node (data = 3).
18+
3rd node (data = 3): Neighbors are 1st node (data = 1) and 2nd node (data = 2).
19+
20+
The adjacency list will be converted to a graph at the backend and the first node of the graph will be passed to your
21+
code.
22+
23+
## Constraints
24+
25+
- 0 ≤ Number of nodes ≤ 100
26+
- 1 ≤ Node.data ≤ 100
27+
- Node.data is unique for each node.
28+
- The graph is undirected, i.e., there are no self-loops or repeated edges.
29+
- The graph is connected, i.e., any node can be visited from a given node.
30+
31+
## Topics
32+
33+
- Hash Table
34+
- Depth-First Search
35+
- Breadth-First Search
36+
- Graph Theory
37+
38+
## Examples
39+
40+
![Example 1](./images/examples/clone_graph_example_1.png)
41+
42+
> Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
43+
> Output: [[2,4],[1,3],[2,4],[1,3]]
44+
> Explanation: There are 4 nodes in the graph.
45+
> 1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
46+
> 2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
47+
> 3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
48+
> 4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
49+
50+
![Example 2](./images/examples/clone_graph_example_2.png)
51+
52+
> Input: adjList = [[]]
53+
> Output: [[]]
54+
> Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does
55+
> not have any neighbors.
56+
57+
> Example 3
58+
> Input: adjList = []
59+
> Output: []
60+
> Explanation: This an empty graph, it does not have any nodes.
61+
62+
## Solution
63+
64+
We use depth-first traversal and create a copy of each node while traversing the graph. We use a hash map to store each
65+
visited node to avoid getting stuck in cycles. We do not revisit nodes that exist in the hash map. The hash map key is
66+
a node in the original graph, and its value is the corresponding node in the cloned graph.
67+
68+
In order to solve this problem using the methodology discussed above, we create a recursive function, clone_helper, that
69+
takes two arguments: the current node being cloned and a hash map. The steps of this recursive function are given below:
70+
71+
1. If graph is empty, return NULL. This will also work as a base case for our recursive function.
72+
2. If the current node is not NULL, create a new Node with the same data as the current node, and add the current node
73+
as key and its clone as value to the hash map.
74+
3. Iterate through all the neighbors of the current node. For each neighbor, check if the neighbor is already cloned by
75+
looking up the neighbor in the hash map:
76+
- If the neighbor is not cloned yet, recursively call the function with the neighbor as the current node.
77+
- If the neighbor is already cloned, add the cloned neighbor to the new node’s neighbors.
78+
4. Finally, return the new node.
79+
80+
The clone function is the main function that creates a deep copy of the graph. It takes a single argument, which is a
81+
reference to the root node of the graph. The function creates an empty hash map to keep track of nodes that have already
82+
been cloned. Then it calls the clone_helper function, passing in the root node and the hash map.
83+
84+
![Solution 1](./images/solutions/clone_graph_solution_1.png)
85+
![Solution 2](./images/solutions/clone_graph_solution_2.png)
86+
![Solution 3](./images/solutions/clone_graph_solution_3.png)
87+
![Solution 4](./images/solutions/clone_graph_solution_4.png)
88+
![Solution 5](./images/solutions/clone_graph_solution_5.png)
89+
![Solution 6](./images/solutions/clone_graph_solution_6.png)
90+
91+
### Time Complexity
92+
93+
The time complexity of this code is O(n+m), where n is the number of nodes, and m is the number of edges.
94+
95+
### Space Complexity
96+
97+
The space complexity of this code is O(n), where n is the number of nodes in the dictionary.
98+
99+
> Note: We can also solve this problem using BFS.
100+
101+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Optional, Dict
2+
from datastructures.graphs.undirected.clone_graph.node import Node
3+
4+
5+
def clone(root: Optional[Node]) -> Optional[Node]:
6+
def clone_helper(n: Optional[Node], nodes_cloned: Dict[Node, Node]) -> Optional[Node]:
7+
# If the node is None, return None
8+
if n is None:
9+
return None
10+
11+
# Create a new Node with the same data as the node
12+
cloned_node = Node(n.data)
13+
# Add the node and its clone to the nodes_completed hash map
14+
nodes_cloned[n] = cloned_node
15+
16+
# Iterate through the neighbours of the node
17+
for neighbour in n.neighbors:
18+
# Retrieve the value of key neighbour in nodes_completed hash map.
19+
# If it exists, assign the corresponding cloned node to cloned_neighbour.
20+
# This checks if the neighbour node neighbour has already been cloned.
21+
cloned_neighbour = nodes_cloned.get(neighbour)
22+
# If the neighbour is not cloned yet, recursively clone it
23+
if cloned_neighbour is None:
24+
cloned_node.neighbors += [clone_helper(neighbour, nodes_cloned)]
25+
# If the neighbour is already cloned, add the cloned neighbour to the new
26+
# node's neighbors
27+
else:
28+
cloned_node.neighbors += [cloned_neighbour]
29+
return cloned_node
30+
31+
# Initialize an empty dictionary to keep track of cloned nodes
32+
nodes_completed: Dict[Node, Node] = {}
33+
# Call the recursive function to clone the graph starting from the root node
34+
return clone_helper(root, nodes_completed)
146 KB
Loading
11.6 KB
Loading
81.4 KB
Loading
95.5 KB
Loading
124 KB
Loading
134 KB
Loading
139 KB
Loading

0 commit comments

Comments
 (0)