Бинарный поиск для нахождения наименьшего и наибольшего элемента в отсортированном массиве, чем заданное значение?


Таким образом, я пытался реализовать алгоритм бинарного поиска (как можно более общий, который может адаптироваться к различным случаям). Я искал это в интернете, и некоторые используют, while (low != high) и некоторые используют, while (low <= high) и некоторые другие различные условия, которые очень запутывают.

Поэтому я начал писать код для нахождения первого элемента, который больше заданного элемента. Я хочу знать, есть ли более элегантное решение, чем это?

Основной код:

#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#include <stack>
#include <queue>
#include <climits>
#include <set>
#include <cstring>

using namespace std;
int arr1[2000];
int n;
int main (void)
{
    int val1,val2;
    cin>>n;
    for (int i = 0; i < n; i++)
        cin>>arr1[i];
    sort(arr1,arr1+n); 
    cout<<"Enter the value for which next greater element than this value is to be found";   
    cin>>val1;
    cout<<"Enter the value for which the first element smaller than this value is to be found";
    cin>>val2;

    int ans1 = binarysearch1(val1);
    int ans2 = binarysearch2(val2);

    cout<<ans1<<"n"<<ans2<<"n";
    return 0;
}
int binarysearch1(int val)
{
    while (start <= end)
    {
        int mid = start + (end-start)/2;
        if (arr[mid] <= val && arr[mid+1] > val)
            return mid+1;
        else if (arr[mid] > val)
            end = mid-1;
        else
            start = mid+1;
    }
}

Аналогично, для нахождение первого элемента, который меньше данного элемента,

int binarysearch2(int val)
{

    while (start <= end)
    {
        int mid = start + (end-start)/2;
        if (arr[mid] >= val && arr[mid] < val)
            return mid+1;
        else if (arr[mid] > val)
            end = mid-1;
        else
            start = mid+1;
    }
}

Я часто очень запутываюсь, когда мне приходится модифицировать бинарный поиск для такой абстракции. Пожалуйста, дайте мне знать, если есть более простой метод для того же? Спасибо!

5 3

5 ответов:

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

Назовем два пределаПервым ипоследним . Мы хотим найти первый элемент, больший некоторого x . Следующий инвариант будет держаться все время:

Каждый элемент прошедший Последний больше x и каждый элемент до Первый меньше или равен (противоположный случай).

Обратите внимание, что инвариант ничего не говорит об интервале [[5]]., Последний ]. Единственная допустимая инициализация пределов без дальнейшего знания вектора - это первая = 0 и Последняя = последняя позиция вектора. Это удовлетворяет условию, так как нет ничего после последнего и ничего до первого , так что все правильно.

Как интервал [Первый, Последний ] Неизвестен, нам придется продолжать, пока он не опустеет, обновляя границы в последствии.

int get_first_greater(const std::vector<int>& v, int x)
{
  int first = 0, last = int(v.size()) - 1;
  while (first <= last)
  {
    int mid = (first + last) / 2;
    if (v[mid] > x)
      last = mid - 1;
    else
      first = mid + 1;
  }
  return last + 1 == v.size() ? -1 : last + 1;
}
Как вы можете видеть, нам нужны только два случая, поэтому код очень прост. При каждой проверке мы обновляем границы, чтобы всегда сохранять наш инвариант истинным.

Когда цикл заканчивается, используя инвариант, мы знаем, что last + 1 больше x , если он существует, поэтому мы должны только проверить, находимся ли мы все еще внутри нашего вектора или нет.

Имея это в виду, вы можете изменить двоичный поиск, как вы хотите. Давайте изменим его, чтобы найти последнее меньше, чем x . Меняем инвариант:

Каждый элемент перед Первым меньше x и каждый элемент после Последний больше или равен x.

С этим, изменение кода действительно легко:

int get_last_smaller(const std::vector<int>& v, int x)
{
  int first = 0, last = int(v.size()) - 1;
  while (first <= last)
  {
    int mid = (first + last) / 2;
    if (v[mid] >= x)
      last = mid - 1;
    else
      first = mid + 1;
  }
  return first - 1 < 0 ? -1 : first - 1;
}

Проверьте, что мы только изменили оператор (>= вместо>) и возврат, используя тот же аргумент, что и раньше.

Трудно писать правильные программы. И после того, как программа была проверена на правильность, ее следует редко изменять и чаще использовать повторно. В этой строке, учитывая, что вы используете C++, а не C, я бы посоветовал вам использовать библиотеки std C++ в максимально возможной степени. Обе функции, которые вы ищете, даны вам внутри алгоритм.

Http://en.cppreference.com/w/cpp/algorithm/lower_bound http://en.cppreference.com/w/cpp/algorithm/upper_bound делает волшебство для вас, и, учитывая удивительную силу шаблонов, вы должны быть в состоянии использовать эти методы, просто добавив другие методы, которые реализуют порядок.

HTH.

Чтобы частично ответить на этот вопрос, можно было бы разложить фактическое сравнение (используя функцию обратного вызова или аналогичную), в зависимости от того, будет ли первый элемент, который больше, чем элемент, подлежать поиску, или первый элемент, который меньше. Однако в первом блоке кода Вы используете

arr[mid] <= val && arr[mid+1] > val

В то время как во втором блоке происходит сдвиг индекса во втором условии

if (arr[mid] >= val && arr[mid] < val)

Опущено, что кажется непоследовательным.

В ваших процедурах поиска были некоторые ошибки [одна была полностью сломана]. Я их немного почистил, но начал с вашего кода. Примечание: никаких гарантий-здесь уже поздно, но это должно дать вам отправную точку. Обратите внимание на "Ло/привет" является стандартной номенклатуры (например, Ло-это ваш старт и привет тебе конец). Кроме того, обратите внимание, что hi / lo устанавливается в mid и не mid+1 или mid-1

Есть крайние случаи, с которыми нужно бороться. Цикл while должен быть "

int
binarysearch_larger(const int *arr,int cnt,int val)
// arr -- array to search
// cnt -- number of elements in array
// val -- desired value to be searched for
{
    int mid;
    int lo;
    int hi;
    int match;

    lo = 0;
    hi = cnt - 1;

    match = -1;

    while (lo < hi) {
        mid = (hi + lo) / 2;

        if (arr[mid] <= val) && (arr[mid+1] > val)) {
            if ((mid + 1) < cnt)
                match = mid + 1;
            break;
        }

        if (arr[mid] > val)
            hi = mid;
        else
            lo = mid;
    }

    return match;
}

int
binarysearch_smaller(const int *arr,int cnt,int val)
// arr -- array to search
// cnt -- number of elements in array
// val -- desired value to be searched for
{
    int mid;
    int lo;
    int hi;
    int match;

    lo = 0;
    hi = cnt - 1;

    match = -1;

    while (lo < hi) {
        mid = (hi + lo) / 2;

        if (arr[mid] <= val) && (arr[mid+1] > val)) {
            match = mid;
            break;
        }

        if (arr[mid] > val)
            hi = mid;
        else
            lo = mid;
    }

    // the condition here could be "<=" or "<" as you prefer
    if ((match < 0) && (arr[cnt - 1] <= val))
        match = cnt - 1;

    return match;
}

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

Если пара возвращенных итераторов указывает на конец диапазона, это означает, что введенный диапазон был пуст.

У меня есть сделал его настолько общим, насколько мог, и он также обрабатывает маргинальные случаи и дубликаты.

template<typename BidirectionalIterator>
std::pair<BidirectionalIterator, BidirectionalIterator>
lowhigh(BidirectionalIterator first, BidirectionalIterator last,
        typename std::iterator_traits<BidirectionalIterator>::value_type const &val) {
  if(first != last) {
    auto low = std::lower_bound(first, last, val);
    if(low == last) {
      --last;
      return std::make_pair(last, last);
    } else if(low == first) {
     if(first != last - 1) {
        return std::make_pair(first, std::upper_bound(low, last - 1, val) + 1);   
      } else {
        return std::make_pair(first, first);  
      }
    } else {
      auto up = std::upper_bound(low, last, val);
      return (up == last)? std::make_pair(low - 1, up - 1) : std::make_pair(low - 1, up);
    }
  }
  return std::make_pair(last, last);
}

ЖИВАЯ ДЕМОНСТРАЦИЯ