Бинарный поиск по ключам SortedList‹K, V›

Мне нужно написать код для линейной интерполяции, и я пытаюсь найти наиболее эффективный способ поиска ключей SortedList<K, V> для верхних и нижних ключей, окружающих мой целевой ключ.

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

double targetX = 3.5;

Каков наиболее эффективный способ поиска в списке и определения того, что 3,5 находится между 3 и 4? У меня есть метод/обман, который работает для целых чисел (временно вставьте целевой ключ в список, а затем найдите индекс), но я решил попросить профессионалов, чтобы я мог создать качественный код.

Спасибо.


person Nitax    schedule 23.05.2011    source источник
comment
отсортированные звуки идеально подходят для бинарного поиска   -  person Marc    schedule 23.05.2011


Ответы (4)


Бинарный поиск дает вам достойную производительность в списке. Однако свойство Keys в SortedList имеет тип IList, тогда как BinarySearch определено в List. К счастью, вы можете найти реализацию бинарного поиска для IList в этом связанном вопросе:

Как выполнить бинарный поиск в IList‹T›?

person ColinE    schedule 23.05.2011
comment
Это мой любимый ответ здесь :) Должен был быть встроен в SortedList. - person nawfal; 14.06.2014
comment
И если вы хотите сделать больше, чем просто найти существующие элементы в отсортированном списке, например, перебирать голову или хвост SortedList обязательно используйте версию Антуана Обри, а не принятое решение, которое возвращает просто -1 в случае несуществующих элементов. - person Evgeniy Berezovsky; 17.07.2015

В моем случае источник SortedList не сильно меняется, так как он используется в качестве таблицы поиска. Так что в этом случае имеет смысл преобразовать SortedList в List<T> один раз. После этого довольно легко использовать встроенный метод BinarySearch из List<T>...

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"
    }
}
person dodgy_coder    schedule 10.08.2011
comment
цель находится ниже позиции ipos - вы имеете в виду, что она находится ниже нижнего ключа в массиве? - person Mołot; 03.07.2015
comment
Проголосовали против, потому что кто-то, запрашивающий бинарный поиск, заинтересован в производительности. ToList, однако, является лишней и медленной операцией O(n), и для производительности (потребления ЦП и памяти) лучше работать непосредственно на IList<K> Keys, как указано Ответ Колина, который также ссылается на вопрос с копируемым и вставляемым ответом. - person Evgeniy Berezovsky; 17.07.2015
comment
@EugeneBeresovsky спасибо за ваш комментарий - я добавил комментарий по этому факту. В моем случае, как я упоминал выше, список, в котором я выполнял двоичный поиск, неизменен, поэтому я выполнял ToList() только один раз в методе статического конструктора. - person dodgy_coder; 25.04.2016

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 элементов или меньше.

person alexD    schedule 23.05.2011
comment
Почему бы не использовать List<T>.BinarySearch()? - person svick; 24.05.2011
comment
Я не очень хорошо с этим знаком. Будет ли List‹T›.BinarySearch() достаточным, чтобы найти то, что он ищет? - person alexD; 24.05.2011
comment
это было бы, если бы он List<T>, но у него есть только IList<T>, так что ваше решение на самом деле хорошее предложение. - person svick; 24.05.2011

Из MSDN,

Элементы объекта SortedList сортируются по ключам либо в соответствии с конкретной реализацией IComparer, указанной при создании SortedList, либо в соответствии с реализацией IComparable, предоставляемой самими ключами. Последовательность индекса основана на последовательности сортировки. Когда элемент добавляется, он вставляется в SortedList в правильном порядке сортировки, и индексация корректируется соответствующим образом. Когда элемент удаляется, индексация также корректируется соответствующим образом. Поэтому индекс конкретной пары ключ/значение может измениться по мере добавления или удаления элементов из SortedList.

*****В этом методе используется алгоритм бинарного поиска; следовательно, этот метод является операцией O(log n), где n равно Count.*****

Начиная с .NET Framework 2.0, этот метод использует методы Equals и CompareTo объектов коллекции для элемента, чтобы определить, существует ли элемент. В более ранних версиях .NET Framework это определение выполнялось с помощью методов Equals и CompareTo параметра элемента для объектов в коллекции.

Другими словами, метод IndexOfKey в SortedList на самом деле уже использует алгоритм двоичного поиска, поэтому в вашем случае нет необходимости преобразовывать форму SortedList в список.

Надеюсь, поможет..

person Yoel Wigoda Kaver    schedule 30.04.2015
comment
IndexOfKey находит только точное совпадение. Ясно, что вопрос не в том, что нужно, им нужны «верхние и нижние клавиши, которые окружают мою целевую клавишу». - person Soonts; 25.04.2016
comment
Поскольку IndexOfKey выполняет алгоритм BinarySearch, мне любопытно, почему Microsoft не реализовала вместо этого более полезный BinarySearch (или оба). Кажется ежу понятно, если в моем мозгу чего-то не хватает? - person Collie; 24.01.2020