深度复制Linkedlist而不破坏原始列表和额外存储空间(使用ANSI C)

时间:2009-12-17 21:33:50

标签: c algorithm

这个链表不同于普通链表,除了下一个指针之外,它还有另一个指针指向链表中除了它自己之外的另一个节点。

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

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

5 个答案:

答案 0 :(得分:10)

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

#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;
}
  1. 在原始列表中关注->next后,创建一个镜像它的新列表。在旧列表中的每个节点上对->data进行管理,以指向新列表中的相应节点。
  2. 并行浏览两个列表,并使用较早的错位->data来确定->what在新列表中的位置。
  3. 并行浏览两个列表并将->data恢复到原始状态。
  4. 请注意,这是3次线性传递,因此它的时间为O(n),并且它不会使用超出创建新列表所需的任何内存。

答案 1 :(得分:3)

这是一个经过测试的ephemient解决方案的C ++实现,它使用下一个指针在第一次传递中交错节点,而不是为此暂时接管数据。复杂性和空间使用保持不变(O(N)(3遍)和O(1)空间)作为ephemients实现

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;
}

答案 2 :(得分:0)

您可以在每个节点上对搜索进行分支,并在遇到已访问过的节点时将路径重新合并。

答案 3 :(得分:0)

这是@ephemient给出的答案的一个小改进,它不使用额外的what指针。这是Scala中实现的草图。评论假定如下列表:

+-----------+
|           |
|           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
}

答案 4 :(得分:0)

以下是带随机指针的链表深度副本的c#版本: 时间复杂度是具有恒定空间的O(N)(即,仅用于创建深拷贝的空间)(注意具有添加O(n)空间的映射可以实现相同)

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

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

    public LinkedListWithRandomPointerNode DeepCopy()         {             if(this._first == null)             {                 return 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;
        }
    
  4. 其中

    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;
            }