Двоичный поиск
От: Stgl  
Дата: 29.11.14 12:11
Оценка:
Есть нижеследующий алгоритм двоичного поиска.

desiredValue // искомое значение
int a[N]; // массив, где проводится поиск
int left = 0; // левая граница поиска
int right = N; // правая граница поиска
int middle; // среднее значение
while (left < right)
{
    middle = left +  (right - left) / 2;
    if (a[middle] < desiredValue)
        left = middle + 1;
    else
        right = middle;
}
if (a[right] == desiredValue)
    return right;
else
    return -1;


Я не до конца понимаю как работает этот алгоритм. У меня возникает множество вопросов почему.
Например:
1) Я хочу доказать, что результат работы при успешном поиске будет находится в right.
Суть работы алгоритма, в моём понимании, это разделить задачу на мелкие подзадачи
до тех пор пока не останется неделимая задача и тогда можно будет решить её. И... не соображаю дальше как это связать с кодом выше.

2) Доказать, что найденное значение является первым среди возможных одинаковых значений. Тут я смутно понимаю, что это как-то связано с тем, что массив наш отсортирован, но дальше не вижу куда двигаться.
3) Почему при a[middle] < desiredValue мы делаем left = middle + 1? Не получится ли так, что из-за такой границы мы не учтём один элемент? Так то я понимаю, что если не делать middle + 1, то мы никогда не выйдем из цикла. Но вот как выразить это присваивание словами?

У Кнута для лучшего понимания нарисовано двоичное дерево. Действительно, как я увидел тот рисунок двоичного дерева, у меня не возникают все эти вопросы почему, но сейчас то у меня простой массив и у меня сомнения, что двоичное дерево можно просто взять и наложить на массив.


Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.
Re: Двоичный поиск
От: Rinbe Россия  
Дата: 29.11.14 12:22
Оценка:
Здравствуйте, Stgl, Вы писали:

S>Есть нижеследующий алгоритм двоичного поиска.


S>
S>desiredValue // искомое значение
S>int a[N]; // массив, где проводится поиск
S>int left = 0; // левая граница поиска
S>int right = N; // правая граница поиска
S>int middle; // среднее значение
S>while (left < right)
S>{
S>    middle = left +  (right - left) / 2;
S>    if (a[middle] < desiredValue)
S>        left = middle + 1;
S>    else
S>        right = middle;
S>}
S>if (a[right] == desiredValue)
S>    return right;
S>else
S>    return -1;
S>


S>Я не до конца понимаю как работает этот алгоритм. У меня возникает множество вопросов почему.

S>Например:
S>1) Я хочу доказать, что результат работы при успешном поиске будет находится в right.
S>Суть работы алгоритма, в моём понимании, это разделить задачу на мелкие подзадачи
S>до тех пор пока не останется неделимая задача и тогда можно будет решить её. И... не соображаю дальше как это связать с кодом выше.

S>2) Доказать, что найденное значение является первым среди возможных одинаковых значений. Тут я смутно понимаю, что это как-то связано с тем, что массив наш отсортирован, но дальше не вижу куда двигаться.

S>3) Почему при a[middle] < desiredValue мы делаем left = middle + 1? Не получится ли так, что из-за такой границы мы не учтём один элемент? Так то я понимаю, что если не делать middle + 1, то мы никогда не выйдем из цикла. Но вот как выразить это присваивание словами?

S>У Кнута для лучшего понимания нарисовано двоичное дерево. Действительно, как я увидел тот рисунок двоичного дерева, у меня не возникают все эти вопросы почему, но сейчас то у меня простой массив и у меня сомнения, что двоичное дерево можно просто взять и наложить на массив.



S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.


Исходить нужно из этого: Инвариант цикла
Re: Двоичный поиск
От: Сергей Мухин Россия  
Дата: 29.11.14 19:42
Оценка:
Здравствуйте, Stgl, Вы писали:


S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.


https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%B8%D0%BD%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D1%8F
---
С уважением,
Сергей Мухин
Re: Двоичный поиск
От: andy1618 Россия  
Дата: 30.11.14 07:01
Оценка: :)
Здравствуйте, Stgl, Вы писали:

S>Есть нижеследующий алгоритм двоичного поиска.


S>
S>desiredValue // искомое значение
S>int a[N]; // массив, где проводится поиск
S>int left = 0; // левая граница поиска
S>int right = N; // правая граница поиска
S>int middle; // среднее значение
S>while (left < right)
S>{
S>    middle = left +  (right - left) / 2;
S>    if (a[middle] < desiredValue)
S>        left = middle + 1;
S>    else
S>        right = middle;
S>}
S>if (a[right] == desiredValue)
S>    return right;
S>else
S>    return -1;
S>


S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.


Ответ не совсем по теме:
в таких случаях обычно поступаю просто — пишу юнит-тест из примерно десятка кейсов с разными входными данными.
Для данного случая: чётное-нечётное-нулевое число элементов, порядок по возрастанию-по убыванию, строгий-нестрогий.
Если все тесты отработали — считаю, что алгоритму в первом приближении можно доверять.
Re[2]: Двоичный поиск
От: Stgl  
Дата: 30.11.14 16:06
Оценка:
Здравствуйте, Сергей Мухин, Вы писали:

СМ>https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%B8%D0%BD%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D1%8F


Прошу помочь понять верно ли я понял принцип математической индукции.
У нас есть сумма вида: 1 + 2 + 3 +...+ n. Я делаю предположение, что эта сумма вычисляется по формуле n^2. С помощью математической индукции я хочу проверить эту гипотезу.
База индукции. P(1): 1 = n^2 = 1^2. Значит, высказывание P(1) истинно.
Переход. P(n + 1):
Прибавим (n + 1) к обеим частям равенства 1 + 2 + 3 +...+ n = n^2.
1 + 2 + 3 +...+ n + (n + 1) = P(n) + P(n + 1) = n^2 + (n + 1)
Если же мы подставим (n + 1) вместо n в формулу n^2, то получим
(n + 1)^2 = n^2 + 2n + 1
Таким образом гипотеза, что сумма такого ряда чисел вычисляется по формуле n^2 ложна.
Я верно рассуждаю?
Отредактировано 30.11.2014 16:09 Stgl . Предыдущая версия . Еще …
Отредактировано 30.11.2014 16:08 Stgl . Предыдущая версия .
Re: Двоичный поиск
От: andyp  
Дата: 30.11.14 22:26
Оценка:
Здравствуйте, Stgl, Вы писали:

S>У Кнута для лучшего понимания нарисовано двоичное дерево. Действительно, как я увидел тот рисунок двоичного дерева, у меня не возникают все эти вопросы почему, но сейчас то у меня простой массив и у меня сомнения, что двоичное дерево можно просто взять и наложить на массив.


Двоичное дерево относится к подзадачам, а не к массиву. На каждом шаге в цикле решается, в какой половине отсортированного массива находится искомое, т.е. вместо начальной задачи решается задача половинного размера.

S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.

Забей на left. Смотри на right. Он всегда указывает на конец той половины, в которой ищем — т.е. то, что лежит в right, всегда больше или равно тому, что ищем. left постоянно подтягивается под начало этой половины. middle+1 потому что нет смысла дважды просматривать middle.
Re: Двоичный поиск
От: Lexey Россия  
Дата: 01.12.14 09:32
Оценка:
Здравствуйте, Stgl, Вы писали:

S>Есть нижеследующий алгоритм двоичного поиска.


S>
S>desiredValue // искомое значение
S>int a[N]; // массив, где проводится поиск
S>int left = 0; // левая граница поиска
S>int right = N; // правая граница поиска
S>int middle; // среднее значение
S>while (left < right)
S>{
S>    middle = left +  (right - left) / 2;
S>    if (a[middle] < desiredValue)
S>        left = middle + 1;
S>    else
S>        right = middle;
S>}
S>if (a[right] == desiredValue)
S>    return right;
S>else
S>    return -1;
S>


S>Я не до конца понимаю как работает этот алгоритм. У меня возникает множество вопросов почему.

S>Например:
S>1) Я хочу доказать, что результат работы при успешном поиске будет находится в right.

Вообще говоря, не будет, ибо не учтен случай, когда искомый элемент больше всех элементов массива. Пример a[] = {1}, ищем 2. Получаем обращение за границу массива.

S>Суть работы алгоритма, в моём понимании, это разделить задачу на мелкие подзадачи

S>до тех пор пока не останется неделимая задача и тогда можно будет решить её. И... не соображаю дальше как это связать с кодом выше.

Не совсем. На каждом шаге алгоритм отсекает половину диапазона, в которой точно нет искомого значения.

S>2) Доказать, что найденное значение является первым среди возможных одинаковых значений. Тут я смутно понимаю, что это как-то связано с тем, что массив наш отсортирован, но дальше не вижу куда двигаться.


Это тривиально. Инвариантом алгоритма является то, что все, что левее left меньше искомого, а все, что правее right не меньше. Это верно и в конце, где left = right. То есть, левее могут быть только меньшие элементы.

S>3) Почему при a[middle] < desiredValue мы делаем left = middle + 1? Не получится ли так, что из-за такой границы мы не учтём один элемент? Так то я понимаю, что если не делать middle + 1, то мы никогда не выйдем из цикла. Но вот как выразить это присваивание словами?


Потому, что a[middle] заведомо меньше требуемого. Соответственно, нет смысла его оставлять в диапазоне поиска. Что-то не учесть мы не можем, ибо вне [left, right] точно нет того, что нам нужно.
Re[2]: Двоичный поиск
От: andyp  
Дата: 01.12.14 11:30
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Вообще говоря, не будет, ибо не учтен случай, когда искомый элемент больше всех элементов массива. Пример a[] = {1}, ищем 2. Получаем обращение за границу массива.


Таки да. right нужно инициализировать как N-1. Или проверять в конце, что оно не равно N.
Re[2]: Двоичный поиск
От: Кодт Россия  
Дата: 01.12.14 14:33
Оценка:
Здравствуйте, Lexey, Вы писали:

S>>1) Я хочу доказать, что результат работы при успешном поиске будет находится в right.

L>Вообще говоря, не будет, ибо не учтен случай, когда искомый элемент больше всех элементов массива. Пример a[] = {1}, ищем 2. Получаем обращение за границу массива.

Можно мысленно дописать a[N] = +oo и a[-1] = -oo

S>>3) Почему при a[middle] < desiredValue мы делаем left = middle + 1? Не получится ли так, что из-за такой границы мы не учтём один элемент? Так то я понимаю, что если не делать middle + 1, то мы никогда не выйдем из цикла. Но вот как выразить это присваивание словами?


L>Потому, что a[middle] заведомо меньше требуемого. Соответственно, нет смысла его оставлять в диапазоне поиска. Что-то не учесть мы не можем, ибо вне [left, right] точно нет того, что нам нужно.


Кстати, с интервалами у нас интересно вышло. Мы всё время имеем дело с закрытым интервалом [left,right]. То есть, мысленно допускаем наличие a[N].
И только специальные меры не позволяют нам выстрелить по памяти: номер точки разбиения округляется влево.
Впрочем, самая последняя проверка таки стреляет по памяти.
Перекуём баги на фичи!
Re: Двоичный поиск
От: qulinxao lj.rossia.org/users/qulinxao
Дата: 01.12.14 19:55
Оценка:
Здравствуйте, Stgl, Вы писали:

S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.




начнём с трисективного бин поиска.

desiredValue // искомое значение
int a[N]; // массив, где проводится поиск
int left = 0; // левая граница поиска 
int right = N; // правая граница поиска
// диапазон полуоткрытый [0,n)
int middle; // место биекции/триекции 
while (left < right){
    middle = left +  (right - left) / 2;
    if (desiredValue<a[middle])
        right = middle - 1;
    else if (desiredValue==a[middle])
        return middle;
    else //if(a[middle]<desiredValue)
        left = middle + 1;
}
return right;


смотри лекции Степанова почему нужно не -1 и прочий NULL , а обыденную позицию. в данном случае например right вполне ( например когда искомое больше наибольшего в массиве будет возвращёна позиция за последним,)(например когда искомое меньше наименьшего в массиве будет возвращена позиция пред первым(это уже не по феншую ))

из вышеприведенного кода можно с целью пипхола сделать исходный вариант с двумя только ветками — путём слияние равенства с одним из крайних случаев и соответствующей(через рассуждения) модификацией ветвей

ps для красоты:

desiredValue // искомое значение
int a[N]; // массив, где проводится поиск
int left = 0; // левая граница поиска 
int right = N; // правая граница поиска
// диапазон полуоткрытый [0,n)
int middle; // место биекции/триекции 
while(left < right){
    switch(sign(desiredValue-a[middle = left +  (right - left)/2]){
       case -1:right = middle - 1;continue;
       case  0:return middle;
       case  1:left = middle + 1;continue;
    }
}
return right;


pps. лучше писать чуть (не более чем в 2 раза) более медленный поиск в упорядоченном массиве, чем ошибочный быстрый.

ну а вообще реальным программистам бинарный поиск писать за карьеру редко кому надо
несравненно явственней, чем в других дисциплинах
не туда :(
От: qulinxao lj.rossia.org/users/qulinxao
Дата: 01.12.14 19:59
Оценка:
S>>if (a[right] == desiredValue)
S>> return right;
S>>else
S>> return -1;
S>>[/ccode]


кстати у тебя классическая ошибка, чтение за пределы массива:

если desireValue>a[N-1], то

не факт что чтение a[N] (ведь right ни разу не сменится с начального N) не выключит аэс.
несравненно явственней, чем в других дисциплинах
Re: Двоичный поиск
От: Dmitry Ulanov Россия  
Дата: 03.12.14 00:59
Оценка:
Здравствуйте, Stgl, Вы писали:
S>Прошу помочь разобраться с этими вопросами. Я действительно не вижу как это работает.


Ты не видишь как это работает потому что код глючный.
Исправь баги и сразу поймешь как он работает.
Отредактировано 03.12.2014 1:06 Dimka9 . Предыдущая версия . Еще …
Отредактировано 03.12.2014 1:03 Dimka9 . Предыдущая версия .
Отредактировано 03.12.2014 1:02 Dimka9 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.