Java中的深度复制

Java中的深度复制,java,constructor,copy,copy-constructor,Java,Constructor,Copy,Copy Constructor,我有一个Java类,节点如下: class Node { public ArrayList<Node> nbrs; } public Node( Node n ) { for( Node curr : n.nbrs ) n.nbrs.add( new Node( curr )); } 此函数应执行以curr为根的整个图形的深度复制,并返回curr的等效副本 我尝试在类节点中实现副本构造函数,如下所示: class Node { publi

我有一个Java类,节点如下:

class Node
{
    public ArrayList<Node> nbrs;
}
public Node( Node n )
{
    for( Node curr : n.nbrs )
        n.nbrs.add( new Node( curr  ));
}
此函数应执行以curr为根的整个图形的深度复制,并返回curr的等效副本

我尝试在类节点中实现副本构造函数,如下所示:

class Node
{
    public ArrayList<Node> nbrs;
}
public Node( Node n )
{
    for( Node curr : n.nbrs )
        n.nbrs.add( new Node( curr  ));
}
现在,我在复制函数中复制节点n

但我发现,当图形包含循环时,此代码会无限地运行

任何关于我应该如何克服这个问题的帮助


PS:这是我朋友面临的一个面试问题,因此类节点不能再包含任何变量

如果节点类有一个
父类
,您就可以通过这种方式检查无限递归。但事实并非如此。因此,在克隆操作期间,您需要保持一些状态,这是一个
集合
,包含当前递归到的节点。拒绝进入已在
集中的节点

如果该节点类有一个
父节点
,则可以通过这种方式检查无限递归。但事实并非如此。因此,在克隆操作期间,您需要保持一些状态,这是一个
集合
,包含当前递归到的节点。拒绝进入已在
集中的节点

标准技巧是首先创建所有新节点并将它们存储在地图中(从旧节点到新节点)。然后在第二次遍历所有节点时,添加所有边(通过添加到
n.nbrs.add
)。

标准技巧是首先创建所有新节点并将它们存储在地图中(从旧节点到新节点)。然后在第二次遍历所有节点时,添加所有边(通过添加到
n.nbrs.add
)。

考虑使节点对象不可变。在这种情况下,使用共享实例不会造成任何伤害。

请考虑使节点对象不可变。在这种情况下,使用共享实例不会造成任何伤害。

将要复制的旧节点和新节点之间的映射保存在允许基于标识检索元素的数据结构中(即,当
=
运算符返回true时检索对象)。这方面的一个例子是。如果创建新节点,则将其保存到数据结构中


在从以前的ony创建新的
节点之前,请尝试从数据结构中检索节点。如果已经有这样的节点,则将检索到的节点添加到父节点。如果没有这样的节点,则继续创建(并添加它)。

将要复制的旧节点和新节点之间的映射保存在允许基于标识检索元素的数据结构中(即,如果
=
操作符返回true,则检索对象)。这方面的一个例子是。如果创建新节点,则将其保存到数据结构中


在从以前的ony创建新的
节点之前,请尝试从数据结构中检索节点。如果已经有这样的节点,则将检索到的节点添加到父节点。如果没有这样的节点,则继续创建一个(并添加它)。

如果可以修改
节点
类并使其
可序列化
,则可以序列化/反序列化对象并获得新的对象图

示例代码说明了这一点:

class Node implements Serializable 
{
    public List<Node> nbrs = new ArrayList<Node>();
}

Node n1 = new Node();
Node n2 = new Node();
Node n3 = new Node();
n1.nbrs.add(n2);
n2.nbrs.add(n1);
n2.nbrs.add(n3);
n3.nbrs.add(n2);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream dos = new ObjectOutputStream(baos);
dos.writeObject(n1);
dos.writeObject(n2);
dos.writeObject(n3);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);

Node n4 = (Node) ois.readObject();
Node n5 = (Node) ois.readObject();
Node n6 = (Node) ois.readObject();
类节点实现可序列化
{
public List nbrs=new ArrayList();
}
节点n1=新节点();
节点n2=新节点();
节点n3=新节点();
n1.NBR.添加(n2);
n2.NBR.添加(n1);
n2.NBR.添加(n3);
n3.NBR.添加(n2);
ByteArrayOutputStream bas=新的ByteArrayOutputStream();
ObjectOutputStream dos=新的ObjectOutputStream(BAS);
dos.writeObject(n1);
dos.writeObject(n2);
dos.writeObject(n3);
ByteArrayInputStream bais=新的ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=新ObjectInputStream(BAI);
节点n4=(节点)ois.readObject();
节点n5=(节点)ois.readObject();
节点n6=(节点)ois.readObject();

在此阶段,您将拥有一组新的
节点
对象,这些对象可以正确地相互引用。

如果您可以修改
节点
类并使其可序列化
,则可以序列化/反序列化对象并获得一个新的对象图

示例代码说明了这一点:

class Node implements Serializable 
{
    public List<Node> nbrs = new ArrayList<Node>();
}

Node n1 = new Node();
Node n2 = new Node();
Node n3 = new Node();
n1.nbrs.add(n2);
n2.nbrs.add(n1);
n2.nbrs.add(n3);
n3.nbrs.add(n2);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream dos = new ObjectOutputStream(baos);
dos.writeObject(n1);
dos.writeObject(n2);
dos.writeObject(n3);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);

Node n4 = (Node) ois.readObject();
Node n5 = (Node) ois.readObject();
Node n6 = (Node) ois.readObject();
类节点实现可序列化
{
public List nbrs=new ArrayList();
}
节点n1=新节点();
节点n2=新节点();
节点n3=新节点();
n1.NBR.添加(n2);
n2.NBR.添加(n1);
n2.NBR.添加(n3);
n3.NBR.添加(n2);
ByteArrayOutputStream bas=新的ByteArrayOutputStream();
ObjectOutputStream dos=新的ObjectOutputStream(BAS);
dos.writeObject(n1);
dos.writeObject(n2);
dos.writeObject(n3);
ByteArrayInputStream bais=新的ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=新ObjectInputStream(BAI);
节点n4=(节点)ois.readObject();
节点n5=(节点)ois.readObject();
节点n6=(节点)ois.readObject();

在此阶段,您将有一组新的
节点
对象,它们正确地相互引用。

递归中没有基本情况,只有没有邻居的节点。

Daniel Earwicker建议使用
集合
,以确保不会两次添加同一个邻居。听起来不错,但是我们如何判断节点是否在集合中?equals的默认实现实际上只是==所以没有两个节点被认为是相等的。Set contains方法依赖于equals来确定对象是否已添加到集合中。我们向节点冷添加一个id字段,然后通过检查id相等性来实现
布尔相等(node other)
。这将使集合解决方案起作用。

递归中没有基本情况,只有没有邻居的节点。
Daniel Earwicker建议使用
集合
,以确保不会两次添加同一个邻居。听起来不错,但是我们如何判断节点是否在集合中?equals的默认实现实际上只是==所以没有两个节点被认为是相等的。集合contains方法依赖于等于determini