Complejidad Temporal y Espacial

Módulo inicial del curso: introduce el análisis de recursos, el conteo de operaciones elementales y la construcción de T(n).

1. Complejidad Temporal y Espacial

El análisis de algoritmos estudia cómo se comportan los algoritmos en términos de los recursos que consumen: principalmente tiempo (velocidad de ejecución) y espacio (memoria utilizada). El objetivo es poder comparar algoritmos de forma independiente a la máquina o al programador que los implemente.

1.1 Principio fundamental

Si dos programadores N y D implementan el mismo algoritmo de forma diferente, ambas implementaciones tendran el mismo orden de crecimiento. La diferencia entre ellas sera, a lo sumo, una constante multiplicativa:

Esto significa que dos implementaciones del mismo algoritmo solo difieren por un factor constante. Por eso, cuando hablamos de eficiencia, desligamos el algoritmo de su implementacion.

Nota

Siempre hay que analizar el algoritmo, no el programa. El lenguaje de programacion y el estilo del programador afectan las constantes, pero no el orden de crecimiento.

1.2 Complejidad Temporal

La complejidad temporal mide cuántas operaciones ejecuta un algoritmo en función del tamaño de la entrada n. Se calcula contando, línea por línea, cuántas veces se ejecuta cada instrucción y a que costo.

¿Qué son las operaciones elementales (OE)?

Son operaciones cuyo costo se considera proporcionalmente estable para el análisis. El modelo RAM (Random Access Machine) define cuáles son:

Operaciones elementales segun el modelo RAM

Tipo de OEEjemplosCosto
Aritmeticas+, -, *, /1 OE
Asignacion / Accesox <- 5, A[i]1 OE
Comparaciones<, >, =, <=, >=, <>1 OE
Booleanasand, or, not1 OE
Llamado a subrutinallamar(funcion)1 OE (el cuerpo se analiza aparte)
Nota

Los end (endfor, endwhile, endif) no se cuentan cómo operaciones. El cuerpo de un ciclo siempre se ejecuta una vez menos que la cabecera.

Estructura de un algoritmo (pseudocódigo)

Un algoritmo en pseudocódigo tiene la siguiente estructura:

Estructura general
# Variables globales (si las hay)
tipo NombreAlgoritmo (E tipo param1, E/S tipo param2) {
  # Área de variables locales
  tipo var1, var2
  # Cuerpo del algoritmo
  instrucciones...
  return valor
  # Si retorna algo: es FUNCIÓN
  # Si no retorna: es PROCEDIMIENTO
}

1.3 Ejemplo 1 - Algoritmo con un ciclo for (AlgoritmoSinMente)

Determinar la función de eficiencia T(n) del siguiente algoritmo.

Pseudocódigo

AlgoritmoSM
int AlgoritmoSM (E int n) {
  int i, suma
  suma <- 0                    # 1 vez --> 1 * C1
  for i <- 1 to n do           # n+1 veces --> (n+1) * C2
    suma <- suma+i             # n veces --> n * C3
  endfor
  return suma                  # 1 vez --> 1 * C4
}

1 Seguimiento línea por línea

Seguimiento línea por línea del algoritmo con un ciclo for

Línea de códigoVeces que se ejecutaOE por ejecuciónCosto total
suma <- 0111 * C1
for i <- 1 to n don + 13(n+1) * C2
suma <- suma + in2n * C3
return suma111 * C4

¿Por qué el for tiene 3 OE? Porque en cada pasada ocurren tres cosas: se evalúa la comparación i <= n, al terminar el cuerpo se incrementa i <- i+1 y la primera vez se hace la asignación i <- 1. Por eso la cabecera cuesta 3 OE por pasada.

Regla clave

El cuerpo de un ciclo for se ejecuta siempre una vez menos que la cabecera: si la cabecera va n+1 veces, el cuerpo va n veces. La ultima pasada de la cabecera es la que falla la condicion y no ejecuta el cuerpo.

2 Análisis tabulando (verificación empírica)

Corremos el algoritmo mentalmente para valores pequeños de n y contamos cuántas OE ocurren. Esto nos da una idea de cómo crece T(n) antes de calcularla formalmente.

Verificación empírica para valores pequeños de n

nsuma<-0 (1 vez)for cabecera (n+1) veces * 3suma<-suma+i (n veces) * 2return (1 vez)T(n) total
112*3 = 61*2 = 2110
213*3 = 92*2 = 4115
314*3 = 123*2 = 6120
415*3 = 154*2 = 8125
516*3 = 185*2 = 10130
n1(n+1)*3n*215n + 5

Observa que cuando n aumenta en 1, T(n) siempre aumenta en 5. Eso confirma que el crecimiento es lineal: cada unidad adicional de n cuesta siempre lo mismo.

Clave

Si la diferencia entre filas consecutivas de T(n) es constante, la función es lineal. Si la diferencia crece linealmente, es cuadrática. Si crece exponencialmente, es exponencial.

3 Tecnica de sumatorias para obtener T(n)

Este algoritmo no requiere sumatorias complejas porque cada línea se ejecuta un número fijo de veces. Simplemente sumamos los costos de todas las líneas:

Suma de costos
Expansion
Agrupacion
Forma lineal

Donde a = C2 + C3 y b = C1 + C2 + C4 son constantes positivas. El resultado es una función lineal en n.

Ver sección 9 - Tabla de Sumatorias Comunes

Para ver propiedades de sumatorias simples y cómo resolvero, consulta la sección 9 de este documento.

Resultado

-> orden-> crecimiento lineal.

1.4 Ejemplo 2 - Ciclos anidados (AlgoritmoSM2)

Determinar la función de eficiencia de dos ciclos for anidados donde ambos van de 1 a n (límite fijo).

Pseudocódigo

AlgoritmoSM2 con límites fijos
AlgoritmoSM2 (E int A[n]) {
  x <- 1                       # 1 vez --> 1*1
  for i <- 1 to n do           # n+1 veces --> (n+1)*3
    for j <- 1 to n do         # n(n+1) veces --> n(n+1)*3
      x <- x + i               # n*n veces --> n^2 * 2
    endfor
  endfor
}

1 Seguimiento línea por línea

Costos por línea en ciclos anidados con límite fijo

Línea de códigoVeces que se ejecutaOE por ejecuciónCosto total
x <- 1111
for i <- 1 to n don+13(n+1) * 3
for j <- 1 to n don*(n+1)3n*(n+1) * 3
x <- x + in*n2

¿Por qué el for interno se ejecuta n*(n+1) veces? El for externo tiene n+1 pasadas de cabecera. Para cada una de esas pasadas, el for interno también tiene n+1 pasadas propias. Pero el cuerpo externo solo ocurre n veces, así que el for interno se lanza n veces, y cada lanzamiento tiene n+1 pasadas de cabecera. Total: n * (n+1).

2 Análisis tabulando (verificación empírica)

Contamos cuántas veces se ejecuta la línea más interna x <- x + i para cada n:

Verificación empírica para ciclos anidados con límite fijo

nIteraciones (for externo)Iteraciones (for interno por cada externo)Total x<-x+i (n * n)T(n) aprox.
11111+6+6+2 = 15
22241+9+18+8 = 36
33391+12+36+18 = 67
444161+15+60+32 = 108
555251+18+90+50 = 159
nnn

La diferencia entre filas crece: 21, 31, 41, 51... (aumenta en 10 cada vez). Diferencia de la diferencia = 10 = constante, función cuadrática confirmada.

3 Tecnica de sumatorias

Como ambos límites son fijos (no dependen del otro índice), no necesitamos sumatorias anidadas complejas. El cuerpo se ejecuta exactamenteveces:

Y armamos T(n) sumando todos los costos:

Suma de costos
Expansion
Forma cerrada
Ver sección 9 - Tabla de Sumatorias Comunes

La formulase explica en la sección 9, tabla de Sumatorias Dobles con límites fijos.

Resultado

-> orden-> crecimiento cuadrático.

1.5 Ejemplo 3 - For anidado con límite variable

Ahora el ciclo interno va de 1 a i (límite variable que depende del externo). Este es el caso más frecuente en el análisis de algoritmos de ordenamiento.

Pseudocódigo

AlgoritmoSM2 con límite variable
AlgoritmoSM2 (E int A[n]) {
  x <- 1                       # 1 vez
  for i <- 1 to n do           # n+1 veces
    for j <- 1 to i do         # LÍMITE VARIABLE: depende de i
      x <- x + i
    endfor
  endfor
}

1 Seguimiento detallado (prueba de escritorio con n=5)

Rastreamos manualmente que valores toma j para cada valor de i, y contamos ejecuciones:

Prueba de escritorio con n=5

ij recorre valoresEjecuciones cabecera for j (i+1 veces)Ejecuciones cuerpo x<-x+i (i veces)
1j = 1, 2 (sale en 2)21
2j = 1, 2, 3 (sale en 3)32
3j = 1, 2, 3, 4 (sale en 4)43
4j = 1, 2, 3, 4, 5 (sale en 5)54
5j = 1, 2, 3, 4, 5, 665
TOTAL (n=5)-2+3+4+5+6 = 201+2+3+4+5 = 15

Observacion: Cuando i=1, j recorre 1 y 2 (la cabecera se ejecuta 2 veces = i+1, el cuerpo 1 vez = i). La cabecera siempre se ejecuta una vez mas que el cuerpo.

2 Análisis tabulando — verificar el patrón cuadrático

Verificación del patrón cuadrático

nEjecuciones cabecera for j SUM(i+1)Ejecuciones cuerpo x<-x+i SUM(i)T(n) aprox.
121pequena
22+3=51+2=3mayor
32+3+4=91+2+3=6mayor
42+3+4+5=141+2+3+4=10mayor
52015mayor
nn(n+1)/2 + nn(n+1)/2

Las columnas del centro crecen como n(n+1)/2, una expresión cuadrática. Eso confirma que el algoritmo es cuadrático.

3 Resolucion formal con sumatorias

Sumatoria de la cabecera del for interno: el for j <- 1 to i tiene su cabecera ejecutandose i+1 veces (i pasadas que cumplen + 1 que falla).

Cabecera del for interno
Linealidad
Formulas conocidas
Forma cerrada

Sumatoria del cuerpo x <- x + i:

Como se resuelve SUM(i=1..n) de i - el truco de la suma doble

Este es el truco mas usado en el curso. Escribimos la suma dos veces, una normal y otra invertida, y las sumamos:

Suma normal
Suma invertida
Suma de ambas
n veces
Resultado
Ver sección 9 - Tabla de Sumatorias Comunes

Las formulasyestán en la sección 9. También la propiedad de linealidad.

Construccion de T(n): sumamos los costos de cada linea:

Suma de costos
Expansion
Agrupacion
Simplificacion
Forma general
Resultado

-> orden-> crecimiento cuadrático.

1.6 Ejemplo 4 - For con while interno (crecimiento logarítmico)

Este es el caso más rico: el ciclo interno es un while que duplica j, lo que produce un comportamiento logarítmico. Es el patrón que explica por qué algoritmos como búsqueda binaria o Merge Sort son tan eficientes.

Pseudocódigo

AlgoritmoXXX
AlgoritmoXXX (E int n) {
  x <- 1                       # 1 vez --> 1*1
  for i <- 1 to n do           # n+1 veces --> (n+1)*3
    j <- 1                     # n veces --> n*1
    while (j <= n) do          # n*(log2(n)+2) veces --> n*(log2(n)+2)*1
      j <- j*2                 # n*(log2(n)+1) veces --> n*(log2(n)+1)*2
    endwhile
  endfor
}

1 Seguimiento detallado — ¿cuántas veces se ejecuta el while?

La clave es entender que hace j <- j*2. Cada vez que pasa el while, j se duplica:

El while termina cuando j > n, es decir cuando 2^k > n. ¿Cuántas veces se duplica antes de superar n?

Igualamos
Despejamos
  1. +1 porque j empieza en 1 (= 2^0), no en 2 (= 2^1). La primera iteración no avanza hacia n, solo establece la posición inicial.
  2. +1 porque la cabecera del while se evalúa una vez más al final: es la evaluación que falla la condición y causa la salida.

Por tanto, la cabecera del while se ejecutaveces por cada iteracion de i, y el cuerpoveces.

Conteo de cabecera y cuerpo del while

nValores que toma j (para un i fijo)Veces cabecera while (log2(n)+2)Veces cuerpo j<-j*2 (log2(n)+1)
1j=1 -> sale21
2j=1, 2 -> sale en j=432
4j=1, 2, 4 -> sale en j=843
8j=1, 2, 4, 8 -> sale en j=1654
16j=1, 2, 4, 8, 16 -> sale en j=3265
32j=1, 2, 4, 8, 16, 32 -> sale en j=6476
nvalores
Funciones piso y techo

Comono siempre es entero (ej:), usamos la función piso:. El piso de 2.32 es 2. Esto garantiza que contamos un número entero de iteraciones. Para el análisis asintótico,yson del mismo orden.

2 Análisis tabulando — verificar el crecimiento n*log(n)

Como el while tieneiteraciones por cada uno de losvalores de i:

Verificación del crecimiento n log n

nlog2(n) (aprox.)While por cada iTotal while (n * pasos)T(n) aprox.
1021*2 = 2pequena
2132*3 = 6mediana
4244*4 = 16mayor
8358*5 = 40mayor
164616*6 = 96mayor
325732*7 = 224mayor
nlog nlog n + 2n*(log n+2)~ n*log(n)

Cada vez que n se duplica, el while agrega un paso mas y el for el doble de iteraciones. El resultado: el costo crece cómo, no cómo.

3 Resolucion formal con sumatorias

El patrón clave: para una j que empieza en 1 y se duplica, el número de iteraciones del while es. Como i recorrevalores, la línea del while se ejecuta en total:

Linea del while
Constante respecto a i
Resultado
Expansion

El cuerpo del while j <- j*2:

Construccion de T(n):

Suma de costos
Expansion
Agrupando términos
Y si j <- j*5 en lugar de j*2?

La unica diferencia seria la base del logaritmo: se convertiria en. Como todos los logaritmos son del mismo orden, el orden asintótico sigue siendo.

Ver sección 9 - Tabla de Sumatorias Comunes

La sumatoriaestá en la sección 9, tabla de Sumatorias Dobles con ciclos logarítmicos.

Resultado

-> orden-> crecimiento linealitmico.

1.7 Complejidad Espacial

La complejidad espacial mide cuanta memoria adicional requiere un algoritmo. Se cuentan las variables locales, parametros y estructuras de datos utilizadas.

Casos frecuentes de complejidad espacial

CasoDescripcionEjemplo
- ConstanteUsa un número fijo de variables, sin importar nVariables simples: i, suma, x
- LinealUsa un arreglo o estructura de tamaño proporcional a nArreglo A[n]
- CuadraticaUsa una matriz n x nMatriz A[n][n]
- LogaritmicaPila de recursion en divide y vencerasBusqueda binaria recursiva