在“initialCapacity=n”的“java.util.PriorityQueue”中插入“n”个元素的时间复杂度
Posted
技术标签:
【中文标题】在“initialCapacity=n”的“java.util.PriorityQueue”中插入“n”个元素的时间复杂度【英文标题】:Time complexity of inserting `n` elements in a `java.util.PriorityQueue` with `initialCapacity=n` 【发布时间】:2019-02-03 09:05:58 【问题描述】:我必须从一个数组中构造一个最大堆(在下面的代码中称为nums
),所以我使用的是java.util.PriorityQueue
。
我的代码如下所示:
PriorityQueue<Integer> pq = new PriorityQueue<>(nums.length, (a, b) -> b - a);
for (int i = 0; i < nums.length; i++)
pq.offer(nums[i]);
我正在尝试找出上述for
循环的时间复杂度(以 Big-O 表示法表示)。
我了解PriorityQueue
没有具体说明底层数据结构的增长细节。 (在最坏的情况下,当扩展内部数组并将所有元素复制到新分配的空间时,它可能是O(n)
)。
但我假设当我指定initialCapacity
并且不添加超过此initialCapacity
的元素时,上述循环的最坏情况时间复杂度应该是O(n)
而不是O(nlog(n))
。我从here 了解到,堆的构建时间是O(n)
,而nlog(n)
是一个宽松的上限。
我是对的,还是我遗漏了什么?
我只是想了解,如果我用n
的initialCapacity
配置我的PriorityQueue
并在该优先级队列中添加n
元素,那么这个构建堆过程的时间复杂度是多少?
PS:我已经看过 this,但是这个问题的答案只是声称没有解释,可能它们不是 Java 特定的。
我还看到java.util.PriorityQueue
有一个接受Collection
的构造函数。这个构造函数的时间复杂度是多少?不应该是O(n)
吗?
【问题讨论】:
prioriyQueue 在 O(logn) 中工作,总数为 O(log(n) * num.length) @JahongirSabirov 我想知道(有解释),在我的具体实现中,我应该假设它在O(nlog(n))
或PriorityQueue
的JDK 实现中工作,我得到O(n)
当我指定initialCapacity
您提出的问题取决于PriorityQueue
的实现细节。如果您真的想了解您的代码将如何执行,您需要自己进行复杂性分析……针对特定的实现。幸运的是,源代码是免费提供的。
【参考方案1】:
我了解
PriorityQueue
没有具体说明底层数据结构的增长细节。
让我们清楚这一点。 javadoc 声明未指定扩展队列的策略。
(在最坏的情况下,当扩展内部数组并将所有元素复制到新分配的空间时,它可能是 O(n))。
当前的政策(Java 11)是:
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
对于“双倍”策略,每次插入的摊销成本为 O(1)。增长 50% 并不是那么好。但 is 比 O(n) 好得多。
可以肯定的是,无论当前规范(技术上)允许什么,他们都不会单方面将此策略更改为复杂性大大降低的策略。
但是,这与您的问题无关,因为您正在使用initialCapacity
容量,无论是明确使用,还是在您从集合中填充PriorityQueue
时。
我假设当我指定
initialCapacity
并且不添加超过此 `initialCapacity 的元素时,上述循环的最坏情况时间复杂度应该是 O(n) 而不是 O(nlog(n)) .我从这里了解到堆的构建时间是 O(n) 并且 nlog(n) 是一个宽松的上限。我是对的,还是我遗漏了什么?
我认为你错过了什么。
假设您的输入数组未排序,则构建堆(“堆化”)并按顺序检索元素相当于将元素按优先顺序排序。平均而言,这是一个 O(nlogn) 操作。虽然 heapification 本身是 O(n)(因为代码使用了 sift down heapification),但实际上您已经推迟了一些排序成本。
因此,除非您只打算检索放入队列的元素的非 O(n) 子集,否则总体答案是 O(nlogn)。
我只是想了解,如果我将
initialCapacity
配置为n
并在该优先级队列中添加n
元素,则此构建堆过程的时间复杂度是多少?
由于上述原因,总体复杂度(添加和删除 n 个元素)将为 O(nlogn)。
我还看到
PriorityQueue
有一个接受集合的构造函数。这个构造函数的时间复杂度是多少?不应该是O(n)吗?
如果集合是未排序的,那么元素必须是heapified;往上看。有一些特殊情况的代码可以处理跳过堆化步骤的SortedCollection
。
注意事项:
-
您可以通过阅读
PriorityQueue
的源代码来确认上述详细信息。 Google 可以为您找到它。
HeapSort 上的***页面讨论了堆化
良好的算法教科书中给出了通过将基于数组的数据结构加倍来实现每次插入的 O(1) 增长的证明。同样的分析可以应用于增长 50%。
您的 lambda 表达式 (a, b) -> b - a
不适用于对整数进行排序,除非它们是正数。
【讨论】:
你能告诉我到底是什么问题吗?你说的是溢出吗?是的,如果溢出,这个 lamda 可能会行为不端。除了溢出条件之外,这个 lamda 还有什么问题吗? 好吧......如果你得到一个溢出,那么比较器给出一个不正确的答案,并且排序中断 => 错误排序的队列。 “假设您的输入数组未排序,构建堆(“堆化”)需要将元素排序为优先顺序。”真的吗?我不认为构建堆实际上会按优先级顺序对元素进行排序。[1, 2, 3, 5, 12, 11, 10, 7]
是未排序的,仍然是堆的(并且是最小堆)。
There is some special-case code to deal with a SortedCollection which skips the heapification step
我明白了,但是 [PriorityQueue(SortedSet‹? extends E› c)
](docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/…。我希望有十多年类似于 java.util.SortedCollection<E>
i>(和一个 PriorityQueue 构造函数,允许集合和一个比较器)。
You don't need to supply a Comparator for Integer elements: the Integer class implements Comparable<Integer>
如果你想反转优先级,你需要 - (a, b) -> b.compareTo(a)
。以上是关于在“initialCapacity=n”的“java.util.PriorityQueue”中插入“n”个元素的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章