В чем разница между partition_point и lower_bound?

С++ 11 включает алгоритм std::partition_point(). Однако во всех случаях, которые я пробовал, он дает тот же ответ, что и std::lower_bound(). Единственное отличие - удобный параметр T& value.

Я что-то пропустил или эти две функции выполняют более или менее одно и то же?

Ответ 1

Они в основном эквивалентны. Это будет действительная реализация lower_bound:

template <typename ForwardIterator, typename T>
ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last,
    T const& value)
{
    return partition_point(first, last, [&](auto&& elem) {
        return elem < value;
    });
}

template <typename ForwardIterator, typename T, typename Compare>
ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last,
    T const& value, Compare comp)
{
    return partition_point(first, last, [&](auto&& elem) {
        return comp(elem, value);
    });
}

Эти два алгоритма полагаются на поиск точки раздела многораздельного диапазона, они просто принимают разные аргументы для поиска (унарный предикат для partition_point, vs значение или значение и бинарный предикат для lower_bound).

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


Хотя мы и находимся в нем, upper_bound также может быть реализован с точки зрения partition_point, просто с перевернутыми операндами и предикатом:

template <typename ForwardIterator, typename T>
ForwardIterator upper_bound(ForwardIterator first, ForwardIterator last,
    T const& value)
{
    return partition_point(first, last, [&](auto&& elem) {
        return !(value < elem);
    });
}

template <typename ForwardIterator, typename T, typename Compare>
ForwardIterator upper_bound(ForwardIterator first, ForwardIterator last,
    T const& value, Compare comp)
{
    return partition_point(first, last, [&](auto&& elem) {
        return !comp(value, elem);
    });
}

Странно, как иначе они сформулированы.

lower_bound возвращает (upper_bound похожа):

Дальнейший итератор i в диапазоне [first, last] такой, что для каждого итератора j в диапазоне [first, i) выполняются следующие соответствующие условия: *j < value или comp(*j, value) != false.

while partition_point возвращает

Итератор mid, так что all_of(first, mid, pred) none_of(mid, last, pred) all_of(first, mid, pred) и none_of(mid, last, pred) являются true.

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

Ответ 2

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

  • std::lower_bound требует, чтобы диапазон был отсортирован в соответствии с (двоичным) предикатом, разделенным по отношению к element < value выражения element < value или comp(element, value) (это случай, если диапазон отсортирован в соответствии с бинарным предикатом).
  • std::partition_point требует, чтобы диапазон разделялся в соответствии с (унарным) предикатом.

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

С:

const std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8};

Вы можете сделать с lower_bound:

assert(std::is_sorted(v.begin, v.end(), std::less<>));
auto it1 = std::lower_bound(v.begin, v.end(), 5, std::less<>);

или с partition_point:

auto pred = [](int e){ return e < 5; }
assert(std::is_partition(v.begin, v.end(), pred));
auto it2 = std::partition_point(v.begin, v.end(), pred);

assert(it1 == it2); // *it1 = 5

Или, с другой стороны

const std::vector<int> v{1, 3, 4, 2, 7, 5, 8, 6};

Вы можете сделать с partition_point:

auto pred = [](int e){ return e < 5; }
assert(std::is_partition(v.begin, v.end(), pred));
auto it1 = std::partition_point(v.begin, v.end(), pred);

или с lower_bound:

auto pred2 = [](int lhs, int rhs){ return (lhs < 5) > (rhs < 5); }
assert(std::is_sorted(v.begin, v.end(), pred2));
auto it2 = std::lower_bound(v.begin, v.end(), 5, pred2);

assert(it1 == it2); // *it1 == 7

Ответ 3

Они более или менее эквивалентны, за исключением того, что lower_bound будет использовать operator< для поиска элементов, если предикат не предоставлен. * partition_point более универсальна тем, что позволяет разбивать диапазон в соответствии с каким-то общим предикатом, а не некоторым предикатом на value,

Обратите внимание, что lower_bound значительно предшествует существованию более общих концепций секционирования в C++.

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


Из [alg.partitions]

template<class ForwardIterator, class Predicate> 
ForwardIterator partition_point(ForwardIterator first, ForwardIterator last, Predicate pred);

Требуется: ForwardIterators значения ForwardIterators должен быть конвертирован в тип аргументов Predicates. [first, last) разбивается на pred, т.е. все элементы, которые удовлетворяют pred должны появляться перед теми, которые этого не делают.

Возвраты: Итератор mid, так что all_of(first, mid, pred) none_of(mid, last, pred) all_of(first, mid, pred) и none_of(mid, last, pred) являются истинными.

Сложность: O(log(last - first)) приложения pred.

И из [lower.bound]

template<class ForwardIterator, class T>
ForwardIterator
lower_bound(ForwardIterator first, ForwardIterator last,
const T& value);

template<class ForwardIterator, class T, class Compare>
ForwardIterator
lower_bound(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp);

Требуется: Элементы e из [first, last) должны быть разделены относительно выражения e < value или comp(e, value).

Возвращает: более длинный итератор i в диапазоне [first, last] такой, что для каждого итератора j в диапазоне [first, i) выполняются следующие условия: *j < value или comp(*j, value) != false.

Сложность: не более log2(last - first) + O(1) сравнения.