133. Clone Graph
Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.
OJ's undirected graph serialization:
Nodes are labeled uniquely.
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}.
The graph has a total of three nodes, and therefore contains three parts as separated by #.
- First node is labeled as
0. Connect node0to both nodes1and2. - Second node is labeled as
1. Connect node1to node2. - Third node is labeled as
2. Connect node2to node2(itself), thus forming a self-cycle.
Visually, the graph looks like the following:
1
/ \
/ \
0 --- 2
/ \
\_/
图的复制问题
图的问题,一般可以考虑用BFS或者DFS解决:BFS适合最短路径问题,DFS适合记录所有路径问题。这道题没有特别要求,两种方法都可行。
图包括点和边。点可以依靠BFS或者DFS来遍历,边需要loop每个点的neighbors。
不管使用何种方法,都需要利用哈希表来记录已经复制的节点。
方法一:使用BFS
数据结构:
BFS -> queue
避免重复拷贝-> 哈希表 unordered_map
1 /** 2 * Definition for undirected graph. 3 * struct UndirectedGraphNode { 4 * int label; 5 * vector<UndirectedGraphNode *> neighbors; 6 * UndirectedGraphNode(int x) : label(x) {}; 7 * }; 8 */ 9 class Solution { 10 public: 11 UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) { 12 if(node==NULL){ 13 return node; 14 } 15 16 unordered_map<UndirectedGraphNode*, UndirectedGraphNode*> visited; 17 queue<UndirectedGraphNode*> queue; 18 19 visited[node] = new UndirectedGraphNode(node->label); //copy node 20 queue.push(node); //push node into queue 21 22 //BFS: 23 while(!queue.empty()){ 24 UndirectedGraphNode* curr = queue.front(); 25 queue.pop(); 26 27 for(UndirectedGraphNode* neighbor: curr->neighbors){ 28 if(visited.find(neighbor)==visited.end()){ //new node 29 visited[neighbor] = new UndirectedGraphNode(neighbor->label); //copy node 30 queue.push(neighbor); // 31 } 32 //copy edge: neighbor clone should have happened if needed, otherwise, we cannot clone edge 33 visited[curr]->neighbors.push_back(visited[neighbor]); 34 } 35 } 36 37 return visited[node]; 38 } 39 };
方法二:使用DFS
同样需要使用哈希表来记录点有没有遍历过。DFS通常可以通过recursion或者利用stack来实现。
1. 使用recursion
1 /** 2 * Definition for undirected graph. 3 * struct UndirectedGraphNode { 4 * int label; 5 * vector<UndirectedGraphNode *> neighbors; 6 * UndirectedGraphNode(int x) : label(x) {}; 7 * }; 8 */ 9 class Solution { 10 public: 11 UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) { 12 if(node==NULL){ 13 return node; 14 } 15 16 unordered_map<UndirectedGraphNode*,UndirectedGraphNode*> visited; 17 return dfs(visited,node); 18 } 19 private: 20 //DFS focus on current node: clone node, clone edges that are originated from this node 21 UndirectedGraphNode* dfs(unordered_map<UndirectedGraphNode*,UndirectedGraphNode*>& visited, UndirectedGraphNode* node){ 22 visited[node]=new UndirectedGraphNode(node->label); //copy node (itself), and store it in hash map 23 for(UndirectedGraphNode* neighbor: node->neighbors){ //copy all edges (originated from current node) 24 if(visited.find(neighbor)==visited.end()){ //recursively dfs on this neighbor 25 dfs(visited,neighbor); 26 } 27 //copy edge: clone of the edge (node->neighbor) 28 visited[node]->neighbors.push_back(visited[neighbor]); 29 } 30 return visited[node]; 31 } 32 };
2. 使用stack
1 /** 2 * Definition for undirected graph. 3 * struct UndirectedGraphNode { 4 * int label; 5 * vector<UndirectedGraphNode *> neighbors; 6 * UndirectedGraphNode(int x) : label(x) {}; 7 * }; 8 */ 9 class Solution { 10 public: 11 UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) { 12 if(!node){ 13 return node; 14 } 15 16 unordered_map<UndirectedGraphNode*, UndirectedGraphNode*> visited; 17 stack<UndirectedGraphNode*> stack; 18 19 stack.push(node); 20 visited[node]=new UndirectedGraphNode(node->label); 21 22 //use stack to iteratively implement dfs 23 while(!stack.empty()){ 24 UndirectedGraphNode* curr = stack.top(); 25 stack.pop(); 26 27 for(UndirectedGraphNode* neighbor: curr->neighbors){ 28 if(visited.find(neighbor)==visited.end()){//new node, not cloned yet 29 visited[neighbor] = new UndirectedGraphNode(neighbor->label); //copy new node 30 stack.push(neighbor); //into stack to clone edges 31 } 32 //edge clone: neighbor clone should have happened if needed, otherwise we cannot clone edge 33 visited[curr]->neighbors.push_back(visited[neighbor]); 34 } 35 } 36 37 return visited[node]; 38 } 39 };
confusion: 细节部分,
1. code采用在while循环外复制根结点,在for循环内复制每个未遍历过的邻居结点,为什么?=>不准确,上面的三种实现方法的code,DFS with recursion方法是在dfs内先clone自己,然后在for循环recursively call DFS,并复制边。
2. 可否在while循环内,for循环外复制节点?=>见下面的分析
分析:
当借助于特殊的数据结构来做遍历时,i.e., BFS利用queue,DFS利用stack: 在利用for循环loop邻居试图clone边时,必须保证该邻居节点已经被clone,然后才能clone此边(把clone的邻居节点放到该节点的clone的邻居里);as a comparison, 如果是利用recursion,recursively call DFS on 邻居节点,当recursion返回时,邻居节点已经clone完成,因此可以直接clone 边。DFS with recursion需要清楚的知道对于该节点,需要做的是什么,在这里是:clone 自己,for 循环loop每个邻居,recursively call recursion on each neighbor to clone it, clone该节点到所有邻居的边。
当然,DFS with recursion 也可以在DFS外先clone节点,然后在DFS该节点是不需要clone自己,需要做的是clone每个邻居节点+该节点到邻居clone的边。评价:这种实现方法很不直观,因为这种DFS需要对该节点和邻居节点同时进行操作,e.g.,clone邻居节点,clone该节点到邻居节点的边;而不是focus在该节点上,这种实现容易导致混乱的逻辑。
浙公网安备 33010602011771号