从链接列表中删除项目的正确方法


10

在本次Slashdot采访中,引用了Linus Torvalds的话:

我见过太多的人通过跟踪“上一个”条目来删除单链接列表条目,然后删除该条目,例如

如果(prev)
  prev-> next = entry-> next;
否则
  list_head = entry-> next;

每当我看到这样的代码时,我都会说“这个人不理解指针”。可悲的是,这很普遍。

理解指针的人只使用“指向入口指针的指针”,并使用list_head的地址对其进行初始化。然后,当他们遍历列表时,只需执行“ * pp = entry-> next”,就可以删除条目而无需使用任何条件。

自从十年前在大学中介绍C以来,作为PHP开发人员,我从未碰过任何东西。但是,我觉得这是我至少应该熟悉的一种情况。Linus在说什么?老实说,如果我被要求实现一个链表并删除一个项目,那么上面的“错误”方法就是我要解决的方法。我需要了解Linus所说的最佳代码吗?

我在这里而不是在Stack Overflow上问,因为我在生产代码中实际上没有这个问题。


1
他的意思是,当您需要存储的位置prev而不是存储整个节点时,只需存储的位置prev.next,因为这是您唯一感兴趣的内容。指针的指针。而且,如果这样做,就可以避免愚蠢的行为if,因为从现在起,您不必再list_head从节点外部成为指针了。然后,指向列表开头的指针在语义上与指向下一个节点的指针相同。
Ordous

@Ordous:知道了,谢谢。为什么发表评论?这是一个简洁,清晰和启发性的答案。
dotancohen

@Ordous该代码段中涉及的所有内容都是指针,因此他的观点与存储整个节点与存储指向该节点的指针没有任何关系。
Doval

Answers:


9

使用我的L331 MS Paint技能:

在此处输入图片说明

原始解决方案是通过指向Nodes curr。在这种情况下,请检查后面的下一个节点curr是否具有删除值,如果是,则重置curr节点next指针。问题是没有节点指向列表的开头。这意味着必须有特殊情况才能对其进行检查。

Linus(可能)提出的建议不是保存指向当前检查的节点的指针,而是保存指向当前节点(标记为pp)的指针的指针。操作相同-如果pp指针指向具有正确值的节点,则将pp指针重置。

区别出现在列表的最开始。虽然没有Node指向列表的开头,但是实际上有一个指向列表的开头的指针。指向一个节点的指针就像另一个节点的next指针一样。因此,列表的开头不需要特殊的子句。


啊,我现在明白了。...您每天都在学习新知识。
劳伦斯·艾洛2015年

1
我认为您正确地描述了事物,但我建议正确的解决方案是使用list_head指向next第一个实际数据项的节点(并已prev初始化为该虚拟对象)指向某个事物。我不喜欢prev指向不同类型的东西的想法,因为这些技巧可能会通过别名引入“未定义行为”,并使代码不必要地对结构布局敏感。
supercat

@supercat就是这样。prev它不必指向节点,而是指向指针。它总是指向相同类型的东西,即指向Node的指针。您的建议基本上是相同的- prev指向“带有next节点”的东西。如果您放弃外壳程序,您只会得到初始list_head指针。换句话说,仅通过具有指向下一个节点的指针来定义的内容在语义上等同于指向节点的指针。
Ordous

@Ordous:这是有道理的,尽管它以此为前提,list_head并且next将拥有相同的“种类”指针。也许在C中不是问题,但是在C ++中可能是有问题的。
2015年

@supercat我一直认为这是链表的“规范”表示,与语言无关。但是我没有足够的能力来判断它是否真的在C和C ++之间产生了区别,以及那里的标准实现是什么。
2015年

11

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

代码示例

// ------------------------------------------------------------------
// Start by pointing to the head pointer.
// ------------------------------------------------------------------
//    (next_ptr)
//         |
//         v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[....]
Node** next_ptr = &list->head;

// ------------------------------------------------------------------
// Search the list for the matching entry.
// After searching:
// ------------------------------------------------------------------
//                                  (next_ptr)
//                                       |
//                                       v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[next]
while (*next_ptr != to_remove) // or (*next_ptr)->val != to_remove->val
{
    Node* next_node = *next_ptr
    next_ptr = &next_node->next;
}

// ------------------------------------------------------------------
// Dereference the next pointer and set it to the next node's next
// pointer.
// ------------------------------------------------------------------
//                                           (next_ptr)
//                                                |
//                                                v
// [head]----->[..]----->[..]----->[..]---------------------->[next]
*next_ptr = to_remove->next;

如果我们需要一些逻辑来销毁该节点,则只需在最后添加一行代码即可:

// Deallocate the node which is now stranded from the list.
free(to_remove);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.