【问题标题】:Why is there a List<T>.BinarySearch(...)?为什么会有 List<T>.BinarySearch(...)?
【发布时间】:2011-03-16 10:10:48
【问题描述】:

我正在查看 List,我看到一个带有一些重载的 BinarySearch 方法,我不禁想知道在 List 中使用这样的方法是否有意义?

除非列表已排序,否则我为什么要进行二进制搜索?如果列表没有排序,调用该方法只会浪费 CPU 时间。在 List 上使用该方法有什么意义?

【问题讨论】:

  • 其实这是个好问题。但是任何类型系统(不仅仅是 C#/.NET)是否有可能允许一种方法仅适用于“排序”项目?您如何知道项目是否在类型系统上排序?
  • 除非列表已排序,否则我为什么要进行二分搜索? -- 您已经回答了自己的问题……您希望进行二分搜索如果您的列表已排序。 如果列表未排序,则调用该方法只会浪费 CPU 时间——因此,如果您的列表未排序,请不要调用它。仅仅因为存在一种方法,并不强迫您使用它,尤其是在不满足先决条件时。
  • @SedatKapanoglu 当然 ...编写一个 MaybeSortedList 类,该类维护一个标志,说明集合是否已排序。如果设置了该标志,则 Add 使用二进制搜索查找插入点,Find 使用二进制搜索查找项目,而 Remove 使用二进制搜索删除项目。如果未设置标志,请在末尾添加并回退到查找和删除的线性搜索。设置标志时的 InsertAt 操作检查项目是否按顺序插入,如果没有则清除标志。但即使有这样一个类,对列表和数组的“不安全”独立二进制搜索方法也是有用的。
  • @SedatKapanoglu 如果您将乱序插入到先前排序的列表中,插入操作只会很慢......所以不要这样做。您应该维护一个已排序或未排序的列表,而不是将它们混合在一起,即使抽象允许它。而且我认为不需要使用 Sort 的新方法……只需使用 OrderBy 和 ThenBy。
  • @SedatKapanoglu我只回答了您关于如何知道项目是否按类型安全排序的问题。我实际上并不推荐 MaybeSortedList ...相反,有 List 和 SortedList (但不是 .NET 的可恶的错误命名的 SortedList 实际上是 IDictionary)两者其中实现 IList,并使用其中一个。

标签: c# list collections binary-search


【解决方案1】:

排序和搜索是列表中两个非常常见的操作。通过不在常规列表上提供二分搜索来限制开发人员的选择是不友好的。

库设计需要妥协 - .NET 设计人员选择在 C# 中对数组和列表提供二进制搜索功能,因为他们可能(和我一样)觉得这些是有用且常见的操作,以及选择使用它们的程序员在调用它们之前了解它们的先决条件(即列表是有序的)。

使用Sort() 重载之一对List&lt;T&gt; 进行排序很容易。如果你觉得你需要一个保证排序的不变量,你总是可以使用SortedList&lt;TKey,TValue&gt;SortedSet&lt;T&gt;来代替。

【讨论】:

  • 我可以确认“List.BinarySearch()”无法识别项目类型上定义的“IComparable”。即使我明确给出它的一个实例作为参数 2,也不会。感谢“SortedSet”的提示。第一次用这个,以前没听说过。大部分时间它在调用 'SortedSet.Add()' 时工作。在极少数情况下,似乎也存在错误,它会引发异常,即在项目类型上缺少“IComparable”。奇怪的。所以我也给了他这个,只是调用我有一个静态单例吸气剂的“Comparable”类。现在可以了。
【解决方案2】:

一些伪代码:

if List is sorted
   use the BinarySearch method
else if List is not sorted and you think sorting it is "waste of CPU time"
   use a different algorithm that is more suitable and efficient

【讨论】:

  • 我挑战任何人,不包括打了就跑的懦夫,以证明这个答案是错误的。只邀请勇敢的人。
  • @JimBalter,谢谢先生。新年快乐!
【解决方案3】:

也许另一点是数组可以同样未排序。所以理论上,在数组上使用 BinarySearch 也可能是无效的。

但是,与高级语言的所有功能一样,它们需要由对数据有理性和理解的人来应用,否则它们会失败。当然,可以应用一些交叉检查,我们可以有一个标记为“IsSorted”的标志,否则它会在二进制搜索中失败,但是......

【讨论】:

    【解决方案4】:

    搜索和排序是算法原语。标准库有快速可靠的实现是有帮助的。否则,开发人员会浪费时间重新发明轮子。

    但是,在 .NET Framework 的情况下,不幸的是,算法的特定选择恰好使它们的用处不如预期。在某些情况下,它们的行为没有定义:

    1. List&lt;T&gt;.BinarySearch如果List包含多个具有相同值的元素,该方法只返回一个出现,它可能返回任何一个出现,不一定是第一个。

    2. List&lt;T&gt; 此实现执行不稳定的排序;也就是说,如果两个元素相等,可能不会保留它们的顺序。相反,稳定的排序会保留相等元素的顺序。

    真可惜,因为有一些确定性算法同样快,而且这些算法作为构建块会更有用。值得注意的是,Python、Ruby 和 Go 中的二进制搜索算法都找到了第一个匹配的元素。

    【讨论】:

    • 终于有人说了。我完全同意你的第一点。我相信List&lt;T&gt; 上的BinarySearch 方法没有任何意义,因为它不仅不会强制执行命令,而且也不会防止重复。其他所有答案都忽略了这一方面。随机索引不是很有帮助。相反,BinarySearch 方法适用于排序集。但是考虑 .NET 中的两个集合,它们也是一个集合 - SortedSet&lt;&gt;SortedList&lt;,&gt; - 你猜怎么着,它们都没有 BinarySearch 方法。奇怪的决定。
    • 关于第 2 点,我有两种想法。 有时我认为没关系,原因是: List 只保证保留插入顺序。排序后,您愿意妥协立场,在这种情况下,可以为所欲为。 而其他时候我希望 MS 选择稳定的排序,因为它更符合保持顺序的哲学。很多时候我想要这个功能。 最后我相信虽然不稳定的排序没有错,但稳定的排序会更正确和有用。通常需要稳定的排序;几乎没有相反的方式。
    • 第 1 点:这对于二进制搜索来说是正常的。如果您需要第一个,您可以通过简短的向后线性搜索找到它。第 2 点:不稳定的排序更快,但 C# 提供了这两者... List.Sort 进行了不稳定的 introsort,而 Enumerable.OrderBy 提供了稳定的复制合并排序。
    • SortedSet 和 SortedList ...它们都没有 BinarySearch 方法——废话; SortedList.IndexOfKey 进行二进制搜索。在这些类中所有键的操作都是进行二分查找。 IndexOf 对 SortedSet 没有语义意义...集合不是列表。
    • 第 1 点:这对于二分搜索是正常的。如果你需要第一个,你可以用一个短的向后线性搜索来定位它——好吧,那是愚蠢的,我把它收回了。应该同时存在 BinarySearch 和 StableBinarySearch(或者称它们为 FastUnstableBinarySearch 和 BinarySearch;后者不是“一样快”;例如,如果您有一个包含所有相等项目的大列表,则前者是 O(1),后者是O(logn)) 方法。它们应该是适用于任何 IList 的静态方法,而不是 .NET 脑残的实例方法。
    【解决方案5】:

    我注意到除了其他正确答案之外,二进制搜索很难正确编写。有很多极端情况和一些棘手的整数运算。由于二分查找显然是排序列表上的常见操作,因此 BCL 团队通过正确编写二分查找算法一次而不是鼓励客户都编写自己的二分查找算法来为世界提供服务;这些客户编写的算法中有很大一部分是错误的。

    【讨论】:

    • 即使是伟大的图书馆设计师也会弄错:googleresearch.blogspot.com/2006/06/…
    • @Ron Warholic:特定实现仅限于大约 20 亿个元素的列表这一事实通常被描述为限制而不是错误。我倾向于认为,即使可以拥有包含超过 20 亿个元素的单一数据结构,但这并不意味着应该这样做。
    • 只需提一下,这里有一个 BinarySearch() 可用于的示例:将新元素添加到排序列表中,使其保持排序状态。 devlicio.us/blogs/marcin_hoppe/archive/2007/05/15/…
    • 但是BinarySearch 方法已经在Array 类上公开为static 方法,我发现这更有意义。而集合上的实例方法不能保证听起来不受欢迎。
    • BCL 团队通过正确编写一次二进制搜索算法而不是鼓励客户都编写自己的二进制搜索算法来为世界提供服务 -- 嗯,不。如果你有数组或 List 以外的东西……比如说 Collection……你就不走运了。其他“团队”做了一些明智的事情,并提供了一个外部算法,可以在任何可索引的序列上运行。
    【解决方案6】:

    BinarySearch 仅对已排序的 List&lt;T&gt; 有意义,就像 IList&lt;T&gt;.Add 仅对带有 IsReadOnly = falseIList&lt;T&gt; 有意义。这很混乱,但只是需要处理:有时功能 X 取决于标准 Y。Y 并不总是正确的事实并不会使 X 无用。

    现在,在 我的 看来,令人沮丧的是 .NET 没有 general SortBinarySearch 方法用于 any IList&lt;T&gt; 实现(例如,作为扩展方法)。如果是这样,我们可以轻松地进行排序搜索任何提供随机访问的非只读集合中的项目。

    再说一遍,您可以随时编写自己的(或copy someone else's)。

    【讨论】:

      【解决方案7】:

      其他人指出BinarySearch 在排序后的List&lt;T&gt; 上非常有用。不过,它并不真正属于 List&lt;T&gt;,因为任何具有 C++ STL 经验的人都会立即认出。

      随着最近 C# 语言的发展,定义排序列表的概念(例如,ISortedList&lt;T&gt; : IList&lt;T&gt;)并将BinarySearch(等)定义为该接口的扩展方法更有意义。这是一种更简洁、更正交的设计。

      作为Nito.Linq 库的一部分,我已经开始这样做了。我预计第一个稳定版本将在几个月后发布。

      【讨论】:

      • 将它与 C++ STL 进行比较时,我不确定您会得到什么。 std::list 是链表,但 List 实际上是作为数组实现的,更接近于 std::vector.
      • 重点是C++ STL支持正交性vector 没有名为 binary_search 的方法;相反,binary_search 可以应用于任何随机访问迭代器,例如 vector 提供的那些。将相同的概念应用于 C# BCL:List&lt;T&gt; 不应提供 BinarySearchIList&lt;T&gt; 也不应该。它应该是任何IList&lt;T&gt; 的扩展方法(从而实现BinarySearch 算法在任何随机访问迭代器/容器上的正交性)。 (续)。
      • 在我的答案(和库)中,我比这更进一步,并定义了一个 ISortedList&lt;T&gt;,这是一个已知(在编译时)要排序的随机访问迭代器/容器.那么很自然的将BinarySearch定义为ISortedList&lt;T&gt;上的扩展方法。
      【解决方案8】:

      我同意在未排序的列表上调用 BinarySearch 是完全愚蠢的,但如果您知道您的大列表已排序,那就完美了。

      我在检查流中的项目是否存在于(或多或少)100,000 个或更多项目的静态列表中时使用它。

      二分查找列表比查找列表快 ORDERS 数量级。查找比数据库查找快很多数量级。

      我说得有道理,我很高兴它在那里(如果不是这样,实施它就不是火箭科学)。

      【讨论】:

        【解决方案9】:

        是的,但是 List 也有 Sort() 方法,所以你可以在 BinarySearch 之前调用它。

        【讨论】:

          猜你喜欢
          • 2011-02-03
          • 2011-06-23
          • 2019-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-01
          相关资源
          最近更新 更多