upper - lower bound c++
¿Cuál es la diferencia entre partition_point y lower_bound? (3)
C ++ 11 incluye el algoritmo std::partition_point() . Sin embargo, para todos los casos que he probado, da la misma respuesta que std::lower_bound() . La única diferencia es el conveniente parámetro T& value .
¿Me perdí algo o estas dos funciones están haciendo más o menos lo mismo?
Ambos utilizan un algoritmo de búsqueda binario (para iterador de acceso aleatorio).
-
std::lower_boundrequiere que el rango sestd::lower_boundacuerdo con el predicado (binario)particionado con respecto alelement < valueexpresiónelement < valueocomp(element, value)(que es el caso si el rango se ordena de acuerdo con el predicado binario). -
std::partition_pointrequiere que el rango se particione de acuerdo con el predicado (unario).
De hecho, puede crear un predicado para poder utilizar el otro algoritmo.
Con:
const std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8};
Puedes hacerlo con lower_bound :
assert(std::is_sorted(v.begin, v.end(), std::less<>));
auto it1 = std::lower_bound(v.begin, v.end(), 5, std::less<>);
o con 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
O bien, con el otro lado.
const std::vector<int> v{1, 3, 4, 2, 7, 5, 8, 6};
Puedes hacerlo con 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);
o con 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
Son básicamente equivalentes. Esta sería una implementación válida de 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);
});
}
Los dos algoritmos se basan en encontrar el punto de partición de un rango particionado, simplemente toman diferentes argumentos con los que buscar (un predicado unario para partition_point , vs un valor o valor y un predicado binario para lower_bound ).
Por lo general, solo pensamos en lower_bound en el contexto de rango ordenado con un predicado binario, aunque un rango completamente ordenado con respecto a dicho predicado no es un requisito para ese algoritmo .
Mientras estamos en ello, upper_bound también se puede implementar en términos de partition_point , solo con los operandos volteados y el predicado negado:
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);
});
}
Es extraño lo diferente que están redactados los dos.
devoluciones de lower_bound (la redacción de upper_bound es similar ):
El iterador más lejano
ien el rango[first, last], de manera que para cada iteradorjen el rango[first, i)cumplen las siguientes condiciones correspondientes:*j < valueocomp(*j, value) != false.
mientras partition_point devuelve
Un iterador
midtal queall_of(first, mid, pred)ynone_of(mid, last, pred)son ambostrue.
Esas frases son equivalentes ya que el requisito es que el rango esté particionado. Pero seguro que no parece ser así a primera vista.
Son más o menos equivalentes, excepto que lower_bound utilizará el operator< para buscar los elementos si no se proporciona un predicado. * partition_point es más genérico, ya que permite que el rango se particione según un predicado genérico en lugar de algún predicado contra un value .
Tenga en cuenta que lower_bound es anterior a la existencia de más conceptos de partición genéricos en C ++.
* y está fuertemente implícito que el predicado usado en lower_bound debe satisfacer una relación menor que, aunque no es obligatorio
De [alg.partitions]
template<class ForwardIterator, class Predicate>
ForwardIterator partition_point(ForwardIterator first, ForwardIterator last, Predicate pred);
Requiere : ForwardIterator''s tipo de valor ForwardIterator''s será convertible al tipo de argumento Predicate''s . [first, last) se pred por pred , es decir, todos los elementos que satisfacen pred antes de los que no.
Devoluciones : un medio iterador tal que all_of(first, mid, pred) y none_of(mid, last, pred) son verdaderos.
Complejidad : O(log(last - first)) aplicaciones de pred .
Y desde [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);
Requiere : Los elementos e de [first, last) se particionarán con respecto a la expresión e < value o comp(e, value) .
Devoluciones : El iterador más avanzado i en el rango [first, last] modo que para cada iterador j en el rango [first, i) cumplen las siguientes condiciones correspondientes: *j < value o comp(*j, value) != false .
Complejidad : a lo sumo log2(last - first) + O(1) comparaciones.