binary search

топ 100 блогов _winnie30.04.2015 В книге "жемчужины программирования" сообщается, что невозможно написать binary search с первого раза.

На самом деле его невозможно написать с первого раза, если не осознаешь что такое инвариант цикла. Если осознаёшь - то скорее всего запросто напишешь (я вот уже в третий раз за год написал).

Если не осознаёшь инвариант цикла, то с большой вероятностью будет одна из ошибок:

* mid = (lo+hi)/2 или hi/2+lo/2 ? (и нет, дело не в overflow, эти формулы просто разные (1+3)/2 != 1/2 + 3/2 ).
* индексация за пределами массива (начинаем проверять элемент array[-1]).
* вечное зацикливание, когда на маленьком массиве середина посчитанная с ±1 ошибкой - совпадает с концом или началом массива (массив длины 2 остается массивом длины 2).
* неправильный выбор элемента для сравнения, array[mid] или array[mid-1]?
* неправильный выбор между < и <=
* отсутствие осознания, что найдётся, когда есть несколько элементов равных искомому, или ни одного

Как писать двоичный поиск чтобы он не бажил:
* Выбрать инвариант "если искомый элемент существует, то он в [lo, hi)", изначально выбрать lo,hi = 0, N.
* mid = lo+(hi-lo)/2 (при этом для кода верно утверждение "промежуток длиннее 1 всегда укоротится").
* Если ищем первый среди равных - то сравнение выглядит как key<=array[mid-1], если последний - key
* в зависимости от сравнения присваиваем mid в lo или hi (в любом случае, не mid-1).



Двоичный поиск для равномерно распределённых ключей (хешей) ускоряется простым хаком "для каждого из 65536 двубайтовых префиксов запомнить, где он начинается", это убирает 16 начальных итераций.
Вместо 16 можно выбрать что-то другое, исходя из количества RAM и размера массива. Если не жалко оперативной памяти, а элементы совсем рандомные (поэтому легко посчитать, какой длины должны быть префикс, чтобы на каждый было примерно по 5 хешей) - то можно таблицу префиксов удлинить так, что вместо двоичного поиска можно вставить линейный.
Таблица считается таким кодом:

# на 1 длиннее, чем количество префиксов,
# чтобы всегда был "следующий" для T[high_bits(k) + 1]

T = [None] * (2**K + 1)
j = 0
for i in xrange(2**K):
    T[i] = j
    while j < N and high_bits(K, array[j]) == i:
         j += 1
T[2**K] = N

При поиске ключа k - ищем не в [0, N), а в [T[high_bits(k)], T[high_bits(k) + 1])

Для равномерно распределённых хешей есть ещё
интерполяционный поиск, но я привык к табличке, она кажется проще чем корректная реализация интерполяционного поиска. Например про код в ru/en вики я не уверен, что он без зависаний обрабатывает случайные/злонамеренные отклонения от равномерности. Когда я думаю как этот код сделать надёжным, то думается про double-арифметику с квадратными корнями, для поиска интервалов которые "скорее всего" содержат искомый элемент, с удвоением размера интервала, или препроцессинг "максимальное отклонение искомого элемента от расчетной позиции".

Оставить комментарий

Архив записей в блогах:
Собственно сабж :) Рост-вес измерят в пятницу, тогда и допишу. По ощущениям вес в районе 10 кг. Тетя-доктор вчера отожгла - посмотрела, как он колбасится на ковре и говорит - он такой подвижный у вас, толстым не будет! :-D Ещё прикол - когда врач тискала ...
Здравствуйте, сегодня попробуем на себе берлинское метро. Вообще Берлин очень разный город, где-то он хороший, а где-то плохой. Если с прошлой статьей про мусорки можно было поспорить, то с сегодняшней нельзя не согласиться. Давайте посмотрим на главный вход в метро. ...
С утра уехали к моему сыну Антошке. Поминальный день, да... Потом - в Макеевку к моей мамочке. День рождения у человека! Фотографий МНОГО! Кто там сомневался в ...
Именно из-за таких, полностью больных на голову куриц, которые давно утратили как моральные качества, отличающие человека от макаки, так и физические, русских женщин по всему миру считают совершенно тупыми инфантильными существами, способными думать исключительно о брульянтах и бабках. ...
Грандиозную находку удалось обнаружить в глубинах Тихого океана исследователям-океанографам с исследовательского судна, принадлежащего журналу National Geographic: во время экспедиции на архипелаг Соломоновы острова в юго-западной части Тихого океана они нашли самый большой из известных ...