в чем разница между mid=(beg+end)/2 и mid=beg+(end-beg)/2 в бинарном поиске?

Это проблема из пятого издания учебника по С++, проблема 3.26, я не знаю разницы между ними? Может быть, второй может избежать переполнения.


person Pegasus    schedule 08.01.2014    source источник
comment
Это случалось даже с лучшими: двоичный поиск Джона Бентли в Programming Pearls содержал эту ошибку переполнения.   -  person TemplateRex    schedule 08.01.2014


Ответы (4)


Может быть, второй может избежать переполнения.

Точно. Нет никакой гарантии, что beg+end представим; но во втором случае промежуточные значения, как и ожидаемый результат, не превышают end, поэтому опасности переполнения нет.

Вторая форма также может использоваться для аффинных типов, таких как указатели и другие итераторы произвольного доступа, которые можно вычесть, чтобы получить расстояние, но не сложить вместе.

person Mike Seymour    schedule 08.01.2014
comment
Забавно, что пространство указателей (и итераторов произвольного доступа) на диапазон является аффинным пространством: поэтому аффинные комбинации итераторов произвольного доступа (например, получение их среднего значения) — это логически правильное действие. Нам просто не хватает примитивов аффинных комбинаций в C++. - person Yakk - Adam Nevraumont; 08.01.2014
comment
@Yakk: Спасибо, я ломал голову, чтобы вспомнить слово affine. Прошло слишком много времени с тех пор, как я изучал чистую математику. - person Mike Seymour; 08.01.2014
comment
Это прекрасно помнить. - person Yakk - Adam Nevraumont; 08.01.2014

В общем случае оба выражения неверны. Например, первое выражение неверно, потому что нет такой операции, как + для указателей или итераторов. Второе выражение некорректно, если используются итераторы неслучайного доступа. Например, когда используются двунаправленные итераторы.

Таким образом, правильная конструкция в C++ будет выглядеть следующим образом.

mid = std::next( beg, std::distance( beg, end ) / 2 );
person Vlad from Moscow    schedule 08.01.2014
comment
Когда вы захотите запустить бинарный поиск на итераторе неслучайного доступа? - person John Bartholomew; 08.01.2014
comment
Это не только я. Но стандарт С++ объявляет std::binary_search с форвардными итераторами template‹class ForwardIterator, class T› bool binary_search(ForwardIterator first, ForwardIterator last, const T& value); - person Vlad from Moscow; 08.01.2014
comment
Я полагаю, это имеет смысл, если функция сравнения очень дорогая. Обычно это не имеет смысла, поскольку вы теряете ограничение O (log n) во время выполнения. - person John Bartholomew; 08.01.2014

Если мы рассмотрим две строки в более общем контексте, не связанном с бинарным поиском, можно сделать следующие наблюдения:

Вы правы в том, что вторая форма пытается избежать проблемы переполнения, пытаясь представить число, большее, чем максимальное представимое число.

Нет никаких ограничений на то, насколько велики отдельные числа beg и end, поэтому потенциально они оба могут быть больше половины максимального представимого числа. Их добавление означает, что промежуточный результат (beg+end) может переполниться.

Второе решение, кажется, устраняет риск переполнения, но вводит еще один. Если значения являются значениями со знаком, их разница может снова переполниться (или потерять значение, в зависимости от их знаков). Беззнаковые значения не имеют проблем.

Есть еще одно решение, которое вы не опубликовали:

mid = beg/2 + end/2

Это решает все проблемы с переполнением и потерей значимости, но вводит новую проблему потери точности. При работе с целочисленными значениями деление на 2 может дать результат, равный 0,5, а сложение их вместе означает, что mid может отличаться на 1:

mid = 3/2 + 5/2; // mid is 3, instead of the 4 expected

Работа со значениями с плавающей запятой имеет и другие проблемы с точностью.

Возвращаясь к рассматриваемой проблеме, бинарному поиску, легко увидеть, что beg и end являются беззнаковыми значениями, поэтому второе решение всегда будет давать правильный результат.

person Tibos    schedule 08.01.2014

Ответ в книге:

«Поскольку итератор, возвращаемый из конца, не обозначает элемент, он не может быть увеличен или разыменован».

Графически это имеет смысл как асимметричный диапазон, [начало, вне конца) или полуоткрытый диапазон.

Из Accelerated C++, стр. 28, Koenig.

person flintjr2    schedule 25.06.2017