【问题标题】:Running Median with window size along with long input运行具有窗口大小和长输入的中位数
【发布时间】:2015-05-26 15:03:45
【问题描述】:

给定一个数据序列(它可能有重复),一个固定大小的移动窗口,在每次迭代时从数据序列的开始移动窗口,使得 (1) 从窗口中删除最旧的数据元素,并将新的数据元素推入窗口 (2) 求每次移动时窗口内数据的中位数。

这里的窗口大小是 7,我们称之为 m。 m = 窗口大小 n = 序列中的元素个数,可以是 1000 或 10000

Median for 4, 6, 99, 10, 90, 12, 17,1,21,32  : 12
Median for 6, 99, 10, 90, 12, 17, 1,21,32  : 12
Median for 99, 10, 90, 12, 17, 1, 21,32 : 17
Median for 10, 90, 12, 17, 1, 21, 32 : 17

我每次都在快速排序 m 个元素的帮助下实现了这个东西,(三个的中位数作为枢轴)。但这需要很多时间。每次排序都是必需的。我应该实现here 提到的最小和最大堆解决方案@

最小-最大堆解决方案中的问题:

当一个新数据被推入窗口时,从其中一个堆中删除最旧的数据,并将新数据与最大和最小堆的顶部进行比较,以确定哪个堆要放置的数据。然后,就像在第一次迭代中一样找到中位数。

  1. 如何找到从堆中删除最旧的数据,我们如何维护它。根据给定的示例,第二次 4 是最旧的元素,第三次 6 是最旧的元素。我们如何从堆中删除它。

  2. 上述问题的后续问题,如何在堆中查找数据元素是一个问题。堆是二叉树而不是二叉搜索树。

任何帮助将不胜感激。

编辑:我已经有输入数据,所以没有插入发生。只发生在固定大小的队列或窗口上,而不是实际的输入序列。

谢谢

【问题讨论】:

  • 正常的方法是保持反向指针将堆索引映射到数组位置,数组位置映射到堆索引,然后堆交换和其他操作有足够的信息来维护。
  • @doynax 你能解释一下吗?没看懂你说的什么
  • @sam_k 关于您的编辑,每次窗口移动时都会发生插入和删除。
  • @sam_k:我建议您在两个方向上交叉引用数据结构,以便您的操作可以有效地实现双向。对于当前窗口中的每个元素,您都在存储它的堆中维护一个索引,相反,对于每个堆条目,您都为要找到数据的窗口保留一个索引。每当一个项目在堆上移动时,您都会查看堆到窗口索引并使用它来更新反向的窗口到堆索引,使其返回到新的堆位置。
  • 感谢@doynax 的评论。我会尽力理解你所说的。我是数据结构的新手,会尝试理解这一点。如果可能的话,用示例来解释你的答案作为这篇文章的答案。

标签: c++ algorithm data-structures heap


【解决方案1】:

对于正在运行的数据中位数(同时发生插入和删除的移动窗口),对移动窗口中的元素使用单个二叉搜索树而不是最小-最大堆可能更有用。

二叉搜索树也是一个顺序统计树,存储以每个节点为根的子树的大小。

这棵树将能够在 O(log n) 内进行插入、删除和中值计算。

在上面给出的例子中,每个操作都会有时间复杂度 O(log 7)。

【讨论】:

  • BST 是一个不错的选择,每次快速排序 (7log7) 500 个元素?
  • 对于大小为 10 的窗口,快速排序工作正常。但对于 100 的窗口大小和元素为 500。然后它变得最差
  • 快速排序是 O(n logn),而 BST 只会在 O(log n) 中回答。即使是 100 大小的窗口,它也非常便宜。
  • @sray:虽然你实际上并不需要一个完整的快速排序,你可以忽略任何不跨越中位数的东西并在线性时间内执行操作。尽管如此,对数堆和 BST 实现更有效。就我个人而言,我会采用堆方法,因为我害怕实现平衡的二叉搜索树;)
  • @doynax Quicksort 肯定是不需要的,而且是效率最低的解决方案!我认为堆解决方案适用于在线中位数问题。但是在这里,除了 2 个堆之外,您还需要一个队列。每次任何堆的元素比另一个多 2 个时,都需要重新调整堆(O(logn) 操作)。
【解决方案2】:

除了你的堆 DS,还保存一个指针(或引用)队列,其中队列中的每个元素都指向代表堆中相关条目的节点。

当您将窗口移动 1 时:

  1. 出列旧元素
  2. 跟随指针,在二叉堆中找到节点。
  3. “切换”“最后一个”节点(最低层最右边的元素)与刚刚找到的节点
  4. 删除新的“最后一个”节点
  5. 重新堆集
  6. 使用下一个元素创建二进制堆的新条目
  7. 将刚刚添加的节点的指针加入队列
  8. 重新计算中位数

【讨论】:

  • 不要忘记,堆化也会对不相关的节点进行洗牌,需要对指针队列进行相应的更新。实际上需要从堆到队列以及从队列到堆的指针(可能代替值)。然后,堆内节点的任何重新排序都可以有效地更新两端的指针。
  • @doynax re-heapfy 可以通过更改每个节点的辅助数据而不触及值来完成,因此一个节点将表示值x 的所有存在。
  • 感谢您的回答,上述操作对于窗口大小 200 和总元素 1000 是有效的。因为在快速排序中,我得到的总时间为 5 秒。您认为它会在高于 min-max 解决方案中减少吗?
  • @amit:您的意思是让堆将指向具有反向索引的节点对象的指针存储到堆中,在重新排序堆时会更新这些指针?
  • @amit 为什么我们需要存储节点的指针。我们不能存储节点的值。然后从二进制堆中找到并删除它。或者指针选项会减少从堆中查找元素的时间?
猜你喜欢
  • 2021-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-27
  • 2019-01-14
相关资源
最近更新 更多