Clone Graph Part I


题目:Clone a graph.

    Input is a Node pointer. 

            Return the Node pointer of the cloned graph.

克隆一个图,输入是一个节点指针,返回是所克隆图的节点指针。

Hint:
There are two main ways to traverse a graph. Do you still remember them? Could you tell if the graph is directed or undirected?

有2种主要的方法遍历图。你仍旧记得嘛?你能区分有向图和无向图吗?

Solution:
There are two main ways to traverse a graph: Breadth-first or Depth-first. Let’s try the Breadth-first approach first, which requires a queue. For the Depth-first approach, please see Clone Graph Part II.

有2种主要的方法遍历图:宽度优先或者深度优先

How does the breadth-first traversal works? Easy, as we pop a node off the queue, we copy each of its neighbors, and push them to the queue.

当我们从队列中取出一个节点,我们复制此节点的每一个邻居,以及把所复制的放到队列中。

A straight forward breadth-first traversal seemed to work. But some details are still missing. For example, how do we connect the nodes of the cloned graph?

问题是我们如何连接所复制图的节点

Before we continue, we first need to make sure if the graph is directed or not. If you notice how Node is defined above, it is quite obvious that the graph is a directed graph. Why?

图是有向的。

For example, A can have a neighbor called B. Therefore, we may traverse from A to B. An undirected graph implies that B can always traverse back to A. Is it true here? No, because whether B could traverse back to A depends if one of B’s neighbor is A.

如果A有个邻居叫B。因此,我们可以从A到B。一个无向图暗示B总是可以返回到A。 这对吗?不。因为B是否能够返回到A取决于B有一个邻居是A。

The fact that B can traverse back to A implies that the graph may contain a cycle. You must take extra care to handle this case. Imagine that you finished implementing without considering this case, and later being pointed out by your interviewer that your code has an infinite loop, yuck!

事实上B能够返回到A说明了这个图包含了一个循环。你必须额外处理这种情况。想象下你在没有考虑这种情况下完成了,之后被你面试官指出,你的代码是一个无限循环。

Let’s analyze this further by using the below example:

A simple graph

Assume that the starting point of the graph is A. First, you make a copy of node A (A2), and found that A has only one neighbor B. You make a copy of B (B2) and connects A2->B2 by pushing B2 as A2′s neighbor. Next, you find that B has A as neighbor, which you have already made a copy of. Here, we have to be careful not to make a copy of A again, but to connect B2->A2 by pushing A2 as B2′s neighbor. But, how do we know if a node has already been copied?

假设图的起点是A,首先复制A(A2),然后发现A只有一个邻居B。你复制B(B2)并且连接A2到B2通过把B2作为A2的邻居。下一步,你发现B有一个邻居A,A你已经复制过了。这里我们不得不小心,不要再次复制A,但是连接B2到A2通过把A2作为B2的邻居。但是我们如何知道一个节点是否已经被复制了呢?

Easy, we could use a hash table! As we copy a node, we insert it into the table. If we later find that one of a node’s neighbor is already in the table, we do not make a copy of that neighbor, but to push its neighbor’s copy to its copy instead. Therefore, the hash table would need to store a mapping of key-value pairs, where the key is a node in the original graph and its value is the node’s copy.

简单,我们能够用HASH TABLE。当我们复制一个节点,我们插入他到表中。如果我们之后发现一个节点的一个邻居已经在表中了,我们不复制那个邻居,但是我们把他的邻居复制作为作为此节点复制的邻居。因此,HASH TABLE 需要保存关键值对的映射,key是在原始图的节点,value是此节点的复制。

用自己的话说,有一个HASH TABLE, key保存节点,value保存对应节点的复制。那上面的图来说,第一步,复制A保存到hash表中(A,A2),第二步发现A只有一个邻居B,复制B保存到hash表中(B,B2),连接A2到B2,把B2作为A2的邻居。现在看B,发现有一个邻居A,不复制A,把A的复制(A2 )作为B的复制(B2) 的邻居,也就是A2 作为B2的邻居。

下面进行一个简单任务实现

克隆Graph1,生成Graph2。通过广度搜索算法。



如图:图由节点组成,节点有自己的邻居(即节点之间的关系);比如A的邻居是B,C。表示A指向B,C。B的邻居是A,表示B指向A。

下面是源代码

package Clone_Graph_Part_I;


public class Main {

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		//***********Generate graph**********
		//Generate nodes A,B,C
		Node A = new Node("1");
		Node B = new Node("2");
		Node C = new Node("3");
		Node D = new Node("4");
		Node E = new Node("5");
		Node F = new Node("6");
		Node G = new Node("7");
		Node H = new Node("8");
		Node I = new Node("9");
		Node J = new Node("10");
		Node K = new Node("11");
		Node L = new Node("12");
		//Get neighbors for A,B,C
		A.addNeighbor(B);
		A.addNeighbor(C);
		A.addNeighbor(D);
		
		B.addNeighbor(E);
		B.addNeighbor(F);
		E.addNeighbor(I);
		E.addNeighbor(J);
		D.addNeighbor(G);
		D.addNeighbor(H);
		G.addNeighbor(K);
		G.addNeighbor(L);
		
		
		
		//Get nodes for graph1
		Graph graph1 = new Graph("Graph1");
		graph1.addNode(A);
		graph1.addNode(B);
		graph1.addNode(C);
		graph1.addNode(D);
		graph1.addNode(E);
		graph1.addNode(F);
		graph1.addNode(G);
		graph1.addNode(H);
		graph1.addNode(I);
		graph1.addNode(J);
		graph1.addNode(K);
		graph1.addNode(L);
		
		
		graph1.printGraph();
		Graph graph2 = graph1.cloneGraph(graph1);
		graph2.printGraph();
	}

}
package Clone_Graph_Part_I;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;


class Graph {
	private String name;
	private ArrayList<Node> nodeList;
	private Map<Node, Node> nodeMap = new HashMap<Node, Node>();
	public Graph(String name){
		this.name = name;
		this.nodeList = new ArrayList<Node>();
	}
	// get Graph Name
	public String getGraphName(){
		return this.name;
	}
	public Graph cloneGraph(Graph graph1) throws InterruptedException{
		Graph graph2 = new Graph("Graph2");
		Node currentNode;
		Node currentNodeCopy;
		Node currentNodeNeighborCopy;
		Queue<Node> q = new LinkedList<Node>();
		ArrayList<Node> currentNeiborList;
		currentNode = graph1.nodeList.get(0);
		q.add(currentNode);
		while(!q.isEmpty()){
			currentNode = q.poll();
			if(!nodeMap.containsKey(currentNode)){
				currentNodeCopy = copyNode(currentNode);
				
			}
			else{
				currentNodeCopy = nodeMap.get(currentNode);
			}
			graph2.addNode(currentNodeCopy);
			currentNeiborList = currentNode.getNeighborList();
			if(!currentNeiborList.isEmpty()){
				for(Node i:currentNeiborList){
					if(!nodeMap.containsKey(i)){
						q.add(i);
						currentNodeNeighborCopy = copyNode(i);
						currentNodeCopy.addNeighbor(currentNodeNeighborCopy);
					}
					else{
						currentNodeCopy.addNeighbor(nodeMap.get(i));
						
					}
					
				}
			}
			
		}
		
		
		return graph2;
	}
	public Node copyNode(Node node){
		Node copyNode = new Node(node.getNodeName()+"B");
		nodeMap.put(node, copyNode);
		return copyNode;
	}
	// add a node to nodeList
	public void addNode(Node node){
		nodeList.add(node);
	}
	// print the Graph
	public void printGraph(){
		System.out.println("Graph Name: "+this.getGraphName());
		for (Node i : nodeList) {
			System.out.print("Node: "+i.getNodeName()+" ");
			for(Node j:i.getNeighborList()){
				System.out.print("Neighbor: "+j.getNodeName()+" ");
			}
			System.out.println();
		}
		
	}
}



package Clone_Graph_Part_I;

import java.util.ArrayList;

class Node {
	private String name;
	private ArrayList<Node> neighborList;
	public Node(String name){
		this.name = name;
		neighborList = new ArrayList<Node>();
	}
	public String getNodeName(){
		return this.name;
	}
	public ArrayList<Node> getNeighborList(){
		return this.neighborList;
	}
	public void addNeighbor(Node node){
		this.neighborList.add(node);
	}
}

代码分析:

3个文件,主程序,Graph类,Node类

主程序:初始化Graph1,也就是表示Graph1。然后打印Graph1,克隆Graph1,生成Graph2,打印Graph2。

Graph类:方法有,获得Graph名字,克隆Graph,复制节点,增加节点,打印Graph。

Node类:方法有,获得节点名字,获得邻居列表,增加邻居。

值得注意的就是

1.广度搜索算法,通过Queue这个方法实现.

2.复制过的节点都保存在HASHMAP中,以免重复复制。



主要讲CloneGraph这个方法

1.把Graph1的第一个节点赋予当前节点,也就是起始位置。

2.然后把这个节点加入到Queue中。(可以把Queue队列理解为需要进行操作的节点队列,队列的顺序就是操作的顺序,也就是广度算法的执行顺序)

3.进入While循环,判断队列是否为空,不为空,执行While.

4.取出队列头节点进行操作,复制节点,注意头节点被取出后,意思是取出的同时,也是从队列中移除了此节点。

5Graph2增加所复制的节点

6判断当前节点有没有邻居,有的话,依次对邻居进行操作。

有邻居:

判断邻居被复制过没有

复制过:

把此邻居的复制 连接到 当前节点的复制(比如此邻居A复制是A2,当前节点是B,复制是B2,把A2给B2,通过调用HASHMAP,把保存的地址A2给B的复制B2)


没有复制过:

把此邻居加入队列,复制此邻居,并把此邻居的复制给当前节点的复制(比如此邻居是C复制是C2,当前节点是B,复制是B2,通过nodeCopy复制一个节点,通    过AddNeighbor把C2给B2 )。 

没有邻居:

回到While起点。

下面举个简单的例子:

Node A Neighbor B C

Node B Neighbor A C D

Node C Neighbor A

Node D






模拟程序步骤

获得起始节点A,加入队列。Queue(A)

进入WHILE。队列不为空执行

从队列取出节点A,给currentNode. Queue(); 目前队列就为空了,这里用Queue()简单表示下

判断currentNode是否被复制过

没有复制过:

复制currentNode节点A, currentNodeCopy 就是A2了。

Graph2 增加节点A2 。

判断A的邻居

有邻居B,C 进入FOR

判断B有没有复制过?

没有。节点B加入队列。Queue(B)。

复制节点B,B2 。把B2作为邻居给A2 。

再次回到FOR

判断C有没有复制过?

没有。节点C加入队列。Queue(B,C) 补充一下,队列是先进先出模式

复制节点C,C2。吧C2 作为邻居给A2

回到WHILE,判断队列是否为空,不为空。(当前队列Queue(B,C))

取出队列头节点,B给currentNode.    当前队列Queue(C)

判断B有没有复制过

复制过:

从HASHMAP中调用B2 给currentCopy

Graph2 加入currentCopy B2

获得B的邻居ACD

有邻居 进入FOR

判断邻居A有没有复制过

复制过

把通过HASHMAP获得A2 给 作为邻居给B2。

判断邻居C有没有复制过

复制过

C2 作为邻居给B2

判断邻居D有没有复制过

没有复制过

把D加入队列当前队列Queue(C D)

复制D,把D2作为邻居给B2

回到WHILE 判断队列是否为空 

取出头节点C 当前队列 Queue( D)

判断C有没有复制过

复制过

获取C2 付给 Graph2

获得C的邻居

有邻居A 

判断A复制过没有?

有复制过。 把A2 作为邻居给C2 

回到WHILE 当前队列Queue( D)

有队列

取出头节点D。当前队列Queue( )

判断D复制过没有?

有复制。获取D2给Graph2

获得D的邻居

没有邻居 回到WHILE

判断队列,队列为空。

结束WHILE

返回GRAPH2。完成。


好吧先到这里,如有问题请补充,第一次写有点啰嗦了。争取下次写的格式更美观。





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值