Algorithm 什么时候双链表比单链表更有效?

Algorithm 什么时候双链表比单链表更有效?,algorithm,linked-list,Algorithm,Linked List,在今天的一次采访中,我被问到了这个问题 除了回答倒序和前后遍历外,面试官还不断强调其中的一些“基本点”。我放弃了,当然在面试后做了一些调查。在双链表中插入和删除似乎比单链表更有效。我不太清楚如何才能更有效地使用双链接列表,因为显然需要更改更多的引用。 有人能解释背后的秘密吗?老实说,我做了很多研究,但我没有理解,主要的问题是,双链接列表仍然需要O(n)搜索。如果要删除链接列表中的某个元素,则需要将上一个元素链接到下一个元素。使用双链接列表,您可以随时访问这两个元素,因为您拥有指向这两个元素的链接

在今天的一次采访中,我被问到了这个问题

除了回答倒序和前后遍历外,面试官还不断强调其中的一些“基本点”。我放弃了,当然在面试后做了一些调查。在双链表中插入和删除似乎比单链表更有效。我不太清楚如何才能更有效地使用双链接列表,因为显然需要更改更多的引用。
有人能解释背后的秘密吗?老实说,我做了很多研究,但我没有理解,主要的问题是,双链接列表仍然需要O(n)搜索。

如果要删除链接列表中的某个元素,则需要将上一个元素链接到下一个元素。使用双链接列表,您可以随时访问这两个元素,因为您拥有指向这两个元素的链接


这假设您已经有一个指向需要删除的元素的指针,并且不涉及任何搜索。

在单链接列表中插入显然不太容易,只要您满足于总是在某个已知元素的开头或后面插入。(也就是说,不能在已知元素之前插入,但请参见下文。)

另一方面,删除更为棘手,因为您需要在要删除的元素之前知道该元素

实现这一点的一种方法是使deleteapi与要删除的元素的前一个一起工作。这反映了insertapi,它采用的元素将是新元素的前身,但它不是很方便,而且很难记录。不过,这通常是可能的。一般来说,您通过遍历列表来获得列表中的元素

当然,您可以从一开始就搜索列表以查找要删除的元素,这样您就知道它的前身是什么。这假设deleteapi包含列表的头部,这也很不方便。而且,搜索速度慢得令人难以置信

几乎没有人使用的方法,但实际上非常有效,就是定义一个单链表迭代器作为指向迭代器当前目标之前元素的指针。这很简单,只有一个间接寻址比直接使用指向元素的指针慢,并且使插入和删除都很快。缺点是删除一个元素可能会使列出元素的其他迭代器失效,这很烦人。(它不会使被删除元素的迭代器无效,这对于删除某些元素的遍历来说很好,但这并没有太多补偿。)


如果删除并不重要,可能是因为数据结构是不可变的,那么单链表提供了另一个真正有用的特性:它们允许结构共享。一个单链表很可能是多个头的尾巴,这对于一个双链表来说是不可能的。因此,单链表传统上是函数式语言选择的简单数据结构

以下是我对双链接列表的看法:

  • 您已准备好访问\insert的两端

  • 它可以同时作为队列和堆栈工作

  • 节点删除不需要额外的指针

  • 您可以应用爬山遍历,因为您已经可以访问两端

  • 如果您正在存储数值,并且列表已排序,则可以为中值保留一个指针/变量,然后使用统计方法可以实现高度优化的搜索操作


以下是一些代码,让我更清楚地了解。。。具有:

class Node{
      Node next;
      Node prev;
}
删除单个链接列表中的节点-O(n)-

您不知道哪个是前一个节点,因此必须遍历列表,直到找到它:

deleteNode(Node node){
    prevNode = tmpNode;
    tmpNode = prevNode.next;

    while (tmpNode != null) {
        if (tmpNode == node) {
            prevNode.next = tmpNode.next;
        }
        prevNode = tmpNode;
        tmpNode = prevNode.next;
    }
}
删除双链接列表中的节点-O(1)-

您只需更新如下链接:

deleteNode(Node node){
    node.prev.next = node.next;
    node.next.prev = node.prev;
}

“除了回答颠倒列表以及向前和向后遍历之外,还有一些“基本的”问题。”


似乎没有人提到过:在双链接列表中,只要有一个指向已删除元素的指针,就可以重新插入已删除元素。参见Knuth的舞蹈链接论文。我认为这是非常基本的。

单链表vs双链表vs动态数组

Doubly Linked list is more effective than the Singly linked list when the location of the element to be deleted is given.Because it is required to operate on "4" pointers only & "2" when the element to be deleted is at the first node or at the last node.


struct  Node {

       int  Value;
       struct Node  *Fwd;
       struct Node  *Bwd;
);


Only the below line of code will be enough to delete the element ,If the element to be deleted is not in the first or last node.

X->Bwd->Fwd = X->Fwd; X->Fwd->Bwd = X->Bwd ;
在比较三种主要数据结构时,从时间复杂性来看,双链表在所有主要任务和操作中都是最有效的。对于双链表,除了仅按索引访问外,它在所有操作中都以固定时间运行,其中它以线性时间(n)运行,因为它需要迭代每个节点以获得所需的索引。当涉及到插入、移除、第一、最后、连接和计数时,双链表以恒定时间运行,而动态数组以线性时间(n)运行

就空间复杂度而言,动态数组只存储元素,因此时间复杂度为常数;单链表存储每个元素的后续元素,因此空间复杂度为线性(n);最糟糕的是,双链表存储每个元素的前置和后续元素,因此空间复杂度为线性(2*n)


除非您的资源/空间非常有限,否则动态数组或单链接列表可能会更好,但是,现在,空间和资源越来越丰富,因此双链接列表更适合于占用更多空间。

我想如果你已经知道了尾部,那么你可以在末尾插入元素。其他答案也不错,但我选择了这个答案,因为它给了我更多信息。+1对于@里希。另一件事-在双链接列表中搜索更容易。当您将索引传递给元素时