Está en la página 1de 32

ANÁLISIS DE COMPLEJIDAD DE ALGORITMOS

Karim Guevara Puente de la Vega


Agenda

q Evaluando el rendimiento
q Conteo de sentencias
q Notación O-Grande
q Caso peor, mejor, medio
q Tipos de algoritmos
q Reglas de notación asintótica
q Complejidad de algoritmos iterativos
q Complejidad de algoritmos recursivos
Introducción

q Cuando hay necesidad de elegir entre varios algoritmos, ¿cómo evaluar


y/o comparar las alternativas?...

§ Con frecuencia interesa el buen rendimiento


ü Tiempo esperado y el uso de la memoria
ü Considerar que sea fácil de implementar, verificar, mantener el código.
ü Asegurar que la complejidad valga la pena.
Evaluando el rendimiento

q A posteriori (empírico)
+ Se prueba con datos reales
- Implementación del algoritmo en un computador, se corrige el código
- Sujeto a variables (hw, so, etc.)
q A priori (teórico)
+ Análisis sin implementar el código
+ Conjunto abstracto, independiente de cualquier entorno
- Los resultados empíricos pueden no coincidir exactamente (especialmente para
las entradas de tamaño pequeño) con un entorno específico
¿Complejidad algorítmica?

q La eficiencia de un algoritmo puede ser cuantificada con las siguientes


medidas de complejidad:
§ Complejidad Temporal o Tiempo de ejecución: Tiempo de cómputo necesario
para ejecutar algún programa.
§ Complejidad Espacial: Memoria que utiliza un programa para su ejecución

q El análisis se basa en las Complejidades Temporales, para cada


problema determinaremos la complejida temporal en función de una
medida n (tamaño de la entrada).
Conteo de sentencias

public double CtoF (double cTemp) {


return cTemp * 9.0 / 5.0 + 32;
}

q Cada sentencia tiene un costo de 1c


§ Multiplicación, división, suma, retorno
§ Total = 4c
q Valor de la entrada es importante?
Más de conteo de sentencias

public double Average (int v[]) {


int sum = 0;
for (int i = 0; i < v.length; i++)
sum += v[i];
return sum/v.length;
}

q Contando las sentencias


§ Fuera bucle: 4 sentencias (inic. sum, inic. i, división, retorno)
§ Cuerpo bucle: 3 sentencias (evaluación, agregación, incr. i) por elemento
§ Total = 3N + 4
q ¿La entrada importa?
§ Si duplica el tamaño del vector ¿cómo cambia el tiempo requerido?
Más de conteo de sentencias
public double GetExtremes (int v[], int r[]) {
r[0] = r[1] = v[0];
for (int i = 1; i < v.length; i++) {
if (v[i] > r[0])
r[0] = v[i];
}
for (int i = 1; i < v.length; i++) {
if (v[i] < r[1])
r[1] = v[i];
}
}

q Bucle: evaluar i, compara, actualiza, incr. i: 4 por iteración * N iteraciones


q 2 bucles
q Fuera bucle: inic. i, inic. r
q 4 + 8N
Comparando algoritmos

q Conteo de sentencia
CtoF(4) Average(3N+4) GetExtremes(8N+4)

§ GetExtremes tendrá siempre más tiempo?

q Considerar el crecimiento del patrón


§ Si Average toma 2 ms. para 1000 elementos,
ü Cuánto estimamos para 2000 o 10000?
§ Qué de GetExtremes?
Notación O-Grande
q Resumir el conteo de sentencias
§ Solo usa los términos más grandes, ignora otros, elimina todos los coeficientes.
§ Tiempo = 3n + 5 à O(n)
§ Tiempo = 10n - 2 à O(n)
§ Tiempo = 1/2n2 - n à O(n2)
§ Tiempo = 2n + n3 à O(2n)
q Describe la curva de crecimiento del algoritmo en el limite superior.
§ Intuición: evitar detalles cuando no importan, debido a que el tamaño de la entrada (N) es lo suficientemente
grande
q Formalmente:
§ O(f(n)) es un límite superior sobre el tiempo requerido
ü Tiempo de ejecución – T(n) <= Cf(n) para alguna constante C y el valor de n suficientemente
grande.
Utilizando O-Grande se predice el tiempo

q Para un algoritmo O(n):


§ 5,000 elementos toma 3.2 segundos
§ 10,000 elementos toma 6.4 segundos
§ 20,000 elementos tomará ….?

q Para un algoritmo O(n2):


§ 5,000 elementos toma 2.4 segundos
§ 10,000 elementos toma 9.6 segundos
§ 20,000 elementos tomará …?.
Caso peor – mejor - medio
public boolean Search (String names[], String key) {
for (int i=0; i<names.length; i++)
if (names[i].equals(key) )
return true;
return false;
}

q Si la clave está al inicio? al medio? al final? Si no está?


q Mejor caso
§ Muy rápido en algunas situaciones, a menudo no es tan relevante
§ W (T): orden inferior de T, u omega de T.
q Peor caso
§ Límite superior de lo malo que puede suceder
§ O(T): orden de complejidad de T
q Caso medio
§ Media de todas las posibles entradas, puede ser más difícil de calcular con precisión
§ Q(T): orden exacto de T
Computador que procesa: 106 instrucciones / seg
Patrones de crecimiento
Funciones de complejidad más frecuentes
No depende del tamaño del problema
O(1) Constante Algunos algoritmos de búsqueda en Tabla
Hashing
O(log n) Logarítmica Búsqueda binaria Eficiente
Búsqueda lineal o secuencial, búsqueda en
O(n) Lineal
texto
O(n•log n) Casi lineal QuickSort

O(n 2) Cuadrática Algoritmo de la burbuja, QuickSort (peor caso)

O(n 3) Cúbica Producto de matrices Tratable

O(n k) k>3 Polinómica

Algunos algoritmos de grafos, muchos


O(k n) k>1 Exponencial problemas de optimización, por lo general en
fuerza bruta Intratable
Algunos algoritmos de grafos , todas las
O(n!) Factorial
permutaciones
Tipos de algoritmos

q Algoritmos polinomiales
§ Aquellos que son proporcionales a nk
§ Factibles o aplicables: son solucionables

q Algoritmos exponenciales
§ Aquellos que son proporcionales a k n

§ No son factibles salvo un tamaño de entrada n exageradamente pequeño.


Reglas de notación asintótica

q Sean T1(n) y T2(n) dos funciones que expresan los tiempos de ejecución
de dos fragmentos de un programa, y se acotan de forma que se tiene:
T1(n) = O(f1(n)) y T2(n) = O(f2(n))

§ Regla de la suma
T1(n) + T2(n) = O(max(f1(n),f2(n)))

§ Regla del producto


T1(n) T2(n) = O(f1(n) f2(n))
COMPLEJIDAD DE ALGORITMOS ITERATIVOS
Instrucciones secuenciales
q Asignaciones y expresiones simples
§ Tiempo de ejecución constante: O(1)

q Secuencia de instrucciones
T. ejecución = S t. ejecución individuales
§ P.e.:
Sean S1 y S2, una secuencia de dos instrucciones:
T(S1 ; S2) = T(S1) + T(S2)

Aplicando la regla de la suma:


O(T(S1 ; S2)) = max(O( T(S1), T(S2) ))
Instrucciones condicionales

q if-then: es el tiempo necesario para evaluar la condición, más el


requerido para el conjunto de instrucciones.

T(if-then) = T(condición) + T(then)

Aplicando la regla de la suma:


O(T(if-then)) = max(O( T(condición),T(then ))
Instrucciones condicionales

q if-then-else: tiempo para evaluar la condición, más el máximo valor del


conjunto de instrucciones de las ramas then y else.

T(if-then-else) = T(condición) + max(T(then), T(else))

Aplicando la regla de la suma:


O(T(if-then-else)) = O( T(condición)) + max(O(T(then )), O(T(else)))
Instrucciones de iteración

q for, while, do while:

§ Producto del número de iteraciones por la complejidad de las instrucciones del


cuerpo del mismo bucle.

§ Considerar la evaluación del número de iteraciones para el peor caso posible.

§ Si existen ciclos anidados, realizar el análisis de adentro hacia fuera.


Ejercicio

public int[][] matProd (int [][] a, int [][]b)


int i, j, k;
int [][]c = new int[a.lenght][b.lenght] ;
for (i = 0; i < a.length; i++) {
for (j = 0; j < b.lenth; j++) {
c[i][j] = 0;
for (k = 0; k < c.length; k++) {
c[i][j] += a [i][k] * b[k][j]; R.P. à 4N
} R.S. à max(4N,1)
} à O(N)
}
}
Ejercicio

public int[][] matProd (int [][] a, int [][]b)


int i, j, k;
int [][]c = new int[a.lenght][b.lenght] ;
for (i = 0; i < a.length; i++) {
for (j = 0; j < b.lenth; j++) {
R.S. à max(3,N) = N
c[i][j] = 0;
R.P. à N2
for (k = 0; k < c.length; k++) {
c[i][j] += a [i][k] * b[k][j]; R.S. à max(N2,1)
è O(N2)
}
}
}
}
Ejercicio
O(N3)
public int[][] matProd (int [][] a, int [][]b)
int i, j, k;
int [][]c = new int[a.lenght][b.lenght] ;
for (i = 0; i < a.length; i++) {
for (j = 0; j < b.lenth; j++) {
R.S. à max(2,N2) = N2
c[i][j] = 0;
R.P. à N2.. N = N3
for (k = 0; k < c.length; k++) {
c[i][j] += a [i][k] * b[k][j]; R.S. à max(N3,1)
è O(N3)
}
}
}
}
Llamadas a procedimientos / métodos

q Tiempo requerido para ejecutar el cuerpo del procedimiento / método


invocado.
q P.e. : public void principal (int [][]a) {
int n,j,i;
n = a.length; O(1)
int b[n][n];
int c[][];
R.S. à max(1,2, n2,n3)
i = 0; O(1) è O(n3)
while (i < n){
for (j = i ; j < n; j++) {
b[i][j] = j * 2; O(n2)
}
i++;
}
c = matProd(a, b); O(n3)
}
COMPLEJIDAD DE ALGORITMOS RECURSIVOS
Analizando algoritmos recursivos
int factorial (int n) {
if (n < 1 )
return 1;
else
return (n * factorial(n-1));
}

q Una inspección al algoritmo puede resultar en una función de recurrencia


(relación de recurrencia):
§ Imita el flujo de control dentro del algoritmo.
q Una vez obtenida esta función se puede aplicar alguna técnica:
§ Recurrencias homogéneas
§ Recurrencias no homogéneas
§ Cambio de variables, etc.
Función de recurrencia
int factorial (int n) {
if (n < 1 )
return 1;
else
return (n * factorial(n-1));
}

q T(n) es el tiempo utilizado para una entrada n


1 si n=0
T(n) =
T(n-1) +1 en otro caso
Resolviendo la recurrencia
1 si n=0
T(n) =
T(n-1) +1 en otro caso

q T(n) = (T(n-2) +1) +1 = T(n-2) +2


= (T(n-3) +1) +2 = T(n-3) +3
= (T(n-4) +1) +3 = T(n-4) +4
...
generalizando :
= T(n-k) +k
Si k=n : = T(n-n) +n
= 1+n = max(0(1),O(n))
= O(n)
Otro ejemplo
public void MoveTower (int n, char src, char dst, char tmp) {
if (n > 0) {
MoveTower (n-1, src, tmp, dst);
MoveDisk (n, src, dst);
MoveTower (n-1, tmp, dst, src);
}
}

q Configurar la recurrencia T(n):


1 si n=0
T(n) =
2T(n-1) +1 en otro caso
Resolviendo la recurrencia
1 si n=0
T(n) =
1 + 2T(n-1) en otro caso

q Repetir la substitución
T(n) = 1 + 2T(n-1)
= 1 + [ 2 + 2T(n-2) ]
= 1 + [ 2 + ( 4 + 8T(n-3))]
...
Generalizar el patrón :
= 2i-1 + 2i T(n-i)

Resolver para n-i = 0 (i=n)


= 2n-1 + 2n T(0)
= 2n+1 -1
= 2n+1 ==> O(2n)

También podría gustarte