metricas-algoritmos-mas-alla-big-o

Métricas en algoritmos

  • 5 min

Las métricas de un algoritmo son criterios para comparar soluciones más allá de cuánto tardan.

En el artículo anterior nos centramos en la notación Big O, que sirve para medir cómo crece el tiempo de ejecución cuando aumenta el tamaño de la entrada. Es una herramienta muy útil, pero no cuenta toda la historia.

Un algoritmo puede ser rápido y aun así ser una mala elección. Puede consumir demasiada memoria, perder precisión, ser difícil de mantener o romperse con datos un poco raros. Así que hoy vamos a mirar el cuadro de mandos completo.

Resumen

Cuando elegimos un algoritmo, no miramos solo el velocímetro. También hay que mirar consumo, estabilidad y mantenimiento.

MétricaPregunta claveCuándo importa mucho
Tiempo¿Cómo escala la velocidad?Grandes volúmenes de datos
Espacio¿Cuánta memoria extra usa?Sistemas con poca RAM o muchos datos
Estabilidad¿Respeta el orden previo?Ordenaciones multicriterio
Precisión¿El resultado es exacto o aproximado?Cálculo científico, finanzas, simulación
Robustez¿Qué pasa con entradas raras?Software en producción
Legibilidad¿Se puede mantener sin sufrir?Casi siempre

Complejidad espacial

Si la complejidad temporal mide “cuánto tarda”, la complejidad espacial mide cuánta memoria necesita.

A menudo existe un intercambio clásico: podemos hacer algo más rápido si gastamos más memoria, o podemos ahorrar memoria si aceptamos repetir cálculos.

Por ejemplo, una caché guarda resultados previos para no recalcularlos. Eso puede acelerar muchísimo un programa, pero a cambio consume RAM.

Conviene distinguir dos cosas:

  • Espacio de entrada: memoria ocupada por los datos que ya recibimos.
  • Espacio auxiliar: memoria extra que necesita el algoritmo para trabajar.

Normalmente, cuando analizamos memoria, nos interesa especialmente el espacio auxiliar.

Supongamos que queremos invertir un array:

[1, 2, 3, 4] -> [4, 3, 2, 1]
Copied!

Una opción es crear un array nuevo y copiar los elementos en orden inverso. Es sencilla, pero usa memoria adicional O(n).

Otra opción es intercambiar el primer elemento con el último, el segundo con el penúltimo, y así sucesivamente. Eso modifica el array original y usa memoria O(1). A esto se le llama trabajar in-place.

En sistemas embebidos, aplicaciones móviles o procesos con muchísimos datos, quedarse sin memoria suele ser peor que tardar un poco más.

Estabilidad

La estabilidad es importante sobre todo en algoritmos de ordenación.

Un algoritmo de ordenación es estable si, cuando dos elementos tienen la misma clave, mantiene el orden relativo que tenían antes.

Parece un detalle menor, hasta que haces ordenaciones encadenadas. Por ejemplo, una lista de empleados ya ordenada por nombre:

NombreDepartamento
AnaVentas
CarlosMarketing
DanielVentas

Si ahora ordenamos por departamento con un algoritmo estable, Ana seguirá antes que Daniel dentro de Ventas. Si el algoritmo no es estable, ese orden puede perderse.

Algoritmos como Merge Sort son estables. QuickSort, en sus implementaciones clásicas, normalmente no lo es. En la práctica, las librerías modernas pueden usar variantes híbridas y optimizadas.

Exactitud y precisión numérica

Otro criterio es si el algoritmo devuelve la solución exacta o una aproximación.

Hay problemas donde buscar la solución perfecta es demasiado caro. En esos casos se usan heurísticas o aproximaciones que dan una respuesta suficientemente buena en un tiempo razonable.

Esto es habitual en problemas de rutas, optimización, inteligencia artificial o planificación. No siempre queremos “la respuesta perfecta”. Muchas veces queremos una respuesta buena antes de que se enfríe el café.

Estabilidad numérica

En cálculo numérico también importa cómo se comportan los errores de redondeo.

Los ordenadores representan muchos números reales con coma flotante (float, double). Eso introduce pequeños errores:

console.log(0.1 + 0.2); // 0.30000000000000004
Copied!

Un algoritmo es numéricamente inestable si esos errores pequeños se amplifican paso a paso hasta estropear el resultado final.

Robustez

La robustez mide cómo se comporta un algoritmo ante entradas incorrectas, inesperadas o maliciosas.

Un algoritmo frágil asume que todo será perfecto:

  • la lista nunca está vacía,
  • el usuario siempre escribe un número,
  • el archivo siempre existe,
  • nunca llega un null,
  • jamás se divide entre cero.

La realidad, por supuesto, tiene otras aficiones.

Un algoritmo robusto valida entradas, contempla casos límite y falla de forma controlada cuando no puede continuar.

Legibilidad y mantenibilidad

Esta métrica parece menos técnica, pero en proyectos reales pesa muchísimo.

A veces podemos hacer un algoritmo un 5% más rápido usando trucos difíciles de leer. La pregunta es si compensa. Si ese código lo vais a mantener durante años, quizá el coste humano sea mayor que el ahorro de CPU.

Cualquier persona puede escribir código que un ordenador entienda. El reto es escribir código que también entiendan las personas.

No significa que el rendimiento no importe. Significa que la claridad también es parte del diseño.