【发布时间】:2019-04-11 16:22:44
【问题描述】:
我非常清楚,当我们想要删除链表中的一个节点(无论是双链还是单链),并且我们必须搜索这个节点时,这个任务的时间复杂度是 O(n),因为我们必须在最坏的情况下遍历整个列表来识别节点。同样,如果我们要删除第 k 个节点,也是 O(k),而且我们还没有对该节点的引用。
人们普遍认为,使用双向链表而不是单链表的好处之一是,当我们有一个要删除的节点的引用时,删除的时间是 O(1)。即,如果要删除节点 i,只需执行以下操作: i.prev.next = i.next 和 i.next.prev = i.prev
据说,只有在您对要删除的节点之前的节点有引用时,单链表中的删除操作是 O(1)。但是,我认为情况不一定如此。如果你想删除节点 i(并且你有对节点 i 的引用),为什么不能只复制 i.next 中的数据,然后设置 i.next = i.next.next?这也将是 O(1),就像在双向链表的情况下一样,这意味着在任何情况下,在双向链表中删除都不会更有效,就 Big-O 而言。当然,如果你要删除的节点是链表中的最后一个节点,这个想法就行不通了。
在比较单链表和双链表时,没有人记得这一点,这让我很烦恼。我错过了什么?
澄清:我在单链接案例中的建议是用下一个节点的数据覆盖您要删除的节点处的数据,然后删除下一个节点。这与删除节点 i 具有相同的预期效果,尽管它本身并不是您正在做的事情。
编辑
我学到了什么:
看来我在某种程度上是正确的。首先,很多人提到我的解决方案并不完整,因为删除最后一个元素是一个问题,所以我的算法是 O(n)(根据 Big-O 的定义)。我天真地建议通过跟踪列表中的“倒数第二个节点”来解决这个问题 - 当然,一旦列表中的最后一个节点第一次被删除,这就会导致问题。一个建议并且似乎确实有效的解决方案是用 NullNode 之类的东西来划分列表的末尾,我喜欢这种方法。
出现的其他问题是引用完整性,以及与从下一个节点复制数据本身相关的时间(即,可能需要昂贵的深度复制)。如果您可以假设您没有其他对象使用您正在复制的节点,并且复制任务本身就是 O(1),那么我的解决方案似乎有效。虽然,在这一点上,使用双向链表也许是值得的:)
【问题讨论】:
-
我的看法是,将“
i的内容设置为i.next的内容”可以完成 如果 a) 内容不'之后不要改变 b)有自动垃圾收集。您不能只使用free(i.next),因为可能有“其他人”知道i.next并在i(概念上)被删除时使用它。同样,复制内容会在发生更改时创建一个不同步的别名。 -
仅当节点具有相同的类型/大小时,覆盖才有效(想想:struct hack,或者更好:VLA)。此外,payload 可以包含指向其他(动态分配的)对象的指针。
-
您可以在列表中添加一个额外的节点来表示列表的结尾而不是空指针让我们将其称为 NullNode,然后您可以检查指针是否等于 NullNode 而不是检查 null .那么对于尾节点,您可以检查 i.next == NullNode 然后简单地说 NullNode = i;并删除 i.next;这样你就可以绕过特殊情况。
-
我喜欢 kingW3 的 NullNode 命题,它似乎解决了“跟踪第 2 个到最后一个节点”解决方案的问题,这根本不好
标签: algorithm data-structures linked-list nodes