Resumen
Buscar y ordenar son los dos problemas fundamentales sobre colecciones, y entre ellos hay un intercambio: ordenar una vez cuesta, pero abarata cada búsqueda posterior. La clase escribe a mano la búsqueda lineal y la binaria, que sobre datos ordenados descarta la mitad en cada paso; justifica la binaria con su invariante, prueba que termina, cuenta sus vueltas y estudia los tres errores que la arruinan. Siguen los ordenamientos por selección y por inserción, con sus invariantes y el conteo exacto de sus comparaciones; después, los mismos problemas con la biblioteca de rangos, y la cuenta que decide cuándo conviene ordenar antes de buscar.
Objetivos de aprendizaje
- Implementar la búsqueda lineal y la binaria, y justificar la binaria con su invariante («si el valor está, está entre
lowyhigh»), con la prueba de que termina. - Implementar el ordenamiento por selección y por inserción, enunciar el invariante de cada uno y contar sus comparaciones en el mejor y en el peor caso.
- Usar
std::ranges::sort,std::ranges::contains,std::ranges::find,std::ranges::binary_searchystd::ranges::lower_bounden lugar de las versiones propias cuando corresponda, respetando sus precondiciones. - Decidir, según el número de búsquedas previstas, si conviene ordenar los datos antes de buscar en ellos, y justificar la decisión con el conteo de comparaciones.
Búsqueda lineal
Buscar es responder, dada una colección y un valor, si el valor está y dónde. Sin información sobre el orden de los datos, no hay más estrategia que mirarlos uno por uno: es la búsqueda lineal. Cuando el valor no está, este curso devuelve values.size(), la primera posición que no pertenece al vector: no es un índice válido, de modo que no se confunde con una respuesta, y es la misma convención que sigue la biblioteca, como se verá en la quinta sección.
#include <cstddef>#include <print>#include <vector>// Poscondición: devuelve el menor índice i con values[i] == target,// o values.size() si target no está.std::size_t linearSearch(const std::vector<int>& values, int target){ // Invariante: target no está en values[0], ..., values[i - 1]. for (std::size_t i{0}; i < values.size(); ++i) { if (values[i] == target) { return i; } } return values.size();}int main(){ const std::vector<int> codes{405, 112, 318, 207, 112, 530}; for (const int target : {207, 112, 999}) { const std::size_t pos{linearSearch(codes, target)}; if (pos == codes.size()) { std::println("{}: no está", target); } else { std::println("{}: posición {}", target, pos); } }}207: posición 3112: posición 1999: no estáEl invariante de la línea 9 da la corrección. Al comenzar, con i igual a 0, afirma algo sobre un tramo vacío y es verdadero. Si vale al comenzar una vuelta y values[i] no es el buscado, vale al terminarla con un elemento más. Ahora bien, el bucle tiene dos salidas: si return i; se ejecuta, values[i] es el buscado y, por el invariante, ningún elemento anterior lo era, de donde i es la primera aparición (por eso el 112, que aparece dos veces, se informa en la posición 1); si el bucle termina por la condición, i vale values.size() y el invariante dice que el valor no está en ninguna parte. El return dentro del bucle termina la función, y con ella el bucle, y ahorra una bandera.
El costo, en el sentido de la clase El costo de un algoritmo, se mide por las comparaciones values[i] == target. Si el valor no está, se hacen exactamente , una por elemento: el peor caso. Si está en la posición , se hacen . La búsqueda lineal es, entonces, : con el doble de datos, el doble de trabajo. Sin información adicional no puede hacerse mejor: un algoritmo que dejara de mirar un elemento no sabría si el valor estaba allí.
Cargando el contenido…