В чем разница между 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)
сравнения.