在 SortedList<K, V> 的键上进行二分查找

21

我需要编写线性插值代码,正在尝试找到在SortedList<K, V>的Key中搜索围绕目标Key的上下Key的最有效方法。

SortedList<int, double> xyTable = new SortedList<int, double>()
{
    {1, 10}, {2, 20}, {3, 30}, {4,40}
};

double targetX = 3.5;

什么是最有效的搜索列表和确定3.5在3和4之间的方法?我有一个适用于整数的方法/技巧(将目标键暂时插入列表然后找到索引),但我想问问专家,以便我能够编写高质量的代码。

谢谢。


3
排序非常适合用于二分查找。 - Marc
1
log(n)下限搜索的示例 - digEmAll
4个回答

12

二分查找在列表中具有良好的性能。然而,SortedList上的Keys属性是IList类型,而BinarySearch是定义在List上的。幸运的是,您可以在这个相关问题中找到一个适用于IList的二分搜索实现:

如何在IList<T>上执行二分搜索?


2
这是我最喜欢的答案 :) 应该在 SortedList 上内置。 - nawfal
1
如果你想做的不仅仅是在已排序列表中查找现有元素,比如迭代SortedList的头或尾部,请确保使用Antoine Aubry的版本,而不是被接受的解决方案,该解决方案在不存在元素的情况下只返回一个简单的-1 - Evgeniy Berezovsky

4

在我的情况下,源代码SortedList并没有太多的改变,因为它被用作查找表。所以,在这种情况下,将SortedList转换为List<T>是有意义的,可以进行一次转换。之后,使用内置的List<T>的BinarySearch方法就很容易了...

double targetX = 3.5;

// Assume keys are doubles, may need to convert to doubles if required here.
// The below line should only be performed sparingly as it is an O(n) operation.
// In my case I only do this once, as the list is unchanging.
List<double> keys = xyTable.Keys.ToList();

int ipos = keys.BinarySearch(targetX);

if (ipos >= 0)
{
    // exact target found at position "ipos"
}
else
{
    // Exact key not found: BinarySearch returns negative when the 
    // exact target is not found, which is the bitwise complement 
    // of the next index in the list larger than the target.
    ipos = ~ipos;
    if (ipos >= 0 && ipos < keys.Count)
    {
        if (ipos > 0)
        {
            // target is between positions "ipos-1" and "ipos"
        }
        else
        {
            // target is below position "ipos"
        }
    }
    else
    {
        // target is above position "ipos"
    }
}

目标在“ipos”位置下方 - 您的意思是它在数组中最低键位以下吗? - Mołot
7
因为有人要求进行二分搜索,所以被踩了。ToList是一项多余且缓慢的O(n)操作,更好的性能(CPU和内存消耗)是直接在 IList<K> Keys 上操作,正如ColinE的回答所示,该回答还链接到一个可复制的答案的问题。 - Evgeniy Berezovsky
@EugeneBeresovsky 感谢您的评论 - 我已经添加了一条关于这个事实的评论。就像我在顶部提到的那样,在我的情况下,我正在执行二分搜索的列表是不变的,因此我只在静态构造方法中执行了一次 ToList()。 - dodgy_coder

1
public class Bounds
{
    int lower;
    int upper;

    public Bounds(int lower, int upper)
    {
       this.lower = lower;
       this.upper = upper;
    }
}

public Bounds BinarySearch(List<int> keys, double target)
{
    // lower boundary case returns the smallest key as the lower and upper bounds
    if (target < keys[0])
        return new Bounds(0, 0);

    else if (target < keys[1])
        return new Bounds(0, 1);

    // upper boundary case returns the largest key as the lower and upper bounds
    else if (target > keys[keys.Length - 1])
        return new Bounds(keys.Length - 1, keys.Length - 1);

    else if (target > keys[keys.Length - 2])
        return new Bounds(keys.Length - 2, keys.Length - 1);

    else
        return BinarySearch(keys, target, 0, keys.Length - 1);

}

// 'keys' is a List storing all of the keys from your SortedList.
public Bounds BinarySearch(List<int> keys, double target, int lower, int upper)
{
    int middle = (upper + lower)/2;

    // target is equal to one of the keys
    if (keys[middle] == target)
        return new Bounds(middle - 1, middle + 1);

    else if (keys[middle] < target && keys[middle + 1] > target)
        return new Bounds(middle, middle + 1);

    else if (keys[middle] > target && keys[middle - 1] < target)
        return new Bounds(middle - 1, middle);

    if (list[middle] < target)
         return BinarySearch(list, target, lower, upper/2 - 1);

    if (list[middle] > target)
         return BinarySearch(list, target, upper/2 + 1, upper);
}

这个可能有效..我没有测试它。如果不行,希望它足够接近,可以通过小的调整来使用它。这是一个奇怪的问题,所以我处理了所有边界情况,这样我就不必考虑当范围缩小到2个或更少元素时算法会做什么。


为什么不使用 List<T>.BinarySearch() - svick
我对此不是很熟悉,使用List<T>.BinarySearch()是否足以找到他所需要的内容? - alexD
如果他有List<T>的话,那就好办了,但是他只有IList<T>,所以你的解决方案实际上是一个很好的建议。 - svick

1

来自MSDN,

SortedList对象的元素根据键按照创建SortedList时指定的特定IComparer实现或由键本身提供的IComparable实现进行排序。索引序列基于排序序列。当添加元素时,它会按照正确的排序顺序插入SortedList中,并相应地调整索引。当删除元素时,索引也会相应地调整。因此,随着从SortedList中添加或删除元素,特定键/值对的索引可能会改变。

*****该方法使用二进制搜索算法;因此,该方法是一个O(log n)操作,其中n为Count。*****

从.NET Framework 2.0开始,此方法使用集合对象的Equals和CompareTo方法在item上确定item是否存在。在早期版本的.NET Framework中,使用集合中的对象上的Equals和CompareTo方法来确定这一点。

换句话说,在SortedList中,IndexOfKey方法已经使用了二进制搜索算法,因此在您的情况下无需将其转换为List。

希望能有所帮助。


3
IndexOfKey 只能找到完全匹配的键。问题很明确,这并不是所需的,他们需要“上下键来包围我的目标键”。 - Soonts
1
由于IndexOfKey正在执行二进制搜索算法,我很好奇为什么Microsoft不会实现更有用的BinarySearch(或两者都实现)。除非我的大脑出了什么问题,否则这似乎是一个显而易见的选择? - Collie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接