package com.freetymekiyan.algorithms.level.medium;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
/**
* Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.
* <p>
* OJ's undirected graph serialization:
* Nodes are labeled uniquely.
* <p>
* We use # as a separator for each node, and , as a separator for node label and each neighbor of the node.
* As an example, consider the serialized graph {0,1,2#1,2#2,2}.
* <p>
* The graph has a total of three nodes, and therefore contains three parts as separated by #.
* <p>
* First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
* Second node is labeled as 1. Connect node 1 to node 2.
* Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a self-cycle.
* Visually, the graph looks like the following:
* <p>
* | 1
* | / \
* | / \
* | 0 --- 2
* | / \
* | \_/
* <p>
* Company Tags: Pocket, Gems, Google, Uber, Facebook
* Tags: Depth-first Search, Breadth-first Search, Graph
* Similar Problems: (H) Copy List with Random Pointer
*/
public class CloneGraph {
/**
* BFS. O(V) Time. O(V) Space.
* Use map<Integer, UndirectedGraphNode> to represent the new graph and a visited set.
* For each visit, connect the node with neighboring nodes.
* If neighboring nodes not in new graph yet, need to create them.
* Visit:
* Check whether current label is in new graph:
* | If not, create a new node with current label and put in map.
* If neighbors exist:
* | For each neighbor:
* | If its not visited, add it to queue and create a new node.
* | Connect current node with this neighbor.
*/
public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
if (node == null) {
return null;
}
Queue<UndirectedGraphNode> q = new ArrayDeque<>();
Map<Integer, UndirectedGraphNode> graph = new HashMap<>(); // New graph, also a visited set.
q.offer(node);
graph.put(node.label, new UndirectedGraphNode(node.label));
while (!q.isEmpty()) {
UndirectedGraphNode cur = q.poll();
if (cur.neighbors != null) {
for (UndirectedGraphNode n : cur.neighbors) {
// Add all unvisited neighbors to the queue.
if (!graph.containsKey(n.label)) {
q.offer(n);
graph.put(n.label, new UndirectedGraphNode(n.label));
}
// Connect new node with its neighbor.
graph.get(cur.label).neighbors.add(graph.get(n.label));
}
}
}
return graph.get(node.label);
}
/**
* DFS. Backtracking.
* Pass the node and map to its neighbors.
* Add neighbors backtrack result to its neighbors and return.
*/
public UndirectedGraphNode cloneGraphB(UndirectedGraphNode node) {
Map<Integer, UndirectedGraphNode> map = new HashMap<>();
return dfs(node, map);
}
/**
* Statement: Given a node, and a graph map to build, return the cloned node.
* Sub-problem: Build neighbors.
* Complete task: Build current node. Build neighbors. Connect current node with its neighbors.
* Base case: If current node is null, return null.
* Implementation:
* For each node in original node's neighbors:
* | If new graph doesn't have it:
* | DFS to copy it and add returned copy node to clone's neighbor.
* | If already have, means it's built:
* | Add it to clone's neighbor.
* Return cloned node.
*/
private UndirectedGraphNode dfs(UndirectedGraphNode node, Map<Integer, UndirectedGraphNode> map) {
if (node == null) {
return null;
}
if (!map.containsKey(node.label)) { // Not visited.
map.put(node.label, new UndirectedGraphNode(node.label)); // Add to new graph.
}
UndirectedGraphNode clone = map.get(node.label);
for (UndirectedGraphNode n : node.neighbors) {
if (!map.containsKey(n.label)) { // Only DFS unvisited neighbors.
clone.neighbors.add(dfs(n, map));
} else { // Add visited neighbors from map directly.
clone.neighbors.add(map.get(n.label));
}
}
return clone;
}
class UndirectedGraphNode {
int label;
List<UndirectedGraphNode> neighbors;
UndirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList<>();
}
}
}