Complejidad de Algoritmos
Karim Guevara Puente de la Vega
[email protected] UCSM, 2014
Agenda
Introducción Tipos de Algoritmos Medidas de eficiencia Complejidad algoritmica Tiempo de ejecución Notaciones asintóticas Análisis de algoritmos no recursivos Análisis de algoritmos recursivos
Introducción
La solución de un problema haciendo uso de las computadoras requiere por una parte un algoritmo y por otra un programa en un LP. Ambos componentes tienen importancia; pero la del algoritmo es absolutamente indispensable
Muchas alternativas de solución
Cuando hay necesidad de elegir entre varios algoritmos, ¿cómo se debe elegir?...
Hay tres objetivos que suelen contradecirse: 1. Que el algoritmo sea fácil de entender, codificar y depurar. 2. Que el algoritmo se ejecute con la mayor rapidez posible. 3. Que el algoritmo utilice de forma óptima la memoria disponible.
Tipos de algoritmos
Algoritmos polinomiales
Aquellos que son proporcionales a nk. En general son factibles o aplicables: son solucionables
Algoritmos exponenciales
Aquellos que son proporcionales a kn En general, no son factibles salvo un tamaño de entrada n exageradamente pequeño.
Los recursos utilizados dependen de:
Factores externos:
El computador donde lo ejecutamos
El lenguaje de programación y el compilador que usamos
Tamaño de los datos de entrada
La implementación que haga el programador del algoritmo (estructuras de datos utilizadas)
E.j.: calcular la media de una matriz de NxM
Contenido de los datos de entrada:
Mejor caso: el contenido favorece una rápida ejecución
Peor caso: la ejecución más lenta posible.
Caso promedio: media de todos los posibles contenidos.
¿Cómo saber si es el mejor algoritmo?
A posteriori (empírico) : Implementación del algoritmo en un computador Se comprueba para distintos tamaños de los datos del problema y se compara Se pierde tiempo en caso el algoritmo sea malo.
A priori (teórico):
Se determina matemáticamente la cantidad de recursos utilizados, en función del tamaño de los datos del problema. Análisis independiente del computador Un algoritmo será mas eficiente siempre que consuma menos recursos: Tiempo Espacio de memoria
¿Complejidad algorítmica?
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
El análisis se basa en las Complejidades Temporales: para cada problema determinaremos una medida n (tamaño de la entrada).
Importancia de la eficiencia
Que utilidad tiene diseñar algoritmos eficientes si las computadoras procesan la información cada vez más rápido? “Contamos con una computadora capaz de procesar datos en 10-4 seg. En esta computadora se ejecuta un algoritmo que lee registros de una base de datos, dicho algoritmo tiene una complejidad exponencial 2n,
¿Cuánto tiempo se tardará en procesar una entrada n de datos? n
Tiempo
10
1 décima de segundo
20
2 minutos
30
> 1 día
40
> 3 años
50
3570 años
100
4.019,693,684,133,150 milenios
Importancia de la eficiencia
Ahora se tiene la misma computadora capaz de procesar datos en 10-4 seg. Pero se ejecuta un algoritmo que hace el mismo trabajo antes citado, pero este algoritmo tiene una complejidad cúbica n3,
¿Cuánto tiempo se tardará en procesar una entrada n de datos? n
Tiempo
10
1 décima de segundo
20
8 décima de segundo
100
1.7 minutos
200
13.3 minutos
1000
1 día
Tiempo de ejecución
Se mide en función de n: T(n) tiempo de ejecución Esta función se puede calcular directamente sobre el código: Instrucciones1; Para x 0 hasta n hacer Instrucciones2;
t1 t2 * n
Demanda: T(n) = t1 + t2 * n Generalmente los algoritmos contienen sentencias condicionales o selectivas, por lo que hay más de un valor para T(n): "el peor caso“, "el mejor caso" y "el caso promedio“.
Notaciones Asintóticas
El tiempo de ejecución T(n) está dado en base a unas constantes que dependen de factores externos. Nos interesa un análisis que sea independiente de esos factores Notaciones asintóticas: indica como crece T, para valores suficientemente grandes (asintóticamente) sin considerar constantes.
O(T): orden de complejidad de T (T): orden inferior de T, u omega de T. (T): orden exacto de T
Orden de complejidad de f(n): O(f) Sea el siguiente algoritmo: PARA x 1 HASTA n HACER PARA y 1 HASTA n HACER PARA z 1 HASTA n HACER PARA w 1 HASTA 3 HACER Instrucciones; PARA y 1 HASTA n HACER PARA z 1 HASTA n HACER PARA w 1 HASTA 2 HACER Instrucciones;
g(n) = 3n3+2n2
Funciones de complejidad más frecuentes
Funciones de complejidad más frecuentes O(1)
Constante
No depende del tamaño del problema Algunos algoritmos de búsqueda en Tabla Hashing
O(log n)
Logarítmica
Búsqueda binaria
O(n)
Lineal
Búsqueda lineal o secuencial, búsqueda en 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
O(n k) k>3
Polinómica
O(k
n)
O(n!)
k>1
Exponencial Factorial
Eficiente
Tratable
Algunos algoritmos de grafos, muchos problemas de optimización, por lo general en fuerza bruta Intratable Algunos algoritmos de grafos , todas las permutaciones
Funciones de complejidad más frecuentes La complejidad no polinomial (NP) o exponencial es aquella que tiene un orden mayor que la polinomial. Ejemplo: La complejidad exponencial O (kn). Comparación entre diferentes complejidades:
n
lg n
n lg n
n2
n3
2n
3n
n!
1
0
0
1
1
2
3
1
2
1
2
4
8
4
9
2
4
2
8
16
64
16
81
24
8
3
24
64
512
256
6.561
40.320
16
4
64
256
4.096
65.536
43.046.721
20.922.789.888.000
32
5
160
1.024
32.768
4.294.967.296
¿?
¿?
64
6
384
4.096
262.144
*
¿?
¿?
128
7
896
16.384
2.097.152
**
¿?
¿?
Propiedades de la notación O()
Sean f(n) y g(n) un par de funciones:
Los factores constantes pueden ser ignorados: kf(n) es O(f(n)) para cualquier K E.j. 5n2 es O(n2) La razón de crecimiento de una suma está dada por el término cuya razón de crecimiento es mayor: Si f es O(g) y g es O(h) entonces f es O(h) Si f crece más rápido que g, y g crece más rápido que h, entonces f crece más rápido que h.
Propiedades de la notación O()
Sean f(n) y g(n) un par de funciones:
Potencias mayores de n crecen más rápido que potencias menores: Si Or<s, entonces nr es O(ns) y ns no es O(nr)
La razón de crecimiento de un polinomio está dado por el término mayor (ignorando constantes) si p(n) es un polinomio de grado d, entonces p(n) es O(nd) E.j. 5n3+2n2+3n+2, es de O(n3)
Propiedades de la notación O()
Sean f(n) y g(n) un par de funciones:
La razón de crecimiento de un producto está dado por la multiplicación de la razón de crecimiento Si f es O(n) y g es O(k), entonces fg es O(nk) Las funciones logaritmo crecen más lento que las potencias Si k > 0, entonces In n es O(nk) Todas las funciones logaritmo crecen a la misma razón. Si b > 1 y c > 1. entonces logb n es O(logc n)
Reglas de notación asintótica
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:
Regla de la suma T1(n) = O(f1(n)) y T2(n) = O(f2(n)) Se puede decir que: T1(n) + T2(n) = O(max(f1(n),f2(n)))
Regla del producto T1(n) = O(f1(n)) y T2(n) = O(f2(n))
Se puede decir que: T1(n) T2(n) = O(f1(n) f2(n))
Por tanto….
Se puede concluir, que solo un algoritmo eficiente, con un orden de complejidad bajo puede tratar grandes volumenes de datos. Por tanto, un algoritmo es:
Muy eficiente si su complejidad es de orden log n
Eficiente si su complejidad es de orden nk
Ineficiente si su complejidad es de orden 2n
Algoritmos iterativos
Instrucciones secuenciales
Asignaciones y expresiones simples
Tiempo de ejecución constante O(1).
Secuencia de instrucciones
Tiempo de ejecución = suma de sus tiempos de 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
SI-ENTONCES: es el tiempo necesario para evaluar la condición, más el requerido para el conjunto de instrucciones. T(SI-ENTONCES) = T(condición) + T(rama ENTONCES) Aplicando la regla de la suma: O(T(SI-ENTONCES)) = max(O( T(condición),T(rama ENTONCES ))
Instrucciones condicionales
SI-ENTONCES-SINO: tiempo para evaluar la condición, más el máximo valor del conjunto de instrucciones de las ramas ENTONCES y SINO. (SI-ENTONCES-SINO) = T(condición) + max(T(rama ENTONCES), T(rama SINO))
Aplicando la regla de la suma: O(T(SI-ENTONCES-SINO)) = O( T(condición)) + max(O(T(rama ENTONCES)), O(T(rama SINO)))
Instrucciones de iteración
PARA: es el producto del número de iteraciones por la complejidad de las instrucciones del cuerpo del mismo bucle.
MIENTRAS-HACER y HACER-MIENTRAS: igual que PARA, pero se considera 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.
Ejercicios PROCEDIMIENTO MatrizProducto (E entero :n; E entero . A[1..n,1..n], B[1..n,1..n]; E/S entero : C[1..n,1..n]) VARIABLES entero: i,j,k INICIO I5 PARA i1 HASTA n I4 PARA i1 HASTA n I3 C[i,j] 0; I2 PARA k1 HASTA n I1 C[i,j] C[i,j] +A[i,k]* B[k,j]; FIN-PARA FIN-PARA FIN-PARA FIN PROCEDIMIENTO
Llamadas a procedimientos
Tiempo requerido para ejecutar el cuerpo del procedimiento llamado.
Ejemplo: PROCEDIMIENTO PRINCIPAL (E entero: A[1..n,1..n], B[1..n,1..n]; E/S entero: C[1..n,1..n]) VARIABLES entero: n, j, i, x INICIO LEER(n); i 1; MIENTRAS i<=n HACER PARA ji HASTA n A[i,j] j * 2; FIN-PARA ii+1 FIN-MIENTRAS O(n3) MatrizProducto( n,A,B,C); FIN-PROCEDIMIENTO
Algoritmos recursivos
Costo del algoritmo? PROCEDIMIENTO Factorial (E entero: n; E/S entero: f) VARIABLES entero: i INICIO f1 PARA i 1 HASTA n ff*i FIN_PARA FIN_PROCEDIMIENTO entero : FUNCION Factorial (E entero: n) INICIO SI n=0 ENTONCES RETORNA R 1 SINO RETORNAR (n * Factorial (n-1)) FIN_SI FIN_FUNCION
Función de recurrencia
Una inspección al algoritmo puede resultar en un función de recurrencia: Imita el flujo de control dentro del algoritmo. 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
Por ejemplo: entero : FUNCION Factorial (E entero: n) INICIO SI n=0 ENTONCES RETORNA R 1 SINO RETORNAR (n * Factorial (n-1)) FIN_SI FIN_FUNCION Factorial(n) =
T(n) =
1 Factorial (n-1) * n
1 T(n-1) +1
si n=0 en otro caso
si n=0 en otro caso
Función de Recurrencia
Función de recurrencia T(n) =
T(n)
1 T(n-1) +1
si n=0 en otro caso
= (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)
Árbol de recursión
Los árboles de recursión son una herramienta visual para analizar el costo de procedimientos recursivos asociados a una estructura de árbol. Método del árbol de recursión
Se construye el árbol para organizar por niveles las operaciones algebraicas necesarias para resolver la recurrencia. Cada nodo del árbol tiene una estructura de dos componentes: la función de costos y el costo no-recursivo
Árbol de recursión
Casos progresivos
Caso base
Utilización del árbol de recursión- Ejemplo 1
P.e.
Primero se construye el árbol de recurrencia determinando para cada nodo la función de costos y el costo no recursivo. Para cada nivel se calcula el costo total:
1er nivel: T(n); n2 2do nivel: T(n/2); (n/2) 2 ...
costo: n2 costo: 2(n/2)2 =n2/2
Utilización del árbol de recursión- Ejemplo 1
P.e.
Utilización del árbol de recursión- Ejemplo 1
En cada nivel
el número de subproblemas aumenta en potencias de dos:
el tamaño de los problemas disminuye en potencias de dos.
la complejidad algorítmica total incrementa en potencias de dos (último nivel se tienen 2m nodos y cada nodo es de tamaño (1/2m)n.
Utilización del árbol de recursión- Ejemplo 1
Usando el patrón de complejidad y las condiciones de terminación, la recurrencia se expresa como una sumatoria
El valor de m se determina igualando las dos expresiones del caso base.
Utilización del árbol de recursión- Ejemplo 1
Para la solución de T(n) se usa la serie geométrica con r=1/2.
Por tanto, la recurrencia T(n) revela que el algoritmo tiene una velocidad de crecimiento….. cuadrática 1/(2logn 2 -1 = 2/n
Utilización del árbol de recursión- Ejemplo 2
P.e.
Utilización del árbol de recursión- Ejemplo 2
En cada nivel
el número de subproblemas aumenta en potencias de dos:
el tamaño más grande de problema disminuye por un factor de (2/3)i:
la complejidad algorítmica permanece constante:
Utilización del árbol de recursión- Ejemplo 2
Usando el patrón de complejidad y las condiciones de terminación, la recurrencia se expresa como una sumatoria
se obtiene una cota superior de las trayectorias restantes.
Utilización del árbol de recursión- Ejemplo 2
El valor de m se determina igualando las dos expresiones del caso base.
Utilización del árbol de recursión- Ejemplo 2
Para la solución de T(n):