Ir al contenido
wannacode

Búsqueda lineal

En resumen: La búsqueda lineal revisa los elementos uno por uno hasta encontrar el buscado o llegar al final: O(n) comparaciones, y funciona con una lista sin ordenar.

Problema

El escáner del almacén

En el almacén los productos están anotados en una lista en el orden en que llegaron: la harina del lunes, el azúcar del martes, el aceite, el arroz. Un trabajador escanea una caja y el programa debe encontrar el producto por su código de barras:

List<Product> shelf = List.of(
new Product("3800200101112", "Harina"),
new Product("3800200104403", "Azúcar"),
new Product("3800200107251", "Aceite"),
new Product("3800200102690", "Arroz"),
new Product("3800200109958", "Sal"),
new Product("3800200105837", "Café"));

Los códigos de barras no están ordenados. No puedes abrir la lista por la mitad y decir «el arroz está más adelante»: el siguiente código puede ser cualquiera. ¿Cómo encuentras el producto y cuánto cuesta cuando hay 50 000 productos y 2000 escaneos al día?

La idea

Miras cada caja

Como el orden no ayuda, solo queda una cosa: empiezas por la primera caja y comparas su código de barras con el que buscas. Si coincide, terminaste: devuelves el lugar. Si no, pasas a la siguiente. Si las cajas se acaban, el producto no está.

Esta es la búsqueda lineal: recorres los elementos uno por uno hasta encontrar el que buscas o llegar al final. Devuelve el lugar (el índice) o una señal de «no está»: -1, porque no existe el lugar -1.

Míralo

El puntero en el estante

static int find(List<Product> shelf, String barcode) {
for (int i = 0; i < shelf.size(); i++) {
if (shelf.get(i).barcode().equals(barcode)) {
return i;
}
}
return -1;
}
int rice = find(shelf, "3800200102690");
System.out.println("3800200102690 -> " + rice + " " + shelf.get(rice).name());
System.out.println("3800200100000 -> " + find(shelf, "3800200100000"));
Salida
3800200102690 -> 3 Arroz
3800200100000 -> -1

static int find(List<Product> shelf, String barcode) {
for (int i = 0; i < shelf.size(); i++) {
if (shelf.get(i).barcode().equals(barcode)) {
return i;
}
}
return -1;
}
int rice = find(shelf, "3800200102690");
int missing = find(shelf, "3800200100000");
Paso a paso, en palabras
  1. El escáner lee el código de barras del arroz: 3800200102690. find empieza por la primera caja.
  2. Compara el código de barras de cada caja con el buscado: harina, azúcar, aceite. No coincide.
  3. La cuarta caja es el arroz. return 3: encontrado tras 4 comparaciones.
  4. Un código de barras que el almacén no tiene: 3800200100000. find empieza otra vez desde el principio.
  5. El puntero pasa por todas las cajas y ninguna coincide.
  6. Se acaban las cajas: return -1 tras 6 comparaciones. Un producto que falta es el peor caso: n comparaciones con n cajas.

El arroz se encuentra tras 4 comparaciones. El código que falta pasa por las 6 cajas: es la única manera de decir que no está.

Los códigos de barras se comparan con equals, no con ==. Con texto, == pregunta si es el mismo objeto en memoria, y el código que llega del escáner es otro objeto con los mismos dígitos.

Pruébalo

Completa

¿Qué devuelve find cuando se acaban las cajas?

static int find(List<Product> shelf, String barcode) {
    for (int i = 0; i < shelf.size(); i++) {
        if (shelf.get(i).barcode().equals(barcode)) {
            return i;
        }
    }
    return ;
}

La idea

Cuántas comparaciones

El número de comparaciones depende de dónde está el producto:

  • el mejor caso: la primera caja, 1 comparación;
  • el peor caso: la última caja o un producto que no está, n comparaciones;
  • el caso habitual: si cada lugar es igual de probable, en promedio unas n/2.

El peor caso y el habitual crecen con n, así que la búsqueda lineal es O(n). El programa cuenta:

static int comparisons(List<Product> shelf, String barcode) {
int count = 0;
for (Product product : shelf) {
count++;
if (product.barcode().equals(barcode)) {
return count;
}
}
return count;
}
System.out.println("Harina: " + comparisons(shelf, "3800200101112"));
System.out.println("Arroz: " + comparisons(shelf, "3800200102690"));
System.out.println("Falta: " + comparisons(shelf, "3800200100000"));
Salida
Harina: 1
Arroz: 4
Falta: 6

Ejecuta los dos casos extremos en un almacén grande. Contamos una comparación por caja:

  • El código de barras no está (peor caso) O(n)
  • El primer producto (mejor caso) O(1)
Algoritmon = 10n = 1000n = 100.000
El código de barras no está (peor caso)101000100.000
El primer producto (mejor caso)111
El código se ejecuta en tu navegador, en JavaScript. La cantidad de operaciones es la misma en todos los lenguajes, porque el algoritmo es el mismo. El tiempo depende de la máquina, así que solo sirve para comparar. «≈» es una estimación a partir de la curva: un n tan grande no lo ejecutamos, para que la página no se congele.

La primera caja cuesta 1 comparación para cualquier n: O(1). Un producto que falta cuesta n: con 100 000 productos son 100 000 comparaciones. La memoria es O(1): un índice.

Pruébalo

Predice la salida

La sal es la quinta caja del estante. ¿Cuántas comparaciones hace la búsqueda?

static int comparisons(List<Product> shelf, String barcode) {
int count = 0;
for (Product product : shelf) {
count++;
if (product.barcode().equals(barcode)) {
return count;
}
}
return count;
}
System.out.println("Sal: " + comparisons(shelf, "3800200109958"));
Respuesta

Míralo

Las funciones incorporadas buscan igual

Cada lenguaje tiene una búsqueda lista en una lista. Por dentro es el mismo bucle:

List<String> barcodes = List.of(
"3800200101112", "3800200104403", "3800200107251",
"3800200102690", "3800200109958", "3800200105837");
System.out.println(barcodes.indexOf("3800200102690"));
System.out.println(barcodes.indexOf("3800200100000"));
System.out.println(barcodes.contains("3800200102690"));
Salida
3
-1
true
indexOf devuelve el lugar o -1, y contains solo «sí» o «no». Ambos comparan con equals desde el primer elemento: O(n). Para una lista de productos por código de barras, Java no tiene un método listo; escribes el bucle o usas un stream (IntStream.range(...).filter(...).findFirst()), que también va en orden.

Una llamada parece una sola operación, pero por dentro hay hasta n comparaciones. La función incorporada es más rápida que tu bucle por un factor constante, no por un orden de magnitud.

Pruébalo

Encuentra el error

Un compañero escribió la búsqueda así. La harina se encuentra, y el arroz «no está»:

static int find(List<Product> shelf, String barcode) {
for (int i = 0; i < shelf.size(); i++) {
if (shelf.get(i).barcode().equals(barcode)) {
return i;
} else {
return -1;
}
}
return -1;
}
System.out.println(find(shelf, "3800200101112"));
System.out.println(find(shelf, "3800200102690"));
Salida
0
-1

¿En qué línea está el error?

Elige la línea con el error.

La idea

Cuándo conviene y cuándo no

La búsqueda lineal es la elección correcta cuando:

  • hay pocos productos: diez o cien comparaciones no se notan;
  • buscas una sola vez: cualquier forma más rápida necesita antes al menos n pasos de preparación;
  • los datos están sin ordenar o cambian todo el tiempo, y no hay nada que mantener.

No lo es cuando hay muchas búsquedas. 2000 escaneos al día entre 50 000 productos son hasta 100 000 000 de comparaciones. Entonces preparas los datos una vez:

  • una tabla código de barras → producto (HashMap): 50 000 pasos una vez, y después cada búsqueda es O(1) en promedio;
  • una lista ordenada y la búsqueda binaria: ordenar es O(n log n) una vez, y cada búsqueda posterior es O(log n), unas 16 comparaciones para 50 000.
Map<String, Product> byBarcode = new HashMap<>();
for (Product product : shelf) {
byBarcode.put(product.barcode(), product);
}
System.out.println(byBarcode.get("3800200102690").name());
System.out.println(byBarcode.get("3800200100000"));
Salida
Arroz
null

La tabla cuesta memoria: otros n lugares. Es el mismo intercambio de tiempo por memoria que el conjunto de la lección «Complejidad en espacio».

Ejercicio

Todas las cajas de un producto

Resuelve la tarea en tu editor.

Inventario. Un producto puede haber llegado en varias entregas y estar en varios lugares de la lista. Escribe findAll(shelf, barcode), que devuelva todos los lugares del código de barras, no solo el primero. El estante es como el de arriba, con más arroz y más sal al final (lugares 6 y 7).

Salida
Arroz: 3, 6
Sal: 4, 7

Más tareas

  • El último lugar. Devuelve el último lugar del código de barras. ¿Por dónde conviene empezar?
  • Por nombre, sin distinguir mayúsculas. Encuentra el producto por nombre («arroz» y «Arroz» son lo mismo). ¿Cuántas comparaciones hay en el peor caso?
  • Productos bajo el mínimo. Lista todos los productos con menos de 10 unidades en existencias. ¿Puedes parar antes del final?

Test

Ponte a prueba

1. La lista tiene 10 000 productos sin ordenar. ¿Cuántas comparaciones hace la búsqueda lineal para un producto que no está?
2. El producto está, y cada lugar es igual de probable. ¿Cuántas comparaciones hacen falta en promedio con n productos?
3. ¿Por qué find no devuelve 0 cuando el producto no está?
4. El escáner hace 2000 búsquedas al día entre 50 000 productos. ¿Qué es mejor?
5. ¿Cuándo es la búsqueda lineal la elección correcta?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·