我正在浏览Java中的双向链表,我正在阅读Book中的双向链表中的哨兵。其中指出
为了避免在附近操作时出现一些特殊情况 双向链表的边界,它有助于在 列表的两端:列表开头的标头节点,以及 列表末尾的尾部节点。这些"虚拟"节点是已知的 作为哨兵(或守卫(,他们不存储 主序列
这些特殊情况是什么?为什么我们需要哨兵方法?是强制性的吗?如果我们对双向链表使用常规方法(没有哨兵(,那不会节省这些额外节点的内存吗?当以这种方式以循环方法制作双链表时,我们必须删除哨兵?
维基百科注释简要提到了使用哨兵节点来简化链表的实现。
哨兵节点是位于列表前面的虚拟节点。
在双向链表中,哨兵节点指向列表的第一个和最后一个元素。我们不再需要为列表的头部和尾部保留单独的指针,就像我们对单向链表所做的那样。
我们也不必担心更新头和尾指针,因为正如我们将看到的,如果我们在哨兵节点之后插入,从而将项目添加到列表中,或者在哨兵节点之前插入,从而将项目附加到列表中,则会自动发生这种情况。
我们可以消除用于单链表的容器对象,因为哨兵节点可以跟踪列表中的第一个和最后一个元素。如果我们这样做,那么我们将向用户返回一个指向哨兵节点的指针。
但是,数据结构通常设计有一个容器对象,该容器对象调解数据结构的用户和数据结构的实现之间的通信,因此我们将保留容器对象。
@6502关于哨兵节点如何提供优于NUL的好处的答案非常有帮助。
以下是在双向链接节点列表中删除节点的代码,其中 NULL 用于标记列表的末尾,其中两个指针用于保存第一个和最后一个节点的地址:
// 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;
如您所见,双链表删除了虚拟节点方法,所有特殊情况和所有条件。