Binary按属性搜索

时间:2013-10-26 11:57:30

标签: c# list binary-search

我有一个范围列表,例如:(0,1),(1,4),(4,8),(8,14),(14,20),
Range是Class的类,包括Start&结束。

我可以应用BinarySearch搜索包含值10的段吗?
还有其他任何方法可以实现吗?

3 个答案:

答案 0 :(得分:1)

您可以将IComparer界面与BinarySearch

一起使用

此致

答案 1 :(得分:1)

查询范围集合的一般且最有效的方法是实现Interval Tree。但是,由于您的间隔从不重叠,因此您可以使用binary search approach

所以,这就是我要做的事。

考虑Range类实现如下:

class Range
{
    public int Start { get; private set; }
    public int End { get; private set; }
    public Range(int start, int end)
    {
        this.Start = start;
        this.End = end;
    }
}

我们为Range类创建了一个只考虑Start属性的比较器(它已经足够了,因为我们假设没有重叠):

class StartOnlyRangeComparer : IComparer<Range>
{
    public int Compare(Range x, Range y)
    {
        return x.Start.CompareTo(y.Start);
    }
}

然后,这是包含在扩展类中的最重要的方法:

static class Extensions
{
    /// <summary>
    /// N.B. ranges must be sorted by ascending Start, must contain 
    ///      non overlapping ranges and ranges with Start=End are not allowed.
    /// </summary>
    /// <param name="ranges"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    public static Range FindRangeContainingPoint(
        this IList<Range> ranges, int point)
    {
        if (ranges.Count == 0)
            return null;

        var index = ranges.FindFirstIndexGreaterThanOrEqualTo(
                    new Range(point, point + 1),
                    new StartOnlyRangeComparer());
        if (index < 0)
            return null; // no existing range contains the point, 
                         // if we wanted to insert a Range(point, point+1) 
                         // would be before the first element 
        if (index >= ranges.Count)
        {
            var lastElement = ranges[ranges.Count - 1];
            if (lastElement.ContainsPoint(point))
                return lastElement;
            else
                return null; // no existing range contains the point,  
                             // if we wanted to insert a Range(point, point+1)
                             // would be after the last element
        }
        if (ranges[index].ContainsPoint(point))
            return ranges[index];
        else if (index > 0 && ranges[index - 1].ContainsPoint(point))
            return ranges[index - 1];
        else
            return null; // no existing range contains the point,  
                         // if we wanted to insert a Range(point, point+1)
                         // would be at index - 1
    }

    /// <summary>
    /// Lower Bound function on sorted list (see credits)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sortedCollection"></param>
    /// <param name="key"></param>
    /// <param name="comparer"></param>
    /// <returns></returns>
    public static int FindFirstIndexGreaterThanOrEqualTo<T>(
        this IList<T> sortedCollection,
        T key,
        IComparer<T> comparer)
    {
        int begin = 0;
        int end = sortedCollection.Count;
        while (end > begin)
        {
            int index = begin + ((end - begin) / 2);
            T el = sortedCollection[index];
            if (comparer.Compare(el, key) >= 0)
                end = index;
            else
                begin = index + 1;
        }
        return end;
    }

    /// <summary>
    /// Check if point is included in [Start, End)
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    public static bool ContainsPoint(this Range range, int point)
    {
        return range.Start <= point && range.End > point;
    }
}

用法示例:

class Program
{
    static void Main(string[] args)
    {
        var list = new List<Range>()
        {
            new Range(0, 1), 
            new Range(1, 4), 
            new Range(4, 8), 
            new Range(8, 14), 
            new Range(14, 20),
            new Range(22, 24),
        };

        // yes I know in this case they're already sorted...
        // but I want to stress the fact that you must sort the list
        list.Sort(new StartOnlyRangeComparer()); 

        var range10 = list.FindRangeContainingPoint(10); // returns (8,14)
        var range21 = list.FindRangeContainingPoint(21); // returns null
        var range20 = list.FindRangeContainingPoint(20); // returns null
        var range14 = list.FindRangeContainingPoint(14); // return (14,20)
   }
}

请注意,排除Start边缘时会包含End边缘(例如,请参阅range20示例)。

FindFirstIndexGreaterThanOrEqualTo方法的this SO post的积分

答案 2 :(得分:0)

我写了自己的功能:

public Range BinarySearch(double position)
{
    Range part = null;

    int from = 0;
    int to = Count - 1;
    while (from < to)
    {
        int middle = (from + to) / 2;
        part = this[middle];

        switch (part.CompareTo(position))
        {
            case 1:
                from = Math.Min(middle + 1, to);
                break;
            case -1:
                to = Math.Max(middle - 1, from);
                break;
            case 0:
                from = to = middle;
                break;
        }
    }

    return this[to];
}