我正在浏览Java中的双向链接列表,我从Book的双向链接列表中了解了Sentinels。哪个指出


  为了避免某些特殊情况,在附近进行操作时
  双链列表的边界,它有助于在以下位置添加特殊节点
  列表的两端:列表开头的标头节点,以及
  列表末尾的预告片节点。这些“虚拟”节点是已知的
  作为前哨(或后卫),并且它们不存储
  主序列


这些特殊情况是什么?为什么我们需要哨兵方法?它是强制性的吗?如果我们对双链表使用普通方法(没有哨兵),是否会节省这些额外节点的内存?当以循环方式制作双链表时,我们必须删除前哨吗?

最佳答案

Wikipedia笔记简要提到了使用前哨节点来简化链表的实现。

前哨节点是位于列表前面的虚拟节点。

在双向链接列表中,前哨节点指向列表的第一个和最后一个元素。我们不再需要为列表的开头和结尾保留单独的指针,就像我们必须处理单链接列表一样。

我们也不必担心更新头和尾指针,因为正如我们将看到的,如果我们在前哨节点之后插入,从而在列表前添加一个项目,或者在前哨节点之前插入,从而附加一个列表中的项目。

我们可以消除用于单链列表的容器对象,因为前哨节点可以跟踪列表中的第一个和最后一个元素。如果这样做,则将向用户返回指向前哨节点的指针。

但是,通常使用容器对象设计数据结构,该容器对象会介导数据结构的用户与数据结构的实现之间的通信,因此我们将保留容器对象。

@ 6502对How does a sentinel node offer benefits over NULL?的回答非常有帮助。

以下是在双向链接的节点列表中删除节点的代码,其中NULL用于标记列表的结尾,并且两个指针first和last用于保存第一个和最后一个节点的地址:

// Using NULL and pointers for first and last
if (n->prev) n->prev->next = n->next;
        else first = n->next;
if (n->next) n->next->prev = n->prev;
        else last = n->prev;


这是相同的代码,相反,这里有一个特殊的虚拟节点来标记列表的结尾,并且列表中第一个节点的地址存储在特殊节点的下一个字段中,而列表中的最后一个节点是存储在特殊虚拟节点的prev字段中:

// Using the dummy node
n->prev->next = n->next;
n->next->prev = n->prev;


节点插入也存在相同的简化形式。例如,在节点x之前插入节点n(具有x == NULL或x ==&dummy表示在最后位置插入),代码为:

// Using NULL and pointers for first and last

n->next = x;
n->prev = x ? x->prev : last;
if (n->prev) n->prev->next = n;
        else first = n;
if (n->next) n->next->prev = n;
        else last = n;




// Using the dummy node
n->next = x;
n->prev = x->prev;
n->next->prev = n;
n->prev->next = n;


如您所见,对于双链表,所有特殊情况和所有条件都删除了虚拟节点方法。

关于java - 具有双链表的前哨方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58049478/

10-12 15:14