【问题标题】:Most efficient way for popping max node from a queue implemented using a linked list? C++从使用链表实现的队列中弹出最大节点的最有效方法? C++
【发布时间】:2025-12-22 14:55:10
【问题描述】:

如果您有一个使用链表实现的先进先出队列,那么弹出具有最高值的节点的最有效方法是什么?

合并排序将是 O(n log n)。 扫描列表将是 O(n)。

任何人都可以提出更有效的方法吗?

队列必须保留fifo排序,它与enqueue和dequeue以通常的方式操作,但有一个额外的方法,例如popMax,它弹出并返回具有最高值的节点。

不需要代码,只需一些想法!谢谢!

【问题讨论】:

  • “需要节点代码” 不,你先展示你的。我们不是代码编写服务!
  • 您可能有两个列表,一个用于fifo,一个用于max。
  • 定义最高效的含义。时间还是空间?最终答案取决于您将如何使用它。
  • 对于基本的需求组合(这在我的职业生涯中经常出现),我总是编写自己的节点指针优先级队列(堆),并有一个节点成员来保存堆的索引位置。然后可以访问任一结构以选择要从两者中删除的项目。
  • @Jarod42,两个列表毫无意义,您正在增加复杂性和存储空间,不是为了节省任何时间,而是将时间从popMax 转移到enqueue

标签: c++ linked-list queue max


【解决方案1】:

popMax 是否足够频繁,以至于将其从 O(N) 更改为 O(logN) 证明了额外的存储空间(每个节点:两个指针加一个索引)和额外的复杂性 AND 从 O 更改入队和出队(1) 到 O(logN) ??

在很多次我解决了这个问题(出于不同的原因和不同的雇主)上面的答案一直是“是”。但这对你来说可能是“不”。所以首先做出这个决定。

对 O(N) 的任何改进都需要能够从主序列的中间移除。如果主序列是一个仅向前的链表,那么现在它需要双向链接:一个额外的指针。

指针堆需要另一个额外的指针(每个节点,但不在节​​点中)。但是随后出队需要能够从堆的中间移除,这需要节点内的索引作为指向其在堆中位置的反向指针。

如果值得所有这些,您可以轻松找到(在线免费)优先级队列/堆的模板版本的源代码,并且应该很明显如何制作堆,对象是 node*less赋予堆的函数比较指向的节点内的值。

接下来,您更改堆源代码(您不简单地使用 std::priority_queue 的原因),以便每次在队列中定位“对象”(意思是 node*)时,它都会执行某种操作回调以通知对象其新索引。

您还需要公开堆代码的一些内部结构。在任何体面版本的堆代码中都有一个要点,其中代码通过检查堆的最后一个元素是否可以正确移动到那里(如果这样做)来处理堆内索引 x 处的孔(缺失元素) ) 或者如果没有将洞的正确孩子移动到洞中并重复那个孩子所在的新洞。通常,该代码不会以 x 作为输入暴露给外部调用者。但是很容易暴露。您的 dequeue 函数需要它才能从堆中删除从列表中删除的相同元素。

为了减少额外的存储空间,但可能需要更多的执行时间(尽管每个函数仍然是 O(logN))。你可以有一堆节点而不是node* 堆。这就是编码这种堆的原因,您应该对通知回调进行通用编码(类似于less)。然后你的双向链表有索引而不是指针(所以增长很健壮),并且通知函数更新前任的前向索引和后继的后退索引。为避免大量特殊套管,您需要有一个完整的循环(可能包括一个虚拟节点)的双链接,而不仅仅是端到端。

我自己还没有处理过细节(因为我在 C++11 后的世界里从来没有重做过任何这些),但我认为上面讨论的 notify 函数的一个更优雅的替代方法是包装对象(将在堆中)在允许移动但不允许复制的包装器中。通知要执行的操作将在移动期间完成。这使得 std::priority_queue 更接近您的需要,但据我了解,它仍然没有在代码中公开用于在任意位置填充孔的关键内部点。

【讨论】:

  • 对不起,应该添加更多细节,是的,节点已经有两个指向(prev 和 next),这是双重的。它还有两个int,ID和优先级(按优先级排序)
  • 因此最优雅的解决方案是将节点本身存储在堆中(按优先级排序),并使用 C++11 移动语义在节点在堆内移动时触发指针修复。
  • 再次抱歉!试图避免任何 STL
  • 这很好,因为 STL 堆或优先级队列无法公开您需要的关键内部方法。因此,无论如何您都需要从头开始重写 std::priority_queue 的重要部分。但是,如果您知道“堆”的含义(而不是 malloc 的含义),那将是相当少量的简单代码。
最近更新 更多