Complejidad computacional: definición y métricas de tiempo y espacio

Descubre la complejidad computacional: conceptos clave, análisis de tiempo y espacio, y métricas para optimizar algoritmos. Guía práctica y fácil de entender.

Autor: Leandro Alegsa

La teoría de la complejidad computacional forma parte de la informática. Examina los algoritmos y trata de decir cuántos pasos o cuánta memoria necesita un ordenador para realizar un determinado algoritmo. Muy a menudo, los algoritmos que utilizan menos pasos usan más memoria (o al revés: si hay menos memoria disponible, se necesitan más pasos para hacerlo). Muchos algoritmos interesantes llevan un número de pasos que depende del tamaño del problema.

¿Qué mide la complejidad?

La complejidad de un algoritmo se expresa principalmente en dos dimensiones:

  • Complejidad temporal: cantidad de operaciones (o tiempo) que requiere el algoritmo en función del tamaño de la entrada.
  • Complejidad espacial: cantidad de memoria adicional necesaria además de la entrada para ejecutar el algoritmo.

Notación asintótica

Para comparar algoritmos se usan notaciones que describen el comportamiento cuando el tamaño de la entrada n crece mucho:

  • O grande (Big O): cota superior asintótica. Indica un límite superior del crecimiento (por ejemplo, O(n²)). Se usa frecuentemente para expresar el peor caso.
  • Ω (Omega): cota inferior asintótica. Indica que el algoritmo necesita al menos tanto tiempo.
  • Θ (Theta): cota ajustada: el crecimiento está acotado por arriba y por abajo por la misma función asintótica.
  • o y ω: notaciones más finas (estrictas) para comparar crecimientos sin igualdad asintótica.

Caso peor, medio y mejor

Al analizar tiempo se suelen distinguir:

  • Peor caso: tiempo máximo que puede tomar el algoritmo para cualquier entrada de tamaño n.
  • Caso promedio: tiempo esperado si las entradas siguen algún modelo de distribución.
  • Mejor caso: menor tiempo posible para entradas de tamaño n.

En práctica, el peor caso y el caso promedio son los más útiles para entender la robustez de un algoritmo.

Ejemplos comunes

  • O(1): tiempo constante (acceso a un elemento por índice).
  • O(log n): crecimiento logarítmico (búsqueda binaria en un arreglo ordenado).
  • O(n): crecimiento lineal (búsqueda lineal).
  • O(n log n): típicamente algoritmos eficientes de ordenación (mergesort, heapsort; quicksort promedio).
  • O(n²): algoritmos cuadráticos (ordenación por inserción, burbuja en su versión simple).
  • O(2^n), O(n!): complejidad exponencial o factorial, típica en problemas combinatorios y de fuerza bruta.

Clases de complejidad (breve)

Algunas clases importantes teóricas:

  • P: problemas resolubles en tiempo polinómico por una máquina determinista.
  • NP: problemas cuyas soluciones pueden verificarse en tiempo polinómico; incluye muchos problemas de decisión difíciles.
  • NP-completo: subclase de NP a la que, si se encuentra un algoritmo polinómico para cualquiera de ellos, todos los problemas de NP serían polinomiales (es el núcleo de la famosa pregunta P vs NP).

Intercambio tiempo-espacio y otros conceptos

En muchos casos existe un trade-off entre tiempo y memoria: usar más memoria para almacenar resultados intermedios (memoización) puede reducir mucho el tiempo. Otros conceptos útiles:

  • Complejidad amortizada: análisis del coste medio de una operación a lo largo de una secuencia (por ejemplo, inserciones en un vector dinámico).
  • Algoritmos aleatorizados: usan azar para mejorar el tiempo promedio o simplificar el diseño.
  • Algoritmos aproximados: devuelven soluciones cercanas a la óptima más rápido cuando encontrar la solución exacta es costoso.

Consideraciones prácticas

La notación asintótica ignora constantes y detalles de implementación: un algoritmo O(n) con una gran constante puede ser peor que un O(n log n) optimizado para tamaños prácticos. Además, factores como la localización de memoria, la caché, paralelismo y el hardware afectan el rendimiento real. Por eso conviene combinar:

  • análisis asintótico para entender escalabilidad;
  • perfiles y pruebas empíricas para comprender el comportamiento en datos reales;
  • optimización de algoritmos y estructuras de datos adecuadas para el problema.

Cómo medir la complejidad

Para estimar complejidad:

  • conteo de operaciones elementales (suma, comparación) como función de n;
  • medición empírica mediante pruebas con entradas crecientes y ajuste de curvas;
  • uso de análisis formal y reducción a problemas conocidos para clasificar su dificultad.

En resumen, la complejidad computacional proporciona las herramientas para categorizar algoritmos según su uso de tiempo y memoria, comparar alternativas y predecir su escalabilidad, lo que es esencial para diseñar soluciones eficientes en informática y al analizar los algoritmos en la práctica.

Diferentes clases de complejidad


Complejidad constante

La teoría de la complejidad también examina cómo cambia un problema si se hace para más elementos. Un problema de clase de complejidad constante es la única clase en la que esto no es cierto. Un problema de complejidad constante tarda el mismo número de pasos en completarse sin importar el tamaño de la entrada o el número de elementos sobre los que se calcula. La transmisión de un mensaje puede considerarse un problema de complejidad constante. No importa cuántas personas necesiten recibir un mensaje, todas pueden escuchar una sola emisión sin necesidad de emisiones adicionales.

Complejidad lineal

Cortar el césped puede considerarse un problema de complejidad lineal. Cortar un área que es el doble de grande que la original lleva el doble de tiempo.

Complejidad cuadrática

Supongamos que quieres saber cuáles de tus amigos se conocen entre sí. Tienes que preguntar a cada par de amigos si se conocen entre sí. Si tienes el doble de amigos que otra persona, tienes que hacer cuatro veces más preguntas para averiguar a quién conoce cada uno. Se dice que los problemas que tardan cuatro veces más cuando el tamaño del problema se duplica tienen una complejidad cuadrática.

Complejidad logarítmica

Esta es a menudo la complejidad de los problemas que implican buscar cosas, como encontrar una palabra en un diccionario. Si el diccionario es el doble de grande, contiene el doble de palabras que el original para comparar. Buscar algo sólo llevará un paso más. El algoritmo para hacer búsquedas es sencillo. La palabra en el centro del diccionario estará antes o después del término que hay que buscar, si las palabras no coinciden. Si está antes, el término debe estar en la segunda mitad del diccionario. Si está después de la palabra, tiene que estar en la primera mitad. De este modo, el espacio del problema se reduce a la mitad en cada paso, hasta que se encuentra la palabra o la definición. Esto se conoce generalmente como complejidad logarítmica

Complejidad exponencial

Hay problemas que crecen muy rápido. Uno de estos problemas se conoce como el problema del vendedor ambulante. Un vendedor tiene que hacer un recorrido por un determinado número de ciudades. Cada ciudad debe ser visitada sólo una vez, la distancia (o el coste) del viaje debe ser mínimo, y el vendedor debe terminar donde empezó. Este problema tiene una complejidad exponencial. Hay que tener en cuenta n posibilidades factoriales. Si se añade una ciudad (de n a n+1) se multiplicará el número de posibilidades por (n+1). La mayoría de los problemas interesantes tienen esta complejidad.



 



Buscar dentro de la enciclopedia
AlegsaOnline.com - 2020 / 2025 - License CC3