Обоснование для std:: lower_bound и std:: upper_bound?

STL предоставляет двоичные функции поиска std:: lower_bound и std:: upper_bound, но я склонен не использовать их, потому что я не мог вспомнить, что они делают, потому что их контракты кажутся мне совершенно мистифицированными.

Просто взглянув на имена, Я предполагаю, что "lower_bound" может быть коротким для "последней нижней границы",
т.е. последний элемент в отсортированном списке, который равен <= заданный вал (если он есть).
И аналогичным образом я бы предположил, что "upper_bound" может быть коротким для "первой верхней границы",
т.е. первый элемент в отсортированном списке, который является >= заданный вал (если он есть).

Но в документации говорится, что они что-то отличаются от этого - что-то, что кажется смешением назад и случайным, для меня. Перефразировать документ:
  - lower_bound находит первый элемент, который >= val
  - upper_bound находит первый элемент, который > val

Таким образом, lower_bound не обнаруживает нижней границы; он находит первую верхнюю границу!? И upper_bound находит первую строгую верхнюю границу.

Это имеет смысл? Как вы его помните?

Ответ 1

Если у вас есть несколько элементов в диапазоне [ first, last), значение которого равно значению val, которое вы ищете, тогда диапазон [l, u), где

l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)

- это в точности диапазон элементов, равный val в диапазоне [first, last). Итак, l и u - это "нижняя граница" и "верхняя граница" для равного диапазона. Это имеет смысл, если вы привыкли думать с точки зрения полуоткрытых интервалов.

(Обратите внимание, что std::equal_range возвращает как нижнюю, так и верхнюю границу пары в один вызов.)

Ответ 2

std::lower_bound

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

std::upper_bound

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

Таким образом, смешивая нижнюю и верхнюю границы, вы можете точно описать, где начинается ваш диапазон и где он заканчивается.

Есть ли в этом смысл??

Да.

Пример:

представить вектор

std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };

auto lower = std::lower_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                  // ^ lower

auto upper = std::upper_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                           // ^ upper

std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));

Отпечатки: 4 4 4


http://en.cppreference.com/w/cpp/algorithm/lower_bound

http://en.cppreference.com/w/cpp/algorithm/upper_bound

Ответ 3

В этом случае, думаю, картина стоит тысячи слов. Предположим, что мы используем их для поиска 2 в следующих коллекциях. Стрелки показывают, какие итераторы возвратятся:

enter image description here

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

Это (между прочим) делает возвращаемые итераторы пригодными для использования в качестве параметра hint для insert.

Поэтому, если вы используете их как подсказку, вставляемый вами элемент станет новым первым элементом с этим значением (если вы использовали lower_bound) или последний элемент с этим значением (если вы использовали upper_bound). Если коллекция ранее не содержала элемент с этим значением, вы все равно получите итератор, который можно использовать как hint, чтобы вставить его в нужную позицию в коллекции.

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

Ответ 4

Рассмотрим последовательность

1 2 3 4 5 6 6 6 7 8 9

нижняя граница для 6 - это положение первых 6.

верхняя граница для 6 - это положение 7.

эти позиции служат общей (начальной, конечной) парой, обозначающей прогон 6 значений.


Пример:

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

auto main()
    -> int
{
    vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
    auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
    auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
    for( auto it = pos1; it != pos2; ++it )
    {
        cout << *it;
    }
    cout << endl;
}

Ответ 5

Я принял ответ Брайана, но я просто понял еще один полезный способ подумать об этом, что добавляет ясности для меня, поэтому я добавляю это для справки.

Подумайте о возвращенном итераторе как указывающем, а не о элементе * iter, но только перед этим элементом, то есть между этим элементом и предыдущим элементом в списке, если он есть. Думая об этом таким образом, контракты двух функций становятся симметричными: lower_bound находит положение перехода от < val к >= val, а upper_bound находит положение перехода от <= val до > val. Или, говоря иначе, lower_bound - это начало диапазона элементов, которые сравниваются с val (т.е. Диапазон, возвращаемый std:: equal_range), а upper_bound - их конец.

Я бы хотел, чтобы они говорили об этом так (или любые другие хорошие ответы) в документах; что сделало бы его гораздо менее загадочным!

Ответ 6

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

Если вы пытаетесь найти что-то в последовательности, используйте lower_bound - он будет указывать непосредственно на элемент, если он найден.

Если вы вставляете в последовательность, используйте upper_bound - он сохраняет исходный порядок дубликатов.

Ответ 7

Уже есть хорошие ответы относительно того, что std::lower_bound и std::upper_bound есть.

Я хотел бы ответить на ваш вопрос "как их запомнить"?

Его легко понять/запомнить, если мы проведем аналогию с методами STL begin() и end() любого контейнера. begin() возвращает исходный итератор в контейнер, а end() возвращает итератор, который находится за пределами контейнера, и все мы знаем, насколько они полезны при итерации.

Теперь, в отсортированном контейнере и заданном значении, lower_bound и upper_bound вернут диапазон итераторов для этого значения, легко итерации (как начало и конец)

Практическое использование::

Помимо вышеупомянутого использования в отсортированном списке для доступа к диапазону для заданного значения, одним из лучших приложений upper_bound является доступ к данным, имеющим отношение "много-к-одному" на карте.

Например, рассмотрим следующее соотношение: 1 → a, 2 → a, 3 → a, 4 → b, 5 → c, 6 → c, 7 → c, 8 → c, 9 → c, 10 → c

Вышеуказанные 10 отображений можно сохранить на карте следующим образом:

numeric_limits<T>::lowest() : UND
1 : a
4 : b
5 : c
11 : UND

Значения могут быть доступны с помощью выражения (--map.upper_bound(val))->second.

При значениях T, начиная от самого низкого до 0, выражение вернет UND. При значениях T от 1 до 3 он вернет "a" и т.д.

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

Ответ 8

Да. Вопрос абсолютно имеет смысл. Когда кто-то дал этим функциям свои имена, они думали только о отсортированных массивах с повторяющимися элементами. Если у вас есть массив с уникальными элементами, "std :: lower_bound()" больше похож на поиск "верхней границы", если он не находит фактический элемент.

Так вот что я помню об этих функциях:

  • Если вы выполняете двоичный поиск, рассмотрите возможность использования std :: lower_bound() и прочтите руководство. На нем также основан std :: binary_search().
  • Если вы хотите найти "место" значения в отсортированном массиве уникальных значений, рассмотрите std :: lower_bound() и прочитайте руководство.
  • Если у вас есть произвольная задача поиска в отсортированном массиве, прочитайте руководство как для std :: lower_bound(), так и для std :: upper_bound().

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

Ответ 9

Представьте, что бы вы сделали, если хотите, чтобы первый элемент был равен val в [first, last). Вы сначала исключаете из первых элементов, которые строго меньше, чем val, а затем исключают назад из последних - 1 те, которые строго больше val. Тогда оставшийся диапазон равен [lower_bound, upper_bound]

Ответ 10

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

lower_bound: находит первую позицию, в которую можно вставить [val] без изменения порядка.

upper_bound: находит последнюю позицию, в которую можно вставить [ val] без изменения порядка.

this [first, last) формирует диапазон, в который val может быть вставлен, но при этом сохраняет первоначальный порядок контейнера

lower_bound возвращает "first", т.е. находит "нижнюю границу диапазона"

upper_bound возвращает "последний", т.е. находит "верхнюю границу диапазона"

Ответ 11

Для массива или вектора:

станд:: lower_bound: Возвращает итератор, указывающий на первый элемент в диапазоне

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

станд:: upper_bound: Возвращает итератор, указывающий на первый элемент в диапазоне

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

  • больше значения. (для массива или вектора в порядке возрастания)