【问题标题】:Fastest method for Queue Implementation in JavaJava中最快的队列实现方法
【发布时间】:2012-12-31 01:19:18
【问题描述】:

任务是在java中实现一个队列,方法如下:

  1. enqueue //添加一个元素到队列
  2. dequeue //从队列中移除元素
  3. peekMedian //求中位数
  4. peekMinimum //找到最小值
  5. peakMaximum //找到最大值
  6. size // 获取大小

假设所有方法都以等频方式调用,任务是实现最快的实现。

我目前的方法: 维护一个有序数组,除了队列,入队和出队都需要O(logn),peekMedian、peekMaximum、peekMinimum都需要O(1)时间。

请建议一种更快的方法,假设所有方法的调用频率相同。

【问题讨论】:

  • maintain sorted array 是 O(n) 每次插入/删除(最坏情况)

标签: algorithm data-structures queue


【解决方案1】:

好吧,你已经接近了 - 但仍然缺少一些东西,因为从排序数组中插入/删除是O(n)(因为插入元素的概率为 1/2 位于数组的前半部分,你会必须将以下所有元素向右移动,其中至少有 n/2 个,因此此操作的总复杂度平均为 O(n) + 最坏情况)

但是,如果您将排序的 DS 切换为 skip list/ balanced BST - 您将获得 O(logn) 插入/删除和 O(1) 最小/最大/中值/大小(带缓存)

编辑:

对于插入,您无法比O(logN) 更好(除非您将peekMedian() 减少到Omega(logN)),因为这将使您能够比O(NlogN) 更好地排序:

首先,请注意,对于您插入的每个“高”元素,中位数向右移动一个元素(在这里,高意味着 >= 当前最大值)。
所以,通过迭代做:

while peekMedian() != MAX:
   peekMedian()
   insert(MAX)
   insert(MAX)

您可以找到已排序数组的“较高”一半。
使用与insert(MIN) 相同的方法,您可以获得数组的最低一半。

假设你有 o(logN)(小符号,比 Theta(logN) 插入和 O(1) peekMedian() 更好,你得到了比 O(NlogN) 更好的排序,但排序是 Omega(NlogN) 的问题。
=>

因此insert() 不能比O(logN) 更好,中位数仍然是O(1)

QED

EDIT2:修改插入中的中位数:

如果插入前的树大小为 2n+1(奇数),则旧中位数在索引 n+1 处,新中位数在同一索引处(n+1),所以如果元素是在插入之前添加的旧中位数 - 您需要获取最后一个中位数的前一个节点 - 这就是新中位数。如果它是在它之后添加的 - 什么都不做,旧的中位数也是新的。

如果列表是偶数(2n 个元素),那么在插入之后,你应该增加一个索引(从 n 到 n+1),所以如果新元素是在中位数之前添加的 - 如果它被添加了,什么也不做在旧中位数之后,您需要将新中位数设置为旧中位数的下一个节点。

注意:这里的next和previous节点是根据key跟随的节点,index表示节点的“位置”(最小的是第一个,最大的是最后一个)。
我只解释了如何插入,删除的想法相同。

【讨论】:

  • @amit- 关于维持一个额外的 BST,我们如何在恒定时间内获得中位数?
  • @user1628340 缓存。在插入/删除时维护它非常简单 - 它仍然在插入/删除的O(logN)
  • @amit-我不太了解缓存。请详细说明。
  • @user1628340 每当你插入/删除一个元素 - 找到新的中位数 - 并将其存储在一个名为 median 的变量中,当你要求中位数时 - 你只需要检索它。您可以进行一些优化 - 但没有一个可以提高渐近时间复杂度 - 所以我暂时忽略它们。
  • @amit- 你如何从 O(logn) 中的 bst 中找到中位数?
【解决方案2】:

有一个更简单或许更好的解决方案。 (如前所述,排序后的数组使入队和出队都是 O(n),这不太好。)

维护 两个 除了队列之外的排序集。 Java 库提供了例如SortedSet,它们是平衡的搜索树。 “低集”按排序顺序存储第一个上限 (n/2) 元素。第二个“高设置”具有最后一层(n / 2)。

注意:如果允许重复,则必须使用 Google's TreeMultiset 之类的东西,而不是常规的 Java 排序集。

要入队,只需添加到队列和正确的集合中。如有必要,通过移动一个元素来重新建立集合之间的平衡:要么将低集合中的最大元素移至较高集合,要么将高集合中的最小元素移至低集合。出队需要同样的重新平衡操作。

如果 n 为奇数,则查找中位数只是查找低集合中的最大元素。如果 n 是偶数,则在低集合中找到最大元素,在高集合中找到最小值并平均。

使用原生 Java 排序集实现(平衡树),对于所有操作,这将是 O(log n)。这将非常容易编码。大约 60 行。

如果您为低集和高集实现自己的筛选堆,那么您将有 O(1) 用于查找中值操作,而所有其他操作将保持 O(log n)。

如果您继续为低集和高集实现自己的Fibonacci heaps,那么您也将插入 O(1)。

【讨论】:

  • @gene- Set 不允许重复值,而队列允许它!你能想到任何允许重复值的有效数据结构吗?
  • 是的。我在上面添加了一个标记为 NB 的注释。您需要多重集来处理重复项。很高兴谷歌在他们的收藏库中提供了这些......
猜你喜欢
  • 1970-01-01
  • 2021-04-20
  • 2012-06-24
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多