使用 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 的值,分别用于优先级队列实现。 请清除我的逻辑
【问题讨论】:
因为HashMap
s 没有被排序,所以你需要一些其他方法来确定什么是“下一个”。
请注意,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】:长话短说,这一切都归结为HashMap
s 没有排序,因此无论如何您最终都需要一些外部机制来确定队列中的“下一个”是什么。
例如,假设您输入了条目
[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
并“正常”实现堆,因为否则您基本上在做同样的事情,但会增加在ArrayList
和HashMap
之间拆分数据的复杂性。
所以最后,额外的工作并不值得。
【讨论】:
感谢您的快速回答:)【参考方案5】:当您尝试遍历 HashMap
时,您会得到一个奇怪的顺序。 HashMap
s(或与此相关的任何地图)没有保证遍历顺序,因此您将无法以有效的方式“排序”队列。
您当然可以查询所有键,对该列表进行排序,并按顺序获取值,但与替代方案相比,这非常低效(您已经在 O(n^2) 的键长度上,而无需执行其他操作工作)
【讨论】:
感谢您的快速回答:)以上是关于使用 hashmap 实现优先队列的主要内容,如果未能解决你的问题,请参考以下文章