Как найти последний элемент массива при двоичном поиске
-
20-09-2019 - |
Вопрос
В алгоритме двоичного поиска элементом верхней границы является array.length-1
, тогда как мне найти последний элемент массива?
Если нижняя и верхняя границы элемента массива длиной 8 равны 6 и 7 соответственно, то мой средний элемент выглядит как:
Mid=(6+7)/2 т.е.6 в Яве
Решение
На самом деле все сводится к использованию правильных сравнений с правильно выбранной средней точкой.Например (без объявления типа переменной),
binsearch(a,val,left,right){
if(left==right) return left;
mid = (left+right)/2;
if(a[mid] < val)
return binsearch(a,val,mid+1,right);
else
return binsearch(a,val,left,mid);
}
даст вам индекс самого левого элемента, который соответствует val (даже если это самый правый элемент в массиве).Вам не нужно явно проверять последние два значения или округлять их в большую сторону, вместо того чтобы использовать встроенное усечение целых чисел.
Однако, если вы хотите, чтобы индекс самого правого элемента был равен val, вам нужно изменить оператор < на >, а Mid должен быть задан как
mid = (left+right+1)/2;
Это так просто.
Редактировать:Еще одна вещь: я посмотрел на свой код, который использую для этого, и понял, что вам также нужно изменить вызовы binsearch, чтобы они оказывались на самом правом элементе.Я просто опубликую полный код для этого (что я должен был сделать в первую очередь).Вот двоичный поиск, чтобы найти самый правый элемент, равный значению val.
binsearch(a,val,left,right){
if(left==right) return left;
mid = (left+right+1)/2;
if(a[mid] > val)
return binsearch(a,val,left,mid-1);
else
return binsearch(a,val,mid,right);
}
Другие советы
Самый простой подход – использовать полуоткрытые диапазоны.Таким образом, ваша верхняя граница указывает на один шаг после последнего допустимого элемента в массиве, тогда как нижняя граница указывает непосредственно на первый элемент.Однако во время поиска вы рассматриваете свой диапазон как включающий: верхняя граница, выходящая за пределы диапазона, является действительным результатом «совпадение не найдено».
В начале каждой итерации у вас есть...
lower <= target <= upper
«цель» означает индекс, который будет найден и возвращен.
Вы рассчитываете середину как «(верхний + нижний) / 2».Поскольку это усекает, Mid никогда не может совпадать с Upper, что важно.Поскольку «верхний» может находиться за пределами границ, мы никогда не сможем юридически вычислить «массив [верхний]».
Чтобы найти первый элемент, больший или равный ключу...
if array[mid] >= k : lower <= target <= mid
if array[mid] < k : mid+1 <= target <= upper
Чтобы найти первый предмет, превышающий ключ...
if array[mid] > k : lower <= target <= mid
if array[mid] <= k : mid+1 <= target <= upper
Эти поддиапазоны являются инклюзивными и должны точно совпадать, но не перекрываться.Перекрытие одного элемента в середине (легкая ошибка) приводит к бесконечным циклам, и это одна из причин, почему мы используем Mid+1 для одного поддиапазона.
Обратите внимание, что все, что меняется между двумя поисками, — это оператор сравнения.
Чтобы найти последнее меньшее или равное, найдите первое большее и вычтите единицу из результата.Вы можете получить -1, если все элементы массива больше ключа.
Обратите внимание: вы проверяете ключ только на середине на каждой итерации (вы знаете, что нижняя и верхняя границы уже верны) и выполняете только одну условную проверку.
Выполните проверку выхода за пределы и проверку равенства (если это то, что вам нужно) снаружи петли.
int find_first_ge (int key)
{
int lower = 0;
int upper = array.length;
while (upper > lower)
{
int mid = (lower + upper) / 2;
if (array [mid] >= key) // for find_first_gt, use ">" here
{
upper = mid;
}
else
{
lower = mid + 1;
}
}
return lower;
}
ПРИМЕЧАНИЕ
Отредактировано, чтобы исправить некоторые ошибки, из-за которых это оставалось таким же бесконечным циклом, как и то, что оно должно было исправить.
Хитрость заключается в том, чтобы гарантировать, что разделенные пополам поддиапазоны будут точно такими, как необходимо после каждого ключевого теста, и всегда по крайней мере, на один меньший, чем исходный полный диапазон - и из-за самоуверенности и плохой памяти именно в этом мне удалось ошибиться.Вышеупомянутое основано на реальном рабочем коде в часто используемой библиотеке (поиск внутри узла в библиотеке многостороннего дерева), поэтому, если это неправильно, у меня есть большой проблемы ;-)
ПРИМЕЧАНИЕ
Отредактировано еще раз, чтобы улучшить формулировку и упростить описания границ поддиапазонов (отмечено, что хотя диапазоны полуоткрыты, они рассматриваются как включающие).
Если ваша нижняя и верхняя границы находятся в пределах друг друга, отметьте обе.
Каждый раз можно округлять.
(6+7)/2.0 == 6.5
Округлите и получите 7.
Или вы можете просто добавить один к своей средней точке.
середина = (6+7)/2 + 1
Другой способ — изменить начальную или конечную точку на +1 или -1 для следующей рекурсии.Это то, что показывает статья в Википедии на эту тему в некоторых реализациях.
мин = середина+1
или
макс = середина 1
Двоичный поиск, как известно, сложен, чтобы получить точный результат.В книге содержится очень тщательный анализ различных проблем и крайних случаев, а также правильная реализация. Жемчуг программирования, книгу, которую, вероятно, должен был прочитать каждый программист хотя бы один раз.