深拷贝Linkedlist而不破坏原始列表和额外的存储空间(使用ANSI C)

Posted

技术标签:

【中文标题】深拷贝Linkedlist而不破坏原始列表和额外的存储空间(使用ANSI C)【英文标题】:deep copy Linkedlist without destroying original list and extra storage space (using ANSI C) 【发布时间】:2009-12-17 21:33:49 【问题描述】:

这个链表与普通链表不同的是,除了next指针之外,它还有一个指针指向链表中除自身之外的另一个节点。

那么,在不破坏原始数据和没有额外空间的情况下,深度复制此链表的最佳方法是什么?

我的方法只是做一个 O(n^2) 循环,但应该是更聪明的方法。

【问题讨论】:

第二个指针有什么具体原因吗? 我认为这真的只是为了让问题变得更难,因为这是一个面试问题 【参考方案1】:

这个实现完全未经测试,但想法很简单。

#include <stdlib.h>

struct node 
    struct node *next;
    struct node *what;
    void *data;
;

struct node *copy(struct node *root) 
    struct node *i, *j, *new_root = NULL;

    for (i = root, j = NULL; i; j = i, i = i->next) 
        struct node *new_node = malloc(sizeof(struct node));
        if (!new_node) abort();
        if (j) j->next = new_node;
        else new_root = new_node;
        new_node->data = i->data;
        i->data = new_node;
    
    if (j) j->next = NULL;

    for (i = root, j = new_root; i; i = i->next, j = j->next)
        j->what = i->what->data;

    for (i = root, j = new_root; i; i = i->next, j = j->next)
        i->data = j->data;

    return new_root;

    在原始列表上的-&gt;next 之后,创建一个镜像它的新列表。 Mangle -&gt;data 在旧列表中的每个节点上指向其在新列表中的对应节点。 并行浏览这两个列表,并使用之前修改的-&gt;data 找出-&gt;what 在新列表中的位置。 并行浏览两个列表并将-&gt;data 恢复到其原始状态。

请注意,这是 3 次线性传递,因此它的时间是 O(n),并且它不使用超出创建新列表所需的任何内存。

【讨论】:

这是 O(n^2),对吧?给定一个 O(1) 查找的数据结构(如哈希表),有可能得到 O(n)。 @Anon 我也倾向于使用哈希表,直到我读到“没有多余的空间”【参考方案2】:

这是一个经过测试的 ephemient 解决方案的 C++ 实现,它使用 next 指针在第一遍中交错节点,而不是为此目的临时接管数据。复杂性和空间使用与 ehemients 实现保持相同(O(N)(3 次通过)和 O(1) 空间)

struct Link 
    int data;
    shared_ptr<Link> next;
    weak_ptr<Link> random;
;

shared_ptr<Link> DeepCopy(Link* original) 
    if (!original) return nullptr;

    // 1st Pass:
    // make new nodes and interleave them between the nodes of the original list
    // by changing the next pointers ie:
    //      [1]->[copy of 1]->[2]->[copy of 2]->[3]->[copy of 3] ....
    // during this pass, we will also set the data of new nodes to match the old node
    // being copied.
    Link* node = original;
    while (node) 
        shared_ptr<Link> new_node = make_shared<Link>();
        new_node->data = node->data;
        new_node->next = node->next;
        node->next = new_node;
        node = node->next->next.get(); // skipping the "copy of" node just inserted
    

    // 2nd Pass:
    // now go through and set the random ptr of the new nodes correctly.
    // i refers to a node on the original list and j the matching node on the new
    // list
    shared_ptr<Link> new_head = original->next; // i.e "copy of 1" is head of new list
    for (Link *i = original; i; i=i->next->next.get()) 
        Link *j = i->next.get(); // new nodes interleave original nodes
        j->random = i->random.lock()->next;
    

    // 3rd Pass:
    // Restore the original list
    Link* new_node = new_head.get();
    node = original;
    while (node) 
        node->next = new_node->next;

        if (node->next)
            new_node->next = node->next->next;

        node = node->next.get();
        new_node = new_node->next.get();
    

    return new_head;

【讨论】:

【参考方案3】:

您可以在每个节点处分支搜索,并在遇到已经访问过的节点时将路径重新合并在一起。

【讨论】:

你建议如何跟踪已经访问过的节点? (这很重要。)【参考方案4】:

这是对不使用额外 what 指针的@ephemient 给出的答案的微小改进。这是 Scala 中的实现草图。 cmets 假设如下列表:

+-----------+
|           |
|           v
A --> B --> C----+
^     |     ^    |
|     |     |    |
+-----+     +----+

还有一个Node 喜欢:

class Node 
  var data: Node = null
  var next: Node = null

  def traverse(fn: Node => Unit) 
    var node = this
    while (node != null) 
      fn(node)
      node = node.next
    
  

这里是克隆方法。

def clone(list: Node): Node = 
  if (list == null) return null

  var cloneHead: Node = null

  // first, create n cloned nodes by traversing the original list in O(n) time
  // this step mutates the data pointers of original list
  list traverse  orig =>
    // for each node in original list, create a corresponding cloned node
    val newNode = new Node

    if (orig == list) 
      cloneHead = newNode // store the head node of the cloned list
    

    // The next pointer of cloned node points to the node pointed to by
    // the corresponding orig node's data pointer i.e. A'.next and A'.data points to C
    newNode.next = orig.data

    // The data pointer on orig node points to the cloned node. i.e. A.data points to A'
    orig.data = newNode
  

  // then, fix up the data pointers of cloned list by traversing the original 
  // list in O(n) time
  list traverse  orig =>
    clone = orig.data // since the data pointer on orig node points to 
                      // the corresponding cloned node

    // A'.next points to C and C.data points to C', so this sets A'.data to C'
    // this establishes the data pointers of the cloned nodes correctly
    clone.data = if (clone.next == null) null else clone.next.data
  

  // then, fix up the data pointers of original list and next pointers of cloned list
  // by traversing the original list in O(n) time
  list traverse  orig =>
    clone = orig.data // since the data pointer on orig node still 
                      // points to the corresponding cloned node

    // A.data points to A' and A'.next points to C, so this sets A.data to C, 
    // restoring the original list
    orig.data = if (orig.data == null) null else orig.data.next

    // A.next points to B and B.data points to B', so this sets A'.next to B'
    // since we are working on linked list's next pointers, we don't have to worry
    // about back pointers - A will be handled by this traversal before B, 
    // so we know for sure that that B.data is not "fixed" yet 
    // (i.e. it still points to B') while A'.next is being set
    clone.next = if (orig.next == null) null else orig.next.data
  

  cloneHead

【讨论】:

【参考方案5】:

这里是带有随机指针的链表的深拷贝的 c# 版本: 时间复杂度为 O(N) 且具有恒定空间(即仅用于创建深拷贝的空间)(注意同样可以通过添加 O(n) 空间的 has map 来实现)

(你也可以参考我的博客:http://codingworkout.blogspot.com/2014/07/deep-copy-with-random-pointer.html) 改动要点:

    在第一次迭代中,创建给定原始列表的副本,使原始节点指向副本 在第二次迭代中更新复制列表的随机指针

    分离它们

    public LinkedListWithRandomPointerNode DeepCopy() 如果(this._first == null) 返回空值;

            LinkedListWithRandomPointerNode i1 = this._first, i2 = null;
            while(i1 != null)
            
                //cre new node
                i2 = new LinkedListWithRandomPointerNode();
                i2.e = i1.e;
                i2.next = i1.next;
                //insert it after i1
                i1.next = i2;
                i1 = i2.next;
            
            LinkedListWithRandomPointerNode firstNodeOfCopiedList = this._first.next;
    
            i1 = this._first; i2 = i1.next;
            while(i2 != null)
            
                if(i1.random != null)
                
                    i2.random = i1.random.next;
                
                if(i2.next == null)
                
                    break;
                
                i1 = i2.next;
                i2 = i1.next;
            
    
            i1 = this._first; i2 = i1.next;
            while (i2 != null)
            
                i1.next = i2.next;
                i1 = i1.next;
                if (i1 != null)
                
                    i2.next = i1.next;
                    i2 = i2.next;
                
                else
                
                    i2.next = null;
                    break;
                
            
            return firstNodeOfCopiedList;
        
    

在哪里

public class LinkedListWithRandomPointerNode
    
        public int e;
        public LinkedListWithRandomPointerNode next;
        public LinkedListWithRandomPointerNode random;
    

单元测试

[TestMethod]
        public void LinkedListWithRandomPointerTests()
        
            var n = this.DeepCopy();
            Assert.IsNotNull(n);
            var i1 = this._first; var i2 = n;
            while(i1 != null)
            
                Assert.IsNotNull(i2);
                Assert.IsTrue(i1.e == i2.e);
                if(i1.random != null)
                
                    Assert.IsNotNull(i2.random);
                    Assert.IsTrue(i1.random.e == i2.random.e);
                
                i1 = i1.next;
                i2 = i2.next;
            
        
        LinkedListWithRandomPointerNode _first = null;
        public LinkedListWithRandomPointer()
        
            this._first = new LinkedListWithRandomPointerNode()  e = 7 ;
            this._first.next = new LinkedListWithRandomPointerNode()  e = 14 ;
            this._first.next.next = new LinkedListWithRandomPointerNode()  e = 21 ;
            this._first.random = this._first.next.next;
            this._first.next.next.random = this._first;
        

【讨论】:

以上是关于深拷贝Linkedlist而不破坏原始列表和额外的存储空间(使用ANSI C)的主要内容,如果未能解决你的问题,请参考以下文章

Python中的深拷贝和浅拷贝区别

JSON对象的深拷贝和浅拷贝

python深拷贝和浅拷贝的区别

[随笔重写] Python3 的深拷贝与浅拷贝

copy与deepcopy

python的赋值,深拷贝和浅拷贝的区别