您如何更新priority_queue中的值,或者是不是有另一种方法可以在c ++中更新堆中的键
Posted
技术标签:
【中文标题】您如何更新priority_queue中的值,或者是不是有另一种方法可以在c ++中更新堆中的键【英文标题】:How do you update values in priority_queue, or is there another way to update keys in heaps in c++您如何更新priority_queue中的值,或者是否有另一种方法可以在c ++中更新堆中的键 【发布时间】:2020-03-05 04:48:59 【问题描述】:当我注意到 Djikstra 的算法时,我可以在 O(logn) 时间内更新堆中的键(使用 n 个键)(pseudocode 中的最后一行)。如何在 C++ 中更新堆中的键,priority_queues 中是否有任何方法可以做到这一点?还是我必须编写自己的堆类才能像这样在 O(logn) 中实现更新?
编辑 1: 澄清我的需求 - 对于具有 n 个元素的二进制堆 - 1) 应插入新值并在 O(logn) 中查找并弹出最小值 2) 应该更新 O(logn) 中已经存在的键
我尝试想出一种方法来使用 make_heap、push_heap、pop_heap 和 John Ding 建议的用于更新的自定义函数来实现这一点。
但是我在制作函数时遇到了问题,我首先需要在堆中找到键的位置。在堆中的 O(logn) 下执行此操作需要查找数组以查找堆中键的位置,see here(我不知道任何其他方式)。但是,当我调用 push_heap 或 pop_heap 时,这些查找表不会更新。
【问题讨论】:
您实际上是在问“如何更改priority_queue
中已有元素的优先级?”或者更抽象地说“如果我需要更改队列中元素的优先级,我应该使用哪个 C++ 容器?”
对,我如何在 O(logn) 时间内为具有 n 个元素的 priority_queue 执行此操作?如果不可能,您如何建议我以不同的方式进行。我希望更新 priority_queue 中已经存在的元素的键。
您需要对此进行基准测试,以了解它在 您的 特定用例中的工作方式,但一些排序算法在几乎排序的列表上运行得非常快,所以如果您是只需更改少数条目,您就可以使用 list
并根据任何更改对其进行排序。
您也可以remove the element 并重新注入它,或者花哨并创建一个reemplace()
函数来为您执行此操作。
在几乎排序的列表中排序至少需要 O(n) 时间,对吗?您需要将其移回原位。
【参考方案1】:
您可以使用 priority_queue
优化 dijktra 算法。它由二进制堆实现,您可以在O(logN)
时间内弹出顶部或推入元素。但是由于priority_queue
的封装,你不能修改任何元素的key(更准确地说是减少key)。
所以我们的方法是将多个元素推入堆中,不管我们是否有多个元素指向同一个节点。
例如,当
Node N : distance = 30, GraphNode = A
(其中A表示图中的一个节点,N表示堆中的一个节点)
已经在堆中,那么当我们尝试放松节点N时,使用priority_queue
无法帮你做这样的操作:
decrease_key_to(N, 20)
通过减少键可以使堆总是包含少于N个元素,但priority_queue
无法实现
我们可以用它做的是在堆中添加另一个节点:
Node N2 : distance = 20, GraphNode = A
push N2 into the heap
对应priority_queue::push
所以你可能需要自己实现一个支持decrease_key
的二叉堆,或者在网上找一个实现,并存储一个指向堆中每个元素的指针表,以便通过图中的节点知道访问元素。
作为扩展,使用斐波那契堆甚至可以让decrease_key
更快,这就是Dijkstra的极致,哈哈:)
我的回答的上一个版本的问题:
我们无法定位使用push_heap
推入堆的元素。
【讨论】:
约翰,我目前的 djikstra 实现是你在这里提到的第一个。您能否补充一下您将如何使用 make_heap、push_heap 和 pop_heap 来实现 reduce_key(x, new_val)。我们会不会因为 push_heap 和 pop_heap 只在数组末尾起作用而被卡住。 我不知道斐波那契堆,我会检查一下。 Djikstra 的复杂程度如何? 我想你的意思是,我们可以实现其他人已经存在的 decrese_key 方法,对吧。谢谢。 @NUMBART 可以简单的在 Github 上搜索基于堆的数组或者什么的,只需要复制decrease_key
函数并使用标准库来构建、推送、弹出堆。如果我没记错的话,我估计使用priority_queue
、二叉堆和decrease_key
、斐波那契堆的复杂度分别是O(ElgE)
、O(ElgN)
、O(NlgN) amortized
。
@NUMBART 我没有看到你的问题。对不起,“我们不会因为 push_heap 和 pop_heap 只在数组末尾工作而被卡住吗?”。不。decrese_key
不需要堆是基于节点的。【参考方案2】:
为了做到这一点,您需要的不仅仅是priority_queue
提供的:您需要知道要更新的元素在底层容器中的何处。例如,在二叉堆中,您需要知道要更改其优先级的元素的位置。有了这个,您可以更改元素的优先级,然后通过向上或向下冒泡修改的元素来恢复 O(log n) 中的堆属性。
对于 Dijkstra,如果我没记错的话,称为斐波那契堆的东西比二叉堆更有效。
【讨论】:
我一定会检查斐波那契堆,正如两个答案所暗示的那样。此外,tadman 在remove element 上分享了此链接。我对实现并不完全确定,但是您认为可以在 log(n) 中将其删除然后重新插入。 对不起,我也不确定。一般来说,您绝对可以在 O(log(n)) 时间内实现删除,但我不清楚建议的实现是否这样做。【参考方案3】:不幸的是,std::priority_queue
不支持更新堆中的条目,但您可以使堆中的条目无效并等待它们渗透到最终可以删除的根。因此,您无需更改现有条目,而是将其无效并插入另一个具有新优先级的条目。您是否可以忍受无效条目填满堆的后果,由您来判断。
有关这在实践中如何运作的想法,请参阅here。
【讨论】:
以上是关于您如何更新priority_queue中的值,或者是不是有另一种方法可以在c ++中更新堆中的键的主要内容,如果未能解决你的问题,请参考以下文章