Graph Deep Copy

- Given a reference to a node in an undirected graph
- Create a deep copy of the graph
- The copy must be independant from the original
- New nodes must be created
The point:
- Duplicate while traversing
- deep_copy_recursive
Complexity :
Time | Space |
---|---|
O(n + e) | O(n) |
- O(n + e) in time because we create n nodes and traverse e edges
- O(n) in space because the size of the recursive stack (can grow up to n) + clone_map hash map stores n key-val pairs
V1
About Rust :
- Based on the version using
Rc
andRefCell
- check
247_introduction.ipynb
Rc
andRefCell
are mandatory- We need pointers pointing to the same nodes =>
Rc
RefCell
supports interior mutability. This is a way of bypassing Rust’s borrowing rules at runtime rather than at compile-time.- Without
RefCell
, anRc<Node>
cannot be modified. Rust forbids to modify a structure contained in an Rc. An Rc only gives (immutable) read access to the underlying data. See :.borrow()
and.borrow_mut()
- There is a cost at runtime.
- check
- YES : tested on the Rust Playground
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashMap;
// Type alias for readability
type GraphNodeRef = Rc<RefCell<GraphNode>>;
#[derive(Debug)]
struct GraphNode {
val: usize,
neighbors: Vec<GraphNodeRef>,
}
fn from_adjacency_list(adj_list: &[Vec<usize>]) -> Vec<GraphNodeRef> {
let nodes: Vec<GraphNodeRef> = (0..adj_list.len()).map(|i| Rc::new(RefCell::new(GraphNode { val: i, neighbors: vec![] }))).collect();
for (i, neighbors) in adj_list.iter().enumerate() {
let mut node_mut = nodes[i].borrow_mut();
node_mut.neighbors = neighbors.iter().map(|&j| Rc::clone(&nodes[j])).collect();
}
nodes
}
fn print_graph(node: &GraphNodeRef) {
use std::collections::HashSet;
let mut visited = HashSet::new();
// This a way to hide the recursive part to the caller who can only call `print_graph()`
fn deep_copy_recursive_print(node: &GraphNodeRef, visited: &mut HashSet<usize>) {
let val = node.borrow().val;
if visited.contains(&val) {
return;
}
visited.insert(val);
let neighbors: Vec<_> = node.borrow().neighbors.iter().map(|n| n.borrow().val).collect();
println!("Node {} has neighbors {:?}", val, neighbors);
for neighbor in &node.borrow().neighbors {
deep_copy_recursive_print(neighbor, visited);
}
}
deep_copy_recursive_print(node, &mut visited);
}
// I’m not nesting deep_copy_recursive() inside deep_copy() to stay consistent with the book’s style and to improve code readability.
fn deep_copy_recursive(node: &GraphNodeRef, clone_map: &mut HashMap<usize, GraphNodeRef>) -> GraphNodeRef {
// If this node was already cloned, then return this previously cloned node
let node_val = node.borrow().val;
if let Some(clone) = clone_map.get(&node_val) {
return Rc::clone(clone);
}
// Clone the current node
let cloned_node = Rc::new(RefCell::new(GraphNode {
val: node_val,
neighbors: vec![],
}));
// Store the current clone to ensure it doesn't need to be created again in future DFS calls
clone_map.insert(node_val, Rc::clone(&cloned_node));
// Iterate through the neighbors of the current node to connect their clones to the current cloned node
for neighbor in &node.borrow().neighbors {
let cloned_neighbor = deep_copy_recursive(neighbor, clone_map);
cloned_node.borrow_mut().neighbors.push(cloned_neighbor);
}
cloned_node
}
// Public deep copy function
fn deep_copy(start_node: &GraphNodeRef) -> GraphNodeRef {
let mut clone_map = HashMap::new();
deep_copy_recursive(start_node, &mut clone_map)
}
fn main() {
// 0
// / | \
// 3 | 1
// \ |
// 2
// /
// 4
let adjacency_list = [
vec![1, 2, 3],
vec![0],
vec![0, 3, 4],
vec![0, 2],
vec![2],
];
let my_graph = from_adjacency_list(&adjacency_list);
let my_start_node = &my_graph[0];
let my_cloned_start = deep_copy(my_start_node);
print_graph(&my_cloned_start);
}
V2
About Rust :
- Changes are in
deep_copy_recursive
- Reduce the number of
node.borrow()
- Use
Vec::with_capacity
for the vector ofneighbors
- Reduce the number of
- Preferred solution?
- YES : tested on the Rust Playground
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashMap;
// Type alias for readability
type GraphNodeRef = Rc<RefCell<GraphNode>>;
#[derive(Debug)]
struct GraphNode {
val: usize,
neighbors: Vec<GraphNodeRef>,
}
fn from_adjacency_list(adj_list: &[Vec<usize>]) -> Vec<GraphNodeRef> {
let nodes: Vec<GraphNodeRef> = (0..adj_list.len()).map(|i| Rc::new(RefCell::new(GraphNode { val: i, neighbors: vec![] }))).collect();
for (i, neighbors) in adj_list.iter().enumerate() {
let mut node_mut = nodes[i].borrow_mut();
node_mut.neighbors = neighbors.iter().map(|&j| Rc::clone(&nodes[j])).collect();
}
nodes
}
fn print_graph(node: &GraphNodeRef) {
use std::collections::HashSet;
let mut visited = HashSet::new();
fn deep_copy_recursive_print(node: &GraphNodeRef, visited: &mut HashSet<usize>) {
let val = node.borrow().val;
if visited.contains(&val) {
return;
}
visited.insert(val);
let neighbors: Vec<_> = node.borrow().neighbors.iter().map(|n| n.borrow().val).collect();
println!("Node {} has neighbors {:?}", val, neighbors);
for neighbor in &node.borrow().neighbors {
deep_copy_recursive_print(neighbor, visited);
}
}
deep_copy_recursive_print(node, &mut visited);
}
// I’m not nesting deep_copy_recursive() inside deep_copy() to stay consistent with the book’s style and to improve code readability.
fn deep_copy_recursive(node: &GraphNodeRef, clone_map: &mut HashMap<usize, GraphNodeRef>) -> GraphNodeRef {
// If this node was already cloned, then return this previously cloned node
let node_borrowed = node.borrow();
if let Some(clone) = clone_map.get(&node_borrowed.val) {
return Rc::clone(clone);
}
// Clone the current node
let cloned_node = Rc::new(RefCell::new(GraphNode {
val: node_borrowed.val,
neighbors: Vec::with_capacity(node_borrowed.neighbors.len()), /
}));
// Store the current clone to ensure it doesn't need to be created again in future DFS calls
clone_map.insert(node_borrowed.val, Rc::clone(&cloned_node));
// Iterate through the neighbors of the current node to connect their clones to the current cloned node
for neighbor in &node_borrowed.neighbors {
let cloned_neighbor = deep_copy_recursive(neighbor, clone_map);
cloned_node.borrow_mut().neighbors.push(cloned_neighbor);
}
cloned_node
}
// Public deep copy function
fn deep_copy(start_node: &GraphNodeRef) -> GraphNodeRef {
let mut clone_map = HashMap::new();
deep_copy_recursive(start_node, &mut clone_map)
}
fn main() {
// 0
// / | \
// 3 | 1
// \ |
// 2
// /
// 4
let adjacency_list = [
vec![1, 2, 3],
vec![0],
vec![0, 3, 4],
vec![0, 2],
vec![2],
];
let my_graph = from_adjacency_list(&adjacency_list);
let my_start_node = &my_graph[0];
let my_cloned_start = deep_copy(my_start_node);
print_graph(&my_cloned_start);
}
V3
About Rust :
- Changes are in
deep_copy_recursive
- Use
.entry()
fromHashMap
to replace.get()
and.insert()
- Use
- YES : tested on the Rust Playground
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashMap;
// Type alias for readability
type GraphNodeRef = Rc<RefCell<GraphNode>>;
#[derive(Debug)]
struct GraphNode {
val: usize,
neighbors: Vec<GraphNodeRef>,
}
fn from_adjacency_list(adj_list: &[Vec<usize>]) -> Vec<GraphNodeRef> {
let nodes: Vec<GraphNodeRef> = (0..adj_list.len()).map(|i| Rc::new(RefCell::new(GraphNode { val: i, neighbors: vec![] }))).collect();
for (i, neighbors) in adj_list.iter().enumerate() {
let mut node_mut = nodes[i].borrow_mut();
node_mut.neighbors = neighbors.iter().map(|&j| Rc::clone(&nodes[j])).collect();
}
nodes
}
fn print_graph(node: &GraphNodeRef) {
use std::collections::HashSet;
let mut visited = HashSet::new();
fn deep_copy_recursive_print(node: &GraphNodeRef, visited: &mut HashSet<usize>) {
let val = node.borrow().val;
if visited.contains(&val) {
return;
}
visited.insert(val);
let neighbors: Vec<_> = node.borrow().neighbors.iter().map(|n| n.borrow().val).collect();
println!("Node {} has neighbors {:?}", val, neighbors);
for neighbor in &node.borrow().neighbors {
deep_copy_recursive_print(neighbor, visited);
}
}
deep_copy_recursive_print(node, &mut visited);
}
// I’m not nesting deep_copy_recursive() inside deep_copy() to stay consistent with the book’s style and to improve code readability.
fn deep_copy_recursive(node: &GraphNodeRef, clones: &mut HashMap<usize, GraphNodeRef>) -> GraphNodeRef {
let node_borrowed = node.borrow();
let val = node_borrowed.val;
match clones.entry(val) {
// If this node was already cloned, then return this previously cloned node
std::collections::hash_map::Entry::Occupied(entry) => {
Rc::clone(entry.get())
}
std::collections::hash_map::Entry::Vacant(entry) => {
// Clone the current node
let cloned_node = Rc::new(RefCell::new(GraphNode {
val,
neighbors: Vec::with_capacity(node_borrowed.neighbors.len()), // Optional optimization
}));
// Store the current clone to ensure it doesn't need to be created again in future DFS calls
let cloned_node_ref = Rc::clone(&cloned_node);
entry.insert(cloned_node);
// Iterate through the neighbors of the current node to connect their clones to the current cloned node
for neighbor in &node_borrowed.neighbors {
let cloned_neighbor = deep_copy_recursive(neighbor, clones);
cloned_node_ref.borrow_mut().neighbors.push(cloned_neighbor);
}
cloned_node_ref
}
}
}
// Public deep copy function
fn deep_copy(start_node: &GraphNodeRef) -> GraphNodeRef {
let mut clone_map = HashMap::new();
deep_copy_recursive(start_node, &mut clone_map)
}
fn main() {
// 0
// / | \
// 3 | 1
// \ |
// 2
// /
// 4
let adjacency_list = [
vec![1, 2, 3],
vec![0],
vec![0, 3, 4],
vec![0, 2],
vec![2],
];
let my_graph = from_adjacency_list(&adjacency_list);
let my_start_node = &my_graph[0];
let my_cloned_start = deep_copy(my_start_node);
print_graph(&my_cloned_start);
}
V4
About Rust :
- Use classes.
- Do not use an “inner” module (see
First attempt
in247_introduction.ipynb
) - Traverse the graph (
deep_copy_recursive()
) - Duplicate nodes while traversing
- Use a hashmap where original nodes are the key and the corresponding cloned node is the value
- YES : tested on the Rust Playground
use std::collections::{HashMap, HashSet};
#[derive(Clone)]
struct GraphNode {
val: usize,
neighbors: Vec<usize>,
}
impl GraphNode {
fn new(val: usize, neighbors: Vec<usize>) -> Self {
Self { val, neighbors }
}
fn process(&self) {
println!("Processing node {}", self.val);
}
}
struct Graph {
nodes: Vec<GraphNode>,
}
impl Graph {
fn new() -> Self {
Self { nodes: Vec::new() }
}
fn from_adjacency_list(adj_list: &[Vec<usize>]) -> Self {
let mut graph = Graph::new();
for (i, neighbors) in adj_list.iter().enumerate() {
graph.nodes.push(GraphNode::new(i, neighbors.clone()));
}
graph
}
// Public print function
fn print(&self, start: usize) {
let mut visited = HashSet::new();
self.print_recursive(start, &mut visited);
}
fn print_recursive(&self, start: usize, visited: &mut HashSet<usize>) {
if visited.contains(&start) {
return;
}
visited.insert(start);
self.nodes[start].process();
for &neighbor in &self.nodes[start].neighbors {
self.print_recursive(neighbor, visited);
}
}
// Public deep copy function
fn deep_copy(&self) -> Graph {
// Return an empty graph if the original graph has no nodes
if self.nodes.is_empty() {
return Graph::new();
}
// Create a map to track already cloned nodes by their index
let mut clone_map = HashMap::new();
// Start the deep copy from node index 0
self.deep_copy_recursive(0, &mut clone_map);
// Convert HashMap to Vec sorted by index
let mut nodes = vec![GraphNode::new(0, vec![]); self.nodes.len()];
for (index, node) in clone_map {
nodes[index] = node;
}
// Return the new graph with the copied nodes
Graph { nodes }
}
fn deep_copy_recursive(&self, index: usize, clone_map: &mut HashMap<usize, GraphNode>) -> GraphNode {
// If the node at this index has already been cloned, return the existing clone
if let Some(existing) = clone_map.get(&index) {
return existing.clone(); // Clone the previously cloned node
}
// Clone the current node (without neighbors for now)
let node = &self.nodes[index];
let mut cloned_node = GraphNode::new(node.val, Vec::with_capacity(node.neighbors.len()));
// Store a copy of the node in the map before recursion
// This avoids infinite recursion in case of cycles
clone_map.insert(index, cloned_node.clone());
// Recursively clone all neighbors and update the neighbor list of the cloned node
for &neighbor_index in &node.neighbors {
let cloned_neighbor = self.deep_copy_recursive(neighbor_index, clone_map);
cloned_node.neighbors.push(cloned_neighbor.val); // Store only the neighbor's value
}
// Update the map with the fully constructed node
clone_map.insert(index, cloned_node.clone());
// Return the cloned node
cloned_node
}
}
fn main() {
// 0
// / | \
// 3 | 1
// \ |
// 2
// /
// 4
let adjacency_list = [
vec![1, 2, 3],
vec![0],
vec![0, 3, 4],
vec![0, 2],
vec![2]
];
let graph = Graph::from_adjacency_list(&adjacency_list);
println!("Original :"); // Original :
graph.print(0); // Processing node 0
// ...
let graph_copy = graph.deep_copy();
println!("\nCopy :"); // Copy :
graph_copy.print(0) // Processing node 0
// ...
}
V5
About Rust :
- There are no pointer relationships, so deep copying can be simplified
- But… We don’t play by the rules
- YES : tested on the Rust Playground
use std::collections::{HashSet};
struct GraphNode {
val: usize,
neighbors: Vec<usize>,
}
impl GraphNode {
fn new(val: usize, neighbors: Vec<usize>) -> Self {
Self { val, neighbors }
}
fn process(&self) {
println!("Processing node {}", self.val);
}
}
struct Graph {
nodes: Vec<GraphNode>,
}
impl Graph {
fn new() -> Self {
Self { nodes: Vec::new() }
}
fn from_adjacency_list(adj_list: &[Vec<usize>]) -> Self {
let mut graph = Graph::new();
for (i, neighbors) in adj_list.iter().enumerate() {
graph.nodes.push(GraphNode::new(i, neighbors.clone()));
}
graph
}
fn print(&self, start: usize) {
let mut visited = HashSet::new();
self.print_recursive(start, &mut visited);
}
fn print_recursive(&self, start: usize, visited: &mut HashSet<usize>) {
if visited.contains(&start) {
return;
}
visited.insert(start);
self.nodes[start].process();
for &neighbor in &self.nodes[start].neighbors {
self.print_recursive(neighbor, visited);
}
}
// There are no pointer relationships, so deep copy can be very simple.
fn deep_copy(&self) -> Graph {
let copied_nodes = self.nodes
.iter()
.map(|node| GraphNode::new(node.val, node.neighbors.clone()))
.collect();
Graph { nodes: copied_nodes }
}
}
fn main() {
// 0
// / | \
// 3 | 1
// \ |
// 2
// /
// 4
let adjacency_list = [
vec![1, 2, 3],
vec![0],
vec![0, 3, 4],
vec![0, 2],
vec![2],
];
let graph = Graph::from_adjacency_list(&adjacency_list);
println!("Original :"); // Original :
graph.print(0); // Processing node 0
// ...
let graph_copy = graph.deep_copy();
println!("\nCopy :"); // Copy :
graph_copy.print(0) // Processing node 0
// ...
}