如何为基于最小堆的优先级队列实现O(logn)减少键操作?

jat*_*iou 48 algorithm heap priority-queue decrease-key

我正在开发一个演示Djikstra算法的应用程序,并且要使用它,我需要在我的元素值减少时恢复堆属性.

关于复杂的问题是,当算法改变的元素的值,该元素的索引用于优先级队列中的内部结构(堆在这种情况下)是未知的.因此,我之前需要进行O(n)搜索,以便恢复索引,然后才能对其执行实际的减少键.

而且,我不确定操作所需的实际代码.我在这里使用D-Heap 作为我的优先级队列.伪代码会有所帮助,但我更喜欢Java中的一个例子,说明应该如何做.

ptk*_*vsk 33

您可以执行以下操作:在堆中存储将堆映射到堆索引的散列映射.然后你应该扩展你常用的堆逻辑:

on Swap(i, j): 
    map[value[i]] = j; 
    map[value[j]] = i;
Run Code Online (Sandbox Code Playgroud)
on Insert(key, value): 
    map.Add(value, heapSize) in the beginning;
Run Code Online (Sandbox Code Playgroud)
on ExtractMin: 
    map.Remove(extractedValue) in the end;
Run Code Online (Sandbox Code Playgroud)
on UpdateKey(value, newKey): 
    index = map[value]; 
    keys[index] = newKey; 
Run Code Online (Sandbox Code Playgroud)

BubbleUp(index)在的情况下DecreaseKey,以及BubbleDown/Heapify(index)在以下情况下IncreaseKey,以恢复最小堆属性.

这是我的C#实现:http://pastebin.com/kkZn123m

在恢复堆属性时,Insert和ExtractMin调用Swap log(N)次.而你正在向Swap添加O(1)开销,因此两个操作都保持为O(log(n)).UpdateKey也是log(N):首先在O(1)的hashmap中查找索引,然后像在Insert/ExtractMin中那样恢复O(log(N))的堆属性.

重要说明:使用索引查找值将要求它们是唯一的.如果您对此条件不满意,则必须向键值对添加一些唯一ID,并维护此唯一ID和堆索引之间的映射,而不是值索引映射.但是对于Dijkstra而言,它不是必需的,因为您的值将是图形节点,并且您不希望优先级队列中有重复的节点.

  • 你也可以使用一个数组,使得`array [vertex id] = heap index`或`-1`表示缺席.它可能需要更多内存,但它比hashmap更快更简单(没有调整大小,冲突).并且内存并不重要,因为你必须将图形保留在数组中的某个位置,这样就可以为每个顶点添加另一个`int`. (2认同)
  • @Ciro - 您所描述的是直接访问哈希表,但它的使用要求您的每个n元素"哈希"到0和n-1之间的唯一值.如果您的顶点没有"顶点id",并且您不能或不想动态地向顶点添加属性,则不能使用vanilla数组.如果您可以添加这样的属性,那么最好只添加"堆索引"属性.哈希表是这里的通用解决方案,O(1)操作的摊销时间,并且不会有任何大小调整. (2认同)

Ric*_*ard 19

根据这个问题,没有必要使用减小键方法来实现Dijkstra算法.

您可以根据需要多次将项目添加到优先级队列,并跟踪您访问过的节点以清除重复项.第一次通过将节点弹出队列实际访问节点时,您已找到该节点的最短路径,并且可以忽略优先级队列上将来出现的所有节点.

在优先级队列上有许多额外的节点并不是什么大问题,因为它是一个O(log N)结构.(对于100万个项目,您需要进行大约20次比较,对于10亿个项目,需要进行30次比较.)

编辑:稍后回答这个问题,我对我的回答感到有点失望:除非你以后做一些特殊的操作,否则所有这些事情都必须从队列中解脱出来.像生活中的许多事情一样,它取决于你如何管理你的记忆以及与此相关的成本.但一般意义仍然是:即使可能需要减少密钥也是不必要的.

  • 这种方式仍然得到O(mlogn)的渐近时间复杂度.为什么?在最糟糕的情况下,我们从堆中弹出m次,然后推送到堆中m次.因为堆增长到最大大小m,所以每个操作都是O(logm).所以总共我们有一个O(mlogm + mlogm)= O(mlogm)的时间.但是m = O(n ^ 2)(握手引理),所以我们有O(mlog(n ^ 2))= O(mlogn)时间.对于大图,减小键几乎没有区别.实际上,在实现减少密钥时增加的难度是否值得任何非渐近时间的好处是值得商榷的.然而,该空间确实从O(m)减少到O(n). (4认同)
  • 你有点失望,我有点惊讶.在实际操作中,没有那么多"所有这些东西"表现会明显降低.在http://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-using-priority_queue-stl/上有一篇很好的文章,它在使用C++ STL时采用了这种方法. (3认同)
  • @Richard这是一个实用的解决方案.我们在应用程序的许多地方使用它,并且我们维护一个跟踪访问节点的单独的unordered_set.当我们查看每个插入的密钥减少密钥的复杂性和开销时,您的解决方案会更好. (3认同)