使用 hashmap 实现优先队列

Posted

技术标签:

【中文标题】使用 hashmap 实现优先队列【英文标题】:Implementing Priority queue using hashmap 【发布时间】:2014-08-13 20:48:39 【问题描述】:

这可能是一个非常幼稚的问题或没有意义。但我真的对优先队列的实现感到困惑。 为什么我们不能使用以键为优先级、以值作为具有该优先级的数据的哈希图。 我在这里缺少一些基本的东西吗? 例如: 如果 A 的优先级为 1,B 为 3,c 为 4 我们可以简单地将 1,3,4 作为键,A,B,C 作为 hashmap 的值,分别用于优先级队列实现。 请清除我的逻辑

【问题讨论】:

因为HashMaps 没有被排序,所以你需要一些其他方法来确定什么是“下一个”。 请注意,PQ 的可能实现是使用 BST。那不过是一个排序的映射(SortedMap 在 java 中)。这里的排序顺序对于 PQ 的规范是必不可少的。 如果其中一个答案对您有所帮助,请接受它作为正确答案。 【参考方案1】:

优先级队列的目的是能够最快地访问最高优先级的元素,而 HashMap 的目的是允许您将键映射到值,因此可以在 O(1) 时间复杂度内访问这些元素。

O(1) 时间复杂度来自 Hash 函数,该函数为任何输入计算 HashMap 中的键或“索引”。但是哈希函数和优先级队列的结合变得复杂,因为HashMap是根据哈希函数分配key的,而优先级队列是根据元素的优先级分配key的,所以在结合的时候,相互矛盾。

【讨论】:

【参考方案2】:

priorityQueue 使您的数据保持排序并维护该结构,如果您要遍历priorityQueue,它将以您实现其构造函数的方式一致地返回您的数据。 HashMap 不保留插入顺序。如果你试图从你的 hashmap 返回所有元素,你会得到不一致的结果。

除此之外,priorityQueue 使用最小/最大堆来保持数据有序,因此,任何插入/删除都是 Log(n),因为它必须从 log(n) 级别比较节点以维护 priorityQueue 属性。其中 n 是队列中的数据数。

也许您可以使用linkedHashMap 来保留数据的顺序,因为它使用双重linkedList 结构。但是,对于 getMax() 等关键操作,运行时比priortyQueue 差。 priorityQueue 将在 O(log(n)) 时间内完成,而linkedHashMap 将花费 O(n) 时间,因为现在您必须遍历整个linkedList 以找到最大/最小值。

希望对你有帮助

【讨论】:

【参考方案3】:

该实现的问题在于它没有利用 HashMap 功能轻松访问给定键的任何元素。由于您正在创建队列,因此您以后不会有优先级 1、3、4,而只需要找到地图的最高优先级,必须完全迭代才能做到这一点!

地图的工作原理是这样的:它创建一个巨大的数组,所有位置都包含空值(非常非常大)。然后,它使用公式获取您的密钥,通常是字符串或字符串表示形式,并将其转换为数字。一个简单的方法是对每个字母的字符代码求和,尽管这对地图来说是一个糟糕的选择,您可以稍后查找原因。然后,它用数组的模数(余数)大小(例如,Java 中的 n % 大小)限制该数字,使其成为数组中的有效位置,然后将元素放在那里。这样,就很容易进行密集搜索,因为您不需要搜索,您就知道每个元素的确切位置。

因此,使用 Map 实现队列会占用大量内存,而没有 HashMap 搜索的明显优势。此外,迭代它非常非常昂贵,因为您需要迭代一个巨大的数组并忽略空值;您不确定实际值在哪里。想象在您的示例中,优先级从 0 到 100。即使您永远不会有 100 个任务,但您将有一个 100 个位置数组来迭代,检查每个位置是否有具有该优先级的任务。

更好的方法是使用简单的列表并将优先级存储在数据中。假设优先级不随时间变化,您只需在添加时迭代一次,以找到正确的位置,并始终访问第一个(或最后一个)元素,即具有已知最高优先级的元素。

关于性能的最后一点,最好在添加时进行迭代,即使您添加的时间量与您正在搜索的时间量完全相同。因为当你搜索最高优先级时,你每次都需要一直走到最后,因为也许最后一个元素是更大的那个。请注意,即使您的键是数字字符串或整数,HashMap 也不会以整齐的新月顺序存储您的项目。它是专门设计的,以避免冲突(两个相似的对象具有相同的键,因此需要将列表添加到大数组的特定位置)。但是,当您添加时,假设您的列表已经排序,您只需要迭代直到到达正确的目的地。

【讨论】:

谢谢它是有道理的“只需要找到地图的最高优先级”,我想检查从最高优先级到最低优先级。如果最大值不存在,则转到第二高,依此类推..绝对列表更好。感谢您的澄清 感谢您的快速回答:) 考虑一下。事实证明,List 在“添加”时进行了一次迭代。 hashmap 也在进行一次迭代,即在“获取”时。所以这两种情况都需要一次迭代。 您能否详细说明一下,在上述情况下列表如何更好,时间复杂度明智。 用新信息更新答案,看看:)【参考方案4】:

长话短说,这一切都归结为HashMaps 没有排序,因此无论如何您最终都需要一些外部机制来确定队列中的“下一个”是什么。

例如,假设您输入了条目

[1, "A"]
[3, "B"]
[4, "C"]

如果您只有一个 HashMap,则没有可靠的方法可以确定 1 是“第一个”没有遍历每个键的,因为在内部键可能是:

[1, "A"]
[3, "B"]
[4, "C"]

[3, "B"]
[4, "C"]
[1, "A"]

[3, "B"]
[1, "A"]
[4, "C"]

等等。

来自HashMap javadoc:

这个类不保证地图的顺序;特别是,它不保证订单会随着时间的推移保持不变。

因此,使用HashMap 确实没有意义,因为无论如何你必须做的额外工作最终会使HashMap 的效率低于堆。


现在,如果您真的想使用HashMap,您可以:

获取EntrySet,按键排序,然后得到第一个值。如果这是您需要获取第一个值的 only 时间,那没关系,但这种情况很少发生。一旦你改变了map,需要再做一次,效率比普通堆还差。

使用一些外部机制,例如ArrayList 或其他东西来跟踪键,也许将其作为插入值的一部分进行排序。不过,此时不妨跳过HashMap 并“正常”实现堆,因为否则您基本上在做同样的事情,但会增加在ArrayListHashMap 之间拆分数据的复杂性。

所以最后,额外的工作并不值得。

【讨论】:

感谢您的快速回答:)【参考方案5】:

当您尝试遍历 HashMap 时,您会得到一个奇怪的顺序。 HashMaps(或与此相关的任何地图)没有保证遍历顺序,因此您将无法以有效的方式“排序”队列。

您当然可以查询所有键,对该列表进行排序,并按顺序获取值,但与替代方案相比,这非常低效(您已经在 O(n^2) 的键长度上,而无需执行其他操作工作)

【讨论】:

感谢您的快速回答:)

以上是关于使用 hashmap 实现优先队列的主要内容,如果未能解决你的问题,请参考以下文章

堆(Heap)和有优先队列(Priority Queue)

JAVA比较器的写法和优先队列的使用

JAVA比较器的写法和优先队列的使用

线性表--优先队列

线性表--优先队列

学习:STL----优先队列