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 #.

  1. First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
  2. Second node is labeled as 1. Connect node 1 to node 2.
  3. 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:

       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在该节点上,这种实现容易导致混乱的逻辑。

posted @ 2018-06-23 08:34  回到明天  阅读(109)  评论(0)    收藏  举报