【问题标题】:Fixed-size collection that keeps top (N) values保持前 (N) 值的固定大小的集合
【发布时间】:2010-08-20 15:53:17
【问题描述】:

我的代码处理大量值,我正在寻找一种有效的结构来跟踪前 (N) 值,其中 N 小于 10,因此收集所有数字然后对列表进行排序并取第一个(N) 可能不是最有效的方式。

为此,我正在构建一个固定大小 N 的集合,以保持前 (N) 值按降序排序。如果 value 高于任何现有值(在这种情况下,最后一个元素被删除)或集合未满,则排序集合的 Add(T value) 方法会将 value 添加到集合中。

我能够使用双重LinkedList<T> 实现我想要的,因为它具有快速插入和删除功能,但我想知道使用SortedDictionary<TKey, TValue> 还是优先队列会更好?

谢谢。

【问题讨论】:

标签: c# arrays algorithm sorting collections


【解决方案1】:

我会简单地使用一个深度有限的堆。我不知道是否已经存在一个库,但它应该很容易实现。

【讨论】:

  • 如果不是,您也可以按相反的顺序对堆进行排序,并在插入后大小大于 N 时删除最小的项。
  • @Svante 如何实现深度有限的堆?大小大于 N 后,必须逐出最小的元素。在最大堆中查找最小元素需要 O(N) 时间。我唯一能想到的是它有两个堆:一个最大堆和一个最小堆。将值插入两个堆,直到有 N 个值。当你想插入另一个值时,从最小堆中弹出最小值。在最大堆中找到该值(通常需要 O(N) 时间,但您可以为堆维护 value=>index 的哈希映射)并将其删除。将新值添加到两个堆。还有其他方法吗?
  • @Svante 嗯,我想我们可以使用最小-最大堆:)
【解决方案2】:

使用 SortedDictionary 或 SortedList 的主要优点是您可以跳过排序智能,因为它们会为您处理(例如,您只需在每次添加值时删除第 (n + 1) 个元素)。 但另一方面,采用10个元素的那种复杂结构就像用核弹杀死一只苍蝇......

也许链表是一个好方法,而且用于按顺序插入值的简单线性比较并不比二分查找慢(我们仍然谈论最多 10 次比较 ~3,当前 CPU 并没有感觉到差异) .

编辑:

固定数组可以用于build prioriry queues with binary heaps,这可能是实现这个的正确方法

【讨论】:

  • 链表不是最优的,因为插入是 O(N) 操作。
  • 准确地说,插入是 O(1),因为不需要移位;找到插入的位置需要 O(n) (最多)。当我说“线性比较”时,我提到了它。无论如何我刚刚编辑它,我已经用“好方法”改变了“最好的方法”;)
  • 链表每项使用一块内存,每新增一项需要预留一块新的内存;这比数组贵一些,可以为所有保留一次(因为它的最大大小是已知的)。虽然垃圾收集效果很好,但最好避免它。
  • @fred-hh:是的,你是对的。事实上,最好的方法可能是将固定数组作为二进制堆。无论如何,我认为最多 10 个元素我们永远不会注意到差异......
【解决方案3】:

性能可能真的会改变。

对于 N

那么关于如何排列数组中的项目有3种主要可能性:

  1. sorted 可能是保持简单的最佳选择:
    • 确定是否插入新项目的恒定时间(与最低比较)
    • O(N) 时间插入 - 但这仅发生在 N 迄今为止最好的项目中。 并且如果您的输入足够随机,则平均时间会更短,因为大多数插入只会移动顶部底部的少数元素。
  2. 未分类:
    • 每个输入元素的 O(N) 时间,与“排序”相比太多了
  3. 实现优先级队列的二进制堆:实现起来更复杂,但可能比“排序”更快
    • 确定是否插入新项目的恒定时间(与最低比较)
    • O(log N) 时间插入 - 这只发生在 N 中最好的项目中

【讨论】:

    【解决方案4】:

    对于这么小的数字,只需保留一个数组即可。扫描数组,跟踪最小值及其位置。如果您的新号码大于集合中最小的号码,请更换它。当然,您应该在插入数字后扫描一次最小值,然后将新数字与该数字进行比较,只有在您有更大的数字时才采取行动(替换并重新扫描)。

    【讨论】:

    • 虽然这个集合很小(只有 10 个元素),但这对我来说听起来效率很低。如果新元素比列表中最大的元素大怎么办?您将用它替换最小的,并且列表变得未排序。现在您必须对所有元素进行线性搜索,以在接下来的插入中找到最小的元素:-s
    • 对,列表未排序。您仅在交换新值后对其进行扫描。您存储最低值及其位置。检查后续值只是与存储的最低值进行比较。如果您有更大的,请交换并重新扫描。
    【解决方案5】:

    除非您有充分的理由不这样做,否则我会使用优先级队列。

    有一个技巧可以大大简化逻辑。大多数人的第一个想法是查看每个传入的项目,并将其插入到集合中,如果集合中包含的项目少于预期,或者新项目大于集合中当前最小的项目。

    如果您为收藏中的一件额外物品留出空间,您可以大大简化事情。 总是将每个传入的项插入到集合中,然后如果集合太大,则删除最小的项。

    虽然可以说优先级队列仅对 10 个项目来说太过分了,但它使逻辑保持简单,并且在空间和时间方面都很高效,所以如果您需要 N=10000(或其他),它仍然可以很好地工作.

    【讨论】:

    • 如果速度真的很重要,我会避免“总是插入每个传入的项目”,因为插入并不便宜 (O(log N)),而且无论如何,在 N 个第一个传入的项目之后,与优先级队列中的顶部项目进行必要的比较(= N 最佳中的最差)就足够了。
    【解决方案6】:

    编辑:

    如果只需要前 N 个值,而对其他值不感兴趣,那么简单的旧数组将可以廉价地完成工作。

    保持排序并针对最大的进行测试。只有 if 需要存储它,正确插入它并移动剩余的元素。对于小尺寸,这是一个便宜的操作,我猜它不会经常这样做。

    【讨论】:

      【解决方案7】:

      如果您的固定大小为 10,为什么不简单地使用长度为 10 的排序数组和二进制搜索?但我不确定在这种大小下,由于一些开销,二分搜索是否比沿数组的愚蠢搜索要大得多。

      【讨论】:

      • 在这种情况下,搜索会很快,但插入一个元素的开销很大,因为它需要将数组复制到一个新的数组中。
      • 不,据我所知,尺寸应始终限制在 10 件以内,您可以取消最后一件。您只需将所有项目移动到您进一步插入的位置之后。而且你完全避免了链表插入的对象实例化。
      【解决方案8】:

      对原始数组使用二进制插入排序,将最小值推到末尾。这通常是用于维护小型排序数组的最快方法,例如,通常用作各种排序算法(例如 MergeSort)的特例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-05
        • 1970-01-01
        • 2015-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多