44

简单的问题 - 给出一个IList<T>如何在不自己编写方法并且不将数据复制到具有内置二进制搜索支持的类型的情况下执行二进制搜索的问题。我现在的状态如下。

  • List<T>.BinarySearch()不是IList<T>
  • 没有等效的ArrayList.Adapter()方法List<T>
  • IList<T>不继承自IList,因此ArrayList.Adapter()无法使用

我倾向于认为使用内置方法是不可能的,但我无法相信 BCL/FCL 中缺少这种基本方法。

如果不可能,谁能给出最短、最快、最聪明或最漂亮的二分搜索实现IList<T>

更新

我们都知道在使用二分搜索之前必须对列表进行排序,因此您可以假设它是。但我认为(但没有验证)排序是同样的问题 - 你如何排序IList<T>

结论

似乎没有内置的二进制搜索IList<T>。可以使用LINQ 方法进行搜索和排序,但它可能会影响性能First()OrderBy()自己实现它(作为扩展方法)似乎是你能做的最好的。

4

11 回答 11

39

我怀疑 .NET 中是否存在类似的通用二进制搜索方法,除了某些基类中存在的方法(但显然不在接口中),所以这是我的通用方法。

public static Int32 BinarySearchIndexOf<T>(this IList<T> list, T value, IComparer<T> comparer = null)
{
    if (list == null)
        throw new ArgumentNullException(nameof(list));

    comparer = comparer ?? Comparer<T>.Default;

    Int32 lower = 0;
    Int32 upper = list.Count - 1;

    while (lower <= upper)
    {
        Int32 middle = lower + (upper - lower) / 2;
        Int32 comparisonResult = comparer.Compare(value, list[middle]);
        if (comparisonResult == 0)
            return middle;
        else if (comparisonResult < 0)
            upper = middle - 1;
        else
            lower = middle + 1;
    }

    return ~lower;
}

这当然假设有问题的列表已经按照比较器将使用的相同规则进行了排序。

于 2009-06-08T21:21:29.097 回答
35

这是我的 Lasse 代码版本。我发现能够使用 lambda 表达式来执行搜索很有用。在对象列表中搜索时,它只允许传递用于排序的键。使用 IComparer 的实现是从这个简单派生的。

我也喜欢在找不到匹配项时返回 ~lower。Array.BinarySearch 可以做到这一点,它可以让您知道您搜索的项目应该插入到哪里以保持排序。

/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <typeparam name="TSearch">The type of the searched item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <param name="comparer">The comparer that is used to compare the value
/// with the list items.</param>
/// <returns></returns>
public static int BinarySearch<TItem, TSearch>(this IList<TItem> list,
    TSearch value, Func<TSearch, TItem, int> comparer)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }
    if (comparer == null)
    {
        throw new ArgumentNullException("comparer");
    }

    int lower = 0;
    int upper = list.Count - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        int comparisonResult = comparer(value, list[middle]);
        if (comparisonResult < 0)
        {
            upper = middle - 1;
        }
        else if (comparisonResult > 0)
        {
            lower = middle + 1;
        }
        else
        {
            return middle;
        }
    }

    return ~lower;
}

/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <returns></returns>
public static int BinarySearch<TItem>(this IList<TItem> list, TItem value)
{
    return BinarySearch(list, value, Comparer<TItem>.Default);
}

/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <param name="comparer">The comparer that is used to compare the value
/// with the list items.</param>
/// <returns></returns>
public static int BinarySearch<TItem>(this IList<TItem> list, TItem value,
    IComparer<TItem> comparer)
{
    return list.BinarySearch(value, comparer.Compare);
}
于 2010-06-01T10:13:17.730 回答
34

我喜欢使用扩展方法的解决方案。但是,有一点警告是有必要的。

这实际上是 Jon Bentley 在他的书 Programming Pearls 中的实现,它受到了一个数字溢出错误的影响,该错误在 20 年左右未被发现。如果 IList 中有大量项目,则 (upper+lower) 可能会溢出 Int32。对此的解决方案是使用减法来稍微不同地进行中间计算;中=下+(上-下)/2;

Bentley 还在 Programming Pearls 中警告说,虽然二分搜索算法于 1946 年发布,而第一个正确的实现直到 1962 年才发布。

http://en.wikipedia.org/wiki/Binary_search#Numerical_difficulties

于 2009-06-09T03:02:51.013 回答
5

一段时间以来,我一直在努力解决这个问题。特别是 MSDN 中指定的边缘情况的返回值:http: //msdn.microsoft.com/en-us/library/w4e7fxsh.aspx

我现在已经从 .NET 4.0 复制了 ArraySortHelper.InternalBinarySearch() 并出于各种原因制作了自己的风格。

用法:

var numbers = new List<int>() { ... };
var items = new List<FooInt>() { ... };

int result1 = numbers.BinarySearchIndexOf(5);
int result2 = items.BinarySearchIndexOfBy(foo => foo.bar, 5);

这应该适用于所有 .NET 类型。到目前为止,我已经尝试过 int、long 和 double。

执行:

public static class BinarySearchUtils
{
    public static int BinarySearchIndexOf<TItem>(this IList<TItem> list,
        TItem targetValue, IComparer<TItem> comparer = null)
    {
        Func<TItem, TItem, int> compareFunc =
            comparer != null ? comparer.Compare :
            (Func<TItem, TItem, int>) Comparer<TItem>.Default.Compare;
        int index = BinarySearchIndexOfBy(list, compareFunc, targetValue);
        return index;
    }

    public static int BinarySearchIndexOfBy<TItem, TValue>(this IList<TItem> list,
        Func<TItem, TValue, int> comparer, TValue value)
    {
        if (list == null)
            throw new ArgumentNullException("list");

        if (comparer == null)
            throw new ArgumentNullException("comparer");

        if (list.Count == 0)
            return -1;

        // Implementation below copied largely from .NET4
        // ArraySortHelper.InternalBinarySearch()
        int lo = 0;
        int hi = list.Count - 1;
        while (lo <= hi)
        {
            int i = lo + ((hi - lo) >> 1);
            int order = comparer(list[i], value);

            if (order == 0)
                return i;
            if (order < 0)
            {
                lo = i + 1;
            }
            else
            {
                hi = i - 1;
            }
        }

        return ~lo;
    }
}

单元测试:

[TestFixture]
public class BinarySearchUtilsTest
{
    [Test]
    public void BinarySearchReturnValueByMsdnSpecification()
    {
        var numbers = new List<int>() { 1, 3 };

        // Following the MSDN documentation for List<T>.BinarySearch:
        // http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx

        // The zero-based index of item in the sorted List(Of T), if item is found;
        int index = numbers.BinarySearchIndexOf(1);
        Assert.AreEqual(0, index);

        index = numbers.BinarySearchIndexOf(3);
        Assert.AreEqual(1, index);


        // otherwise, a negative number that is the bitwise complement of the
        // index of the next element that is larger than item
        index = numbers.BinarySearchIndexOf(0);
        Assert.AreEqual(~0, index);

        index = numbers.BinarySearchIndexOf(2);
        Assert.AreEqual(~1, index);


        // or, if there is no larger element, the bitwise complement of Count.
        index = numbers.BinarySearchIndexOf(4);
        Assert.AreEqual(~numbers.Count, index);
    }
}

我只是从我自己的代码中剪掉了这个,所以如果它不能开箱即用,请发表评论。

希望这可以一劳永逸地解决问题,至少根据 MSDN 规范。

于 2012-01-01T15:21:22.977 回答
4

您将遇到一些问题二进制搜索 an IList<T>,首先,就像您提到的那样,该BinarySearch方法List<T>不是IList<T>接口的成员。其次,您无法在搜索之前对列表进行排序(您必须这样做才能使二进制搜索起作用)。

我认为您最好的选择是创建一个新的List<T>,对其进行排序,然后进行搜索。它并不完美,但如果你有一个IList<T>.

于 2009-06-08T21:16:02.320 回答
3

请注意,下面 Antoine 提供的实现中存在一个错误:当搜索大于列表中任何一个的项目时。返回值应该是 ~lower 而不是 ~middle。反编译方法 ArraySortHelper.InternalBinarySearch (mscorlib) 查看框架实现。

于 2010-08-04T14:17:10.077 回答
2

IList<T>如果您需要对s进行二分搜索的现成实现, Wintellect 的 Power Collections 有一个(in Algorithms.cs):

/// <summary>
/// Searches a sorted list for an item via binary search. The list must be sorted
/// by the natural ordering of the type (it's implementation of IComparable&lt;T&gt;).
/// </summary>
/// <param name="list">The sorted list to search.</param>
/// <param name="item">The item to search for.</param>
/// <param name="index">Returns the first index at which the item can be found. If the return
/// value is zero, indicating that <paramref name="item"/> was not present in the list, then this
/// returns the index at which <paramref name="item"/> could be inserted to maintain the sorted
/// order of the list.</param>
/// <returns>The number of items equal to <paramref name="item"/> that appear in the list.</returns>
public static int BinarySearch<T>(IList<T> list, T item, out int index)
        where T: IComparable<T>
{
    // ...
}
于 2009-09-28T12:29:32.657 回答
2

您可以使用List<T>.BinarySearch(T item). 如果您想使用自定义比较器,请使用List<T>.BinarySearch(T item, IComparer<T> comparer). 查看此 MSDN链接了解更多详细信息。

于 2016-11-18T19:03:22.840 回答
-1

请记住,对于某些列表实现,二分搜索可能效率很低。例如,对于一个链表,如果你正确地实现它,它是 O(n),如果你天真地实现它,它是 O(n log n)。

于 2009-09-28T12:36:47.137 回答
-1

如果您可以使用 .NET 3.5,则可以使用构建在 Linq 扩展方法中:

using System.Linq;

IList<string> ls = ...;
var orderedList = ls.OrderBy(x => x).ToList();
orderedList.BinarySearch(...);

但是,这实际上只是 Andrew Hare 解决方案的一种稍微不同的方式,并且只有在您从同一个有序列表中搜索多次时才真正有用。

于 2009-06-08T21:43:47.587 回答
-3

请注意,虽然 List 和 IList 没有 BinarySearch 方法,但 SortedList 有。

于 2009-06-08T21:34:15.920 回答