Де я можу отримати «корисний» алгоритм пошуку бінарного C ++?


106

Мені потрібен алгоритм бінарного пошуку, сумісний з контейнерами ST + C ++, щось подібне std::binary_searchдо <algorithm>заголовка стандартної бібліотеки , але мені він потрібен, щоб повернути ітератор, який вказує на результат, а не простий булевий сигнал, який повідомляє мені, чи існує елемент.

(Зі сторони, про що, мабуть, думав стандартний комітет, коли вони визначали API для binary_search ?!)

Моє головне занепокоєння тут полягає в тому, що мені потрібна швидкість двійкового пошуку, тому, хоча я можу знайти дані за допомогою інших алгоритмів, як було зазначено нижче, я хочу скористатися тим, що мої дані сортуються, щоб отримати переваги бінарного. пошук, а не лінійний пошук.

поки що lower_boundі upper_boundне вдасться, якщо дата відсутня:

//lousy pseudo code
vector(1,2,3,4,6,7,8,9,0) //notice no 5
iter = lower_bound_or_upper_bound(start,end,5)
iter != 5 && iter !=end //not returning end as usual, instead it'll return 4 or 6

Примітка. Я також чудово використовую алгоритм, який не належить до простору імен std, якщо він сумісний з контейнерами. Як, скажімо, boost::binary_search.


2
Щодо редагування: саме тому std :: equi_range є рішенням. В іншому випадку вам доведеться перевірити рівність (або еквівалентність бути більше)
Люк Ермітт

Ви повинні перевірити рівність після використання (нижнього / верхнього) _bound (див. Відповідь нижче).
Люк Турей

Документація down_bound та верхній_bound зазначає, що діапазон повинен бути відсортований, і через це вони можуть бути реалізовані у вигляді двійкового пошуку.
vividos

@vividos, ура! Ви знайшли лише той документ, про який мені потрібно було знати! Дякую!
Роберт Гулд

Роберто, алгоритми нижнього / верхнього_бійного / рівногопомаранчевого кольору не працюють з несортованими діапазонами. Вам просто пощастило побачити, як вони працюють із зразком елементів, який ви взяли.
Люк Ермітт

Відповіді:


97

Немає таких функцій, але ви можете написати просту за допомогою std::lower_bound, std::upper_boundабо std::equal_range.

Проста реалізація може бути

template<class Iter, class T>
Iter binary_find(Iter begin, Iter end, T val)
{
    // Finds the lower bound in at most log(last - first) + 1 comparisons
    Iter i = std::lower_bound(begin, end, val);

    if (i != end && !(val < *i))
        return i; // found
    else
        return end; // not found
}

Іншим рішенням буде використання a std::set, яке гарантує впорядкування елементів та забезпечує метод, iterator find(T key)який повертає ітератор до заданого елемента. Однак ваші вимоги можуть бути не сумісні з використанням набору (наприклад, якщо вам потрібно зберігати один і той же елемент кілька разів).


так, це працює, і я маю подібну реалізацію зараз, однак це "наївна" реалізація, в тому сенсі, що вона не використовує контекст ситуації, в даному випадку відсортовані дані.
Роберт Гулд

5
Я не дуже розумію ваш коментар, оскільки нижній_бійд може бути використаний лише для відсортованих даних. Складність нижча, ніж використання find (див. Редагування).
Люк Турей

4
Щоб доповнити відповідь Люка, перегляньте класичну статтю Метта Остерна « Чому не слід використовувати набір та що слід використовувати замість цього» (C ++ звіт 12: 4, квітень 2000 р.), Щоб зрозуміти, чому двійковий пошук із відсортованими векторами переважно кращий для std :: set , який є асоціативним контейнером на основі дерева.
ZunTzu

16
Не використовуйте *i == val! Швидше використовувати !(val < *i). Причина в тому, що lower_boundвикористання <не є ==(тобто Tнавіть не потрібно, щоб вони були порівнянні з рівністю). (Див. Ефективну STL Скотта Майєра для пояснення різниці між рівністю та еквівалентністю .)
gx_

1
@ CanKavaklıoğlu Немає елемента, розташованого на end. Діапазони в стандартній бібліотеці C ++ представлені з напіввідкритими інтервалами: кінцевий ітератор "вказує" після останнього елемента. Як такий, його можна повернути алгоритмами, щоб вказати, що значення не знайдено.
Люк Турайль

9

Ви повинні подивитися std::equal_range. Він поверне пару ітераторів до діапазону всіх результатів.


Відповідно до cplusplus.com/reference/algorithm/equal_range, вартість std :: equ_range приблизно вдвічі вище, ніж std :: lower_bound. Здається, що він завершує виклик std :: lower_bound і виклик std :: upper_bound. Якщо ви знаєте, що у ваших даних немає дублікатів, то найкращий вибір - це overkill, а std :: lower_bound (як показано у верхній відповіді).
Брюс Доусон

@BruceDawson: cplusplus.com дає лише посилання на реалізацію для визначення поведінки ; для реальної реалізації ви можете перевірити свою улюблену стандартну бібліотеку. Наприклад, у llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm ми можемо побачити, що виклики до нижнього та верхнього_бійного виконуються на роз'єднаних інтервалах (після деякого ручного двійкового пошуку). При цьому, ймовірно, це буде дорожче, особливо на діапазонах із збігом кількох значень.
Маттьє М.

6

Є їх набір:

http://www.sgi.com/tech/stl/table_of_contents.html

Шукати:

Окрему записку:

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


3
binary_search не повертає ітератор, як я вже згадував, тому я шукаю альтернативу.
Роберт Гулд

1
Так, я знаю. Але він вписується у набір алгоритмів двійкового пошуку. Тож приємно іншим про це знати.
Мартін Йорк

8
binary_search просто, як і багато інших речей у STL, названий неправильним. Я ненавиджу це. Тестування на існування - це не те, що щось шукати.
OregonGhost

2
Ці функції бінарного пошуку не є корисними у тому випадку, коли ви хочете знати індекс потрібного елемента. Я повинен написати свою рекурсивну функцію для цього завдання. Я сподіваюся, що цей шаблон <class T> int bindary_search (const T & item) повинен бути доданий до наступної версії C ++.
Kemin Zhou

3

Якщо std :: lower_bound занадто низький, щоб вам сподобалося, ви можете перевірити boost :: container :: flat_multiset . Це заміна, що випадає для std :: multiset, реалізована як відсортований вектор за допомогою двійкового пошуку.


1
Гарне посилання; а також гарна посилання в засланні: lafstern.org/matt/col1.pdf , який описує , як пошуки реалізовані з допомогою відсортованого вектора, а не набір (хоча обидва журналу (N)), мають значно кращі константи пропорційності і ~ вдвічі швидше (недоліком є ​​більший час ВСТАНОВЛЕННЯ).
Dan Nissenbaum

2

Найкоротша реалізація, цікаво, чому вона не входить у стандартну бібліотеку:

template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
{
    // Note: BOTH type T and the type after ForwardIt is dereferenced 
    // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. 
    // This is stricter than lower_bound requirement (see above)

    first = std::lower_bound(first, last, value, comp);
    return first != last && !comp(value, *first) ? first : last;
}

З https://en.cppreference.com/w/cpp/algorithm/lower_bound


Я можу подумати з двох причин. Це не в стандартній бібліотеці: вони вважають, що це легко реалізувати, але головна причина, ймовірно, полягає в тому, що він може вимагати зворотної версії оператора () (), якщо значення не є взаємозамінним з * першим.
user877329

1

Перевірте цю функцію, qBinaryFind :

RandomAccessIterator qBinaryFind ( RandomAccessIterator begin, RandomAccessIterator end, const T & value )

Здійснює двійковий пошук діапазону [початок, кінець) і повертає позицію виникнення значення. Якщо значення не відбувається, повернення закінчується.

Елементи в діапазоні [початок, кінець] повинні бути відсортовані у порядку зростання; див. qSort ().

Якщо є багато подій одного і того ж значення, будь-яке з них можна повернути. Використовуйте qLowerBound () або qUpperBound (), якщо вам потрібен тонший контроль.

Приклад:

QVector<int> vect;
 vect << 3 << 3 << 6 << 6 << 6 << 8;

 QVector<int>::iterator i =
         qBinaryFind(vect.begin(), vect.end(), 6);
 // i == vect.begin() + 2 (or 3 or 4)

Функція включена в <QtAlgorithms>заголовок, який є частиною бібліотеки Qt .


1
На жаль, цей алгоритм не сумісний з контейнерами STL.
bartolo-otrit


0
int BinarySearch(vector<int> array,int var)
{ 
    //array should be sorted in ascending order in this case  
    int start=0;
    int end=array.size()-1;
    while(start<=end){
        int mid=(start+end)/2;
        if(array[mid]==var){
            return mid;
        }
        else if(var<array[mid]){
            end=mid-1;
        }
        else{
            start=mid+1;
        }
    }
    return 0;
}

Приклад: Розглянемо масив, A = [1,2,3,4,5,6,7,8,9] Припустимо, ви хочете шукати індекс 3 Спочатку, start = 0 і end = 9-1 = 8 Тепер , з початку <= кінця; середина = 4; (масив [середина], який дорівнює 5)! = 3 Тепер, 3 лежить зліва від середини як менший за 5. Отже, ми шукаємо лише ліву частину масиву Отже, тепер start = 0 і end = 3; mid = 2. Масив Since [середина] == 3, отже, ми отримали номер, який шукали. Отже, ми повертаємо його індекс, рівний середині.


1
Добре мати код, але ви могли б покращити відповідь, надавши коротке пояснення того, як це працює для людей, які не знайомі з мовою.
Taegost

Хтось неправильно позначив вашу публікацію як неякісну . Відповідь лише для коду - неякісна . Чи намагається відповісти на запитання? Якщо ні, позначте як "не відповідь" або рекомендуйте видалити (якщо в черзі на огляд). б) Це технічно неправильно? Відхилення або коментар.
Вай Ха Лі

0

Рішення, що повертає позицію всередині діапазону, може бути таким, використовуючи лише операції над ітераторами (воно повинно працювати, навіть якщо ітератор не є арифметичним):

template <class InputIterator, typename T>
size_t BinarySearchPos(InputIterator first, InputIterator last, const T& val)
{       
    const InputIterator beginIt = first;
    InputIterator element = first;
    size_t p = 0;
    size_t shift = 0;
    while((first <= last)) 
    {
        p = std::distance(beginIt, first);
        size_t u = std::distance(beginIt, last);
        size_t m = p + (u-p)/2;  // overflow safe (p+u)/2
        std::advance(element, m - shift);
        shift = m;
        if(*element == val) 
            return m; // value found at position  m
        if(val > *element)
            first = element++;
        else
            last  = element--;

    }
    // if you are here the value is not present in the list, 
    // however if there are the value should be at position u
    // (here p==u)
    return p;

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.