如何在 Scala 中获取优先级队列的第 k 个最小元素?

Posted

技术标签:

【中文标题】如何在 Scala 中获取优先级队列的第 k 个最小元素?【英文标题】:How do I get the k-th minimum element of a Priority Queue in Scala? 【发布时间】:2020-12-04 14:54:36 【问题描述】:

我尝试了以下但似乎是错误的!

import collection.mutable

object Main 
  def main(args: Array[String]): Unit = 
    val asc = Ordering.by((_: (Double, Vector[Double]))._1).reverse
    val pq = mutable.PriorityQueue.empty[(Double, Vector[Double])](asc)

    pq.enqueue(12.4 -> Vector(22.0, 3.4))
    pq.enqueue(1.2 -> Vector(2.3, 3.2))
    pq.enqueue(9.1 -> Vector(12.0, 3.2))
    pq.enqueue(32.4 -> Vector(22.0, 13.4))
    pq.enqueue(13.2 -> Vector(32.3, 23.2))
    pq.enqueue(93.1 -> Vector(12.0, 43.2))

    val k = 3

    val kthMinimum = pq.take(k).last
    println(kthMinimum)
  

【问题讨论】:

您能补充一下代码打印的内容以及您希望打印的内容吗? 【参考方案1】:

在Scala API doc中有明确说明:

只有 dequeue 和 dequeueAll 方法会返回元素 优先顺序(从堆中删除元素时)。标准 包括 drop、iterator 和 toString 在内的集合方法将删除 或以最方便的顺序遍历堆。

如果你想坚持使用PriorityQueue,似乎dequeue-ing k 次或pq.dequeueAll(k-1) 可能是实现优先检索的唯一手段。

【讨论】:

【参考方案2】:

问题在于PriorityQueue 属性与继承的集合方法(如take 等)之间的不兼容。Scala 集合的另一个奇怪实现问题示例。

Java 的PriorityQueue 也存在同样的问题。

import java.util.PriorityQueue

val pQueue = new PriorityQueue[Integer]

pQueue.add(10)
pQueue.add(20)
pQueue.add(4)
pQueue.add(15)
pQueue.add(9)

val iter = pQueue.iterator()

iter.next() // 4
iter.next() // 9
iter.next() // 10
iter.next() // 20
iter.next() // 15

因此,PriorityQueue 在底层ArrayBuffer 中维护您的数据(不完全是,而是一个特殊的继承类)。这个“数组”保持堆积。继承的take API 工作在这个堆积如山的类数组数据结构之上。最小堆数组的第一个 k 元素肯定与 minimum k 元素不同。

现在,定义 PriorityQueue 应该支持 enqueuedequeue。它只是维护最高优先级(第一个)元素,只是无法可靠地提供队列中的第 k 个元素。

虽然我说这是 Java 和 Scala 实现的问题,但它只是不可能为此想出一个理智的实现。我只是想知道为什么PriorityQueue 实现中仍然存在这些误导性方法。我们不能直接删除它们吗?

我强烈建议您使用适合您要求的最严格的 API。换句话说,坚持使用 Queue API 而不是使用继承的 API 方法(这可能会做一些奇怪的事情)。

虽然没有什么好的方法(因为它不是 PriorityQueue 明确要求的)。

您可以通过简单的dequeueing k times 在时间复杂度为k * log(n) 的循环中实现这一点。

val kThMinimum = 
  val pqc = pq.clone()
  (1 until k).foreach(i => pqc.dequeue())
  pqc.dequeue()

【讨论】:

以上是关于如何在 Scala 中获取优先级队列的第 k 个最小元素?的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode刷题100天—215. 数组中的第K个最大元素(优先队列)—day15

Leetcode刷题100天—5855. 找出数组中的第 K 大整数(优先队列)—day22

Leetcode刷题100天—5855. 找出数组中的第 K 大整数(优先队列)—day22

Leetcode刷题100天—703. 数据流中的第 K 大元素(优先队列)—day16

合并 k 排序数组 - 优先队列与传统合并排序合并,何时使用哪个?

热题100_20230510