深拷贝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;
-
在原始列表上的
->next
之后,创建一个镜像它的新列表。 Mangle ->data
在旧列表中的每个节点上指向其在新列表中的对应节点。
并行浏览这两个列表,并使用之前修改的->data
找出->what
在新列表中的位置。
并行浏览两个列表并将->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)的主要内容,如果未能解决你的问题,请参考以下文章