Upload
others
View
6
Download
0
Embed Size (px)
Citation preview
7: Complejidad 1
ELO320 Estructuras de Datos y Algoritmos
Complejidad
Tomás Arredondo Vidal
Este material está basado en:
❒ Robert Sedgewick, "Algorithms in C", (third edition), Addison-Wesley, 2001
❒ Thomas Cormen et al, “Algorithms”, (eighth edition), MIT Press, 1992.
❒ material del curso ELO320 del Prof. Leopoldo Silva
❒ material en el sitio http://es.wikipedia.org
7: Complejidad 2
7-Complejidad
7.1 Tiempo de ejecución: T(n)
7.2 Complejidad: O(n)
7.3 Complejidad: Θ(n)
7.4 Algoritmos
7: Complejidad 3
Tiempo de ejecución: T(n)
❒ Se desea tener una medida de la duración del tiempo de ejecución de un algoritmo en función del tamaño de la entrada n.
❒ A través de llamados al sistema operativo se puede conocer el valor del reloj del procesador.
❒ Invocando el reloj (e.g. usando clock( ) en Unix), antes y después de realizar el algoritmo se tendrá una medida de la duración del tiempo de ejecución: T(n).
❒ Esta medida es muy dependiente del hardware (memoria, reloj, procesador) y software (sistema operativo).
7: Complejidad 4
Tiempo de ejecución: T(n) (cont)
❒ Por la razón anterior como una medida del tiempo de ejecución, se considera contar las instrucciones del lenguaje de alto nivel que son necesarias realizar.
❒ El tamaño de la entrada (n) debe ser precisado con más detalle.
❒ En forma tradicional n se considera como el número de elementos o componentes básicos que son sometidos al proceso o algoritmo siendo medido.
❒ También se suele denominar a T(n) como una complejidad temporal.
7: Complejidad 5
Tiempo de ejecución: T(n) (cont)
❒ La función T(n) mide el tiempo que el algoritmo requiere para procesar los n elementos de entrada.
❒ El tiempo de ejecución de un algoritmo es la suma de los tiempos de cada línea ejecutada (i.e. statement)
❒ Una línea de código i que toma tiempo Ci (e.g. pasos o ciclos del reloj) para ejecutar y se ejecuta n veces va a contribuir nCi al tiempo de ejecución del programa.
❒ Se debe sumar el tiempo de todas las líneas del programa para obtener T(n).
❒ Afecta al tiempo de ejecución el orden en que se presentan los elementos de entrada:
❍ un caso típico o promedio (e.g. random), ❍ el mejor caso posible, ❍ el peor caso posible (típicamente el caso mas importante).
7: Complejidad 6
Tipos de funciones de T(n)
❒ Las funciones T(n) pueden ser de varios tipos:❍ Funciones constantes: T (n) = 5, T (n) = 10
❍ Funciones logarítmicas: T (n) = log(n), T (n) = nlog(n)
❍ Funciones polinomiales: T (n) = 2n2, T (n) = 8 n2 + 5n
❍ Funciones exponenciales: T (n) = 2n, T (n) = 25n
❍ Mezclas de las anteriores,
❒ Cualquier función de n en un caso general.
7: Complejidad 7
Ejemplo: Cálculo de T(n) (cont)
❒ Se calculan los tiempos asociadas a cada línea considerando como operaciones elementales a: ❍ comparaciones, ❍ asignaciones, ❍ sumas, ❍ restas, ❍ acceso a elementos.
❒ Cada operación elemental tiene asociado una unidad de tiempo (e.g. una constante o simplemente el valor 1).
❒ También se podría considerar esta unidad como cualquier constante (i.e. O(1)).
7: Complejidad 8
Ejemplo: Cálculo de T(n), (1era approx.)
❒ Algoritmo de ordenamiento (peor caso): (ti - 1)void Alg1(int a[], int n) {
int i, j, temp; line cost times
for (i=0; i<n-1; i++) //1 : C1 t1 = Σi
for (j=n-1; j>=i+1; j--) //2 : C2 t2 =Σi(Σj(...))if(a[j-1] > a[j]) { //3 : C3 t2 - 1
temp=a[j-1]; //4 : C4 t2 - 1
a[j-1]=a[j]; //5 : C5 t2 - 1
a[j]=temp; //6 : C6 t2 - 1
}
}
7: Complejidad 9
Ejemplo: Cálculo de T(n) (2nda approx.)❒ Sumando las operaciones ejecutadas por cada línea (peor caso) en su
inicialización, iteración y salida:
❍ C1 = ops1 = 1 + 4 en cada iteración + 2
❍ C2= ops2 = 2 + 4 en cada iteración + 2
❍ C3= ops3 = 4
❍ C4= ops4 = 3
❍ C5= ops5 = 4
❍ C6= ops6 = 2
❒ Las operaciones internas se realizan: (n-1) - (i+1) + 1 = (n-i-1) veces
❒ Considerando el for interno, T(n) tiene un costo:
❒ Considerando el for externo, T(n) tiene un costo:
)1(1742)944(2)(1
1
−−+=++++= ∑−
+=
innTn
ij
∑−
=
+−−+++=2
0
2)))1(174(4(1)(n
i
innT
7: Complejidad 10
Ejemplo: Cálculo de T(n) (2nda approx.)
❒ El for externo tiene un costo:
❒ Arreglando y considerando los términos que no dependen de i se suman (n-1) veces:
∑−
=
+−−+++=2
0
2)))1(174(4(1)(n
i
innT
2/)717(2
)1)(2(17)1)(17178(3
17)1)(17178(3
))1717178(3
))1(178(3)(
2
2
0
2
0
2
0
+−=
−−−−−++=
−−−++=
−−++=
−−++=
∑
∑
∑
−
=
−
=
−
=
nn
nnnn
inn
in
innT
n
i
n
i
n
i
7: Complejidad 11
7-Complejidad
7.1 Tiempo de ejecución: T(n)
7.2 Complejidad: O(n)
7.3 Complejidad: Θ(n)
7.4 Algoritmos
7: Complejidad 12
O(n): Acotamiento de funciones
❒ El acotamiento de funciones permite clasificar las funciones por su orden de magnitud.
❒ Interesa encontrar una cota superior del crecimiento de la función T(n) a medida que n →∞.
❒ Consideremos la siguiente definición preliminar de la función O mayúscula, con la intención de clasificar funciones de n.
❒ Ejemplo: se dice que T(n) es O(ni) , si existen constantes c0 y n0 tales que T(n) <= c0 ni para todos los n >= n0
7: Complejidad 13
Ejemplo: O(n)
❒ Sea T(n) = (n + 1)2. Para el menor i posible...
❒ Hay que encontrar c0 y n0 tales que: (n + 1)2 <= c0 ni.
7: Complejidad 14
Ejemplo: O(n) (cont)
❒ Dado que T(n) = (n +1)2. T (n) es O(ni) si para el menor i posible se encuentran c0 y n0 tales que:
T(n) <= c0ni
(n+1)2 <= c0ni.❒ Si suponemos que i = 3:
❍ la desigualdad anterior se cumple para c = 4 y n >= 1 ❍ la desigualdad anterior se cumple para c = 2 y n >= 1,4376
❒ Si suponemos que i = 2:❍ la desigualdad anterior se cumple para c = 4 y n >= 1
❒ Si suponemos que i = 1 la desigualdad no se cumple.❒ Lo cual prueba que:
T(n) = O(n2).
7: Complejidad 15
O(n): Complejidad (O-notation)
❒ Definición de O(n):
Se dice que T(n) es O( f(n) ) solo si existen c y n0 tales que: T(n) <= c f(n) con n >= n0
❒ Sin embargo es posible acotar mejor el orden o magnitud de una función.
❒ Esto considerando el primer ejemplo, en el que se podía decir que T(n) era O(n3) y también que era O(n2).
❍ Θ(n) acota T(n) superior e inferiormente
7: Complejidad 16
Actividad:
❒ Probar que: T (n) = 3n3 + 2n2 es O(n3)
7: Complejidad 17
O(n): Costo Unitario
❒ Aplicando la definición puede comprobarse que las funciones T(n) constantes tienen complejidad O(1).
❒ Ejemplo:Sea T(n) = 5. Se puede escribir: 5 <= c•1 con n >= n0Es simple encontrar: c = 6 y cualquier n0Es decir f(n) = 1. Con lo cual, se puede afirmar que :
T(n) = 5 es O(1) para todo n.
❒ Una de las mayores simplificaciones para cálculos de complejidad es considerar que las acciones primitivas del lenguaje son de costo unitario.
7: Complejidad 18
O(n): Teorema de acciones secuénciales
❒ Teorema de acciones secuenciales:
Se realiza la acción A, como la secuencia dos acciones A1
y A2 de complejidades temporales T1(n) y T2(n) respectivamente.
Si T1(n) es O(f(n)) y T2(n) es O(g(n)) entonces A es de complejidad O( max( f(n), g(n) ).
7: Complejidad 19
O(n): Teorema de productos
❒ Teorema de productos.
Si T1(n) es O(f(n)) y T2(n) es O(g(n)) entonces: T1(n)T2(n) es O( f(n) g(n) ).
❒ Ejemplos: O( 3 n2 ) = ?
O( 3 n2 ) = O (n2) ya que 3 es O(1), y n2 es O(n2).
Si c es una constante y n el tamaño de la entrada:
O(c) = c*O(1) = O(1)
O(cn) = c*O(n) = O(n)
La regla del producto también puede aplicarse en:
n*O( n ) = O (n2)
7: Complejidad 20
O(n): Teorema de alternativas
❒ Teorema de alternativa
if (...)
then A1
else
A2
❒ A1(n) es O(f(n)) y A2(n) es O(g(n))
Se toma la complejidad de la acción de mayor orden, entonces esta condición es O( max(f(n), g(n) )).
7: Complejidad 21
O(n): Teorema de iteraciones
❒ Teorema de iteración
for ( i = 0; i < n; i++)
{
a;
}
Por regla de sumas se tiene n veces la complejidad temporal de la acción a.
Si la acción del bloque a es O(1) entonces el for es de complejidad n *O(1) = O(n)
7: Complejidad 22
7-Complejidad
7.1 Tiempo de ejecución: T(n)
7.2 Complejidad: O(n)
7.3 Complejidad: Θ(n)
7.4 Algoritmos
7: Complejidad 23
Θ(n): Acotamiento superior e inferior
❒ Una mejor definición para acotar funciones, es la función Big Theta Θ(n) que define simultáneamente cotas superior e inferiores para T(n).
❒ Es la definición preferencial de complejidad.
❒ Definición de Θ(n):
Se dice que T(n) es Θ( f(n) ) , si existen c1, c2 y n0 tales que:
c1f(n) <= T(n) <= c2 f(n) con n >= n0
7: Complejidad 24
EjemploΘ(n):
❒ Para el ejemplo anterior, c1 = 3, y c2 = 5, con n0 =1.
7: Complejidad 25
Actividad Θ(n):
❒ Se tienen tres for anidados:
for (i = 1; i <= n-1; i++)
for( j = i+1; j <= n; j++)
for (k = 1; k <= j; k++)
{
a[i]=a[j+2] ;
}
❒ Calcular Θ(n).
7: Complejidad 26
7-Complejidad
7.1 Tiempo de ejecución: T(n)
7.2 Complejidad: O(n)
7.3 Complejidad: Θ(n)
7.4 Algoritmos
7: Complejidad 27
Evaluando la complejidad en función del tiempo:
❒ Dado que T(n) refleja el número de instrucciones de costo unitario que deben realizarse para n entradas
❒ Si T(1) es equivalente a 1 [μseg], se puede construir la siguiente tabla de tiempos de ejecución:
❒ El teorema de sumas nos da que: O(3n 2 + 7n) = O(n 2 ). ❒ La tabla muestra que las complejidades de los dos
algoritmos cuadráticos son comparables en el orden de magnitud.
7: Complejidad 28
Relaciones de recurrencia:
❒ En varios casos de mucho interés se conoce la complejidad temporal de un algoritmo mediante una relación de recurrencia.
❍ Por ejemplo: T(n) = T(n/2) + c con T(1) = c.
❒ Es decir el algoritmo aplicado a una entrada de tamaño n, puede descomponerse en un problema de la mitad del tamaño.
❒ El costo de la descomposición es una constante de valor c.
❒ Para resolver una relación de recurrencia es preciso conocer el costo para una entrada dada; en este caso para entrada unitaria T(1) el costo es la constante c.
7: Complejidad 29
Relaciones de recurrencia: T(n)=O(log2n)
❒ Esto ocurre en programas que en cada pasada descartan la mitad de los datos de entrada
❒ Si a partir del caso conocido se van evaluando casos más complejos, pueden obtenerse una expresión general: T(n) TiempoT(1) = c = c = 0 + c T(2) = T(1) + c = 2c = c + c T(4) = T(2) + c = 3c = 2c + c T(8) = T(4) + c = 4c = 3c + c T(16) =T(8) + c = 5c = 4c + c…
T(2i) = T(2i-1) + c = (i+1)c= ic + c = c(i+1)
7: Complejidad 30
Relaciones de recurrencia: T(n)=O(log2n)
❒ Se han evaluado en números que son potencias de dos, para obtener con una expresión para el término general:
Con 2i = n, sacando logaritmo de dos en ambos lados:
log2(2i) = log2(n), pero log2(2
i) = i log2(2) = i.
Resultando: i = log2(n).
Reemplazando en la expresion general T(n) = c(i+1): T(n) = c(log2(n) + 1)
7: Complejidad 31
Relaciones de recurrencia: T(n)=O(log2n)
❒ Las gráficas muestran que para T(n) existen c1 y c2 que la acotan por encima y por debajo, para n > 2,2.
❒ Finalmente, se tiene que la solución de la ecuación de recurrencia es:
T(n) = O( log2(n) )
7: Complejidad 32
Relaciones de recurrencia: T(n)=O(log2n)
❒ Las gráficas comparan el costo lineal versus el logarítmico.
7: Complejidad 33
Búsqueda secuencial
❒ La búsqueda secuencial compara una clave con cada una de las n componentes.
❒ La primera vez que encuentre un elemento del arreglo igual al valor buscado se detiene el proceso.
❒ No encuentra claves repetidas y no se requiere que el arreglo esté ordenado.
❒ Si lo recorre en su totalidad, cuidando de no exceder los rangos del arreglo, y no lo encuentra debe indicarlo con algún valor específico de retorno.
❒ Se retornar el índice de la componente que cumple el criterio de búsqueda, un retorno con valor -1 (ya que no es un índice válido), indicará que el valor no fue hallado.
7: Complejidad 34
Búsqueda secuencial (cont)
Indice BusquedaSecuencial(Tipo A[], Indice Inf, Indice Sup, Tipo Clave) {
Indice i;
for (i = Inf; i <= Sup; i++)
if (A[i] == Clave)
return(i);
return (-1) ; // no se encontró
}
❒ La iniciación del for es O(1). La condición del if es O(1), también el retorno es O(1).
❒ El test de la condición del for es O(1), también el incremento de i es O(1). El bloque se repite (Sup-Inf+1) veces en peor caso.
❒ T(n) = O(1) + (Sup-Inf + 1)*( O(1) + (O(1) + O(1)) + O(1) ) + O(1)
❒ Simplificando: T(n) = (Sup-Inf+1) O(1) + O(1) = O(Sup-Inf+1)
❍ La entrada n, es el número de componentes en las que se busca.
❒ Si n = Sup–Inf+1, se tiene finalmente: T(n) = O(n).
7: Complejidad 35
Búsqueda binaria
❒ Se requiere tener un arreglo ordenado en forma ascendente.
❒ El algoritmo está basado en ubicar, mediante la variable auxiliar M, la mitad del arreglo aproximadamente:
❍ Entonces o se lo encuentra justo en el medio; el costo de encontrar la mitad es de costo constante.
❍ o en la mitad con índices menores que M si el valor buscado es menor que el de la componente ubicada en la mitad;
❍ o en la mitad con índices mayores que M si el valor buscado es mayor.
❍ El ajustar los índices también es de costo constante, corresponde a los dos if then else anidados.
❒ Si se tienen n componentes, la complejidad puede describir según:
T(n) = T(n/2) + c
❒ La solución de esta ecuación de recurrencia es:
T(n) = O(log(n)).
7: Complejidad 36
Búsqueda binaria (cont)
int BusquedaBinaria(Tipo A[], Indice Inf, Indice Sup, Tipo Clave)
{
Indice M;
while (verdadero) {
M = (Inf + Sup)/2;
if (Clave < A[M])
Sup = M - 1;
else if (Clave > A[M])
Inf = M + 1;
else
return M;
if (Inf > Sup)
return (noencontrado) ;
}
}
7: Complejidad 37
Algoritmos de multiplicación
❒ Un algoritmo primitivo para efectuar multiplicaciones es mediante la suma repetitiva de uno de los operandos.
❒ Por ejemplo el producto de dos números de 4 bits:
0101*1100
------
0000
0000
0101
0101
-------
0111100
❒ Cual es la complejidad considerando la suma como O(n)?
❒ Este algoritmo requiere n sumas si los operandos son de n bits
❒ Lo cual implica un costo O(n 2) para la multiplicación, considerando que cada suma es de costo O(n).
❒ Razón por la cual se suelen diseñar unidades de multiplicación, en hardware, con mejores algoritmos.
7: Complejidad 38
Algoritmos de multiplicación (cont)
❒ En un ambiente de microcontroladores o microprocesadores que no tengan implementadas la operación multiplicación o división, pero que si tengan: sumas, restas y desplazamientos a la izquierda y derecha en un bit, se pueden considerar esas instrucciones de costo O(1).
❒ Consideremos, en este último contexto, dos algoritmos para multiplicar:
7: Complejidad 39
Algoritmos de multiplicación (cont)
/* retorna m*n */
unsigned int multiplique1(unsigned int m, unsigned int n)
{
unsigned int r=0;
while ( n>0 ) {
r+=m;
n--;
}
return(r);
}
❒ Cual es la complejidad?
❒ El bloque se repite n veces, y estáconstituido por un test de condición, una suma y un incremento.
❒ Todas operaciones que pueden traducirse a instrucciones de máquina, y se consideran de costo O(1).
❒ Entonces esta multiplicación es de costo O(n) o lineal.
7: Complejidad 40
Algoritmos de multiplicación (cont)
/* retorna m*n */
unsigned int multiplique2(unsigned int m,
unsigned int n)
{
unsigned int r=0;
while ( n>0 )
{
if(n&1)
r+=m;
m*=2;
n/=2;
}
return(r); }
❒ Cual es la complejidad?
❒ En cada ciclo del while se divide por dos el valor de n.
❒ Multiplicación por dos (o leftshift << 1)
❒ La división por dos equivale a un right shift (>> 1)
❒ El test del bit menos significativo se realiza con una operación and; acumular el producto parcial en la local r, se efectúa con una suma.
❒ Todas estas operaciones, en este ambiente se consideran de costo constante: con T(1) = O(1).
❒ El algoritmo puede describirse : T(n) = T(n/2) + O(1) = O(log2n)
7: Complejidad 41
Algoritmos de división
❒ Consideremos un algoritmo primitivo de división, basado en restas sucesivas.
/* Retorna cuociente q y resto r. n = q*d + r. */
unsigned int divide1( unsigned int n, unsigned int d, unsigned int *r)
{ unsigned int q=0;
while (n >= d) {
n -= d;
q++;
}
*r = n; //escribe el resto
return(q); }
❒ Cual es la complejidad?
❒ En su peor caso, con denominador unitario, y n el máximo representable, se tiene costo: O(n).
❒ Entonces esta multiplicación es de costo O(n) o lineal.
7: Complejidad 42
Algoritmos de división (cont)// n/d -> q, r. Retorna cuociente q // y resto r. Con n = q*d + r . unsigned int divide2( unsigned int n,
unsigned int d, unsigned int *r) { unsigned int dd=d, q=0;
*r = n; while (dd <= n) dd*=2; while (dd > d) { dd/=2; q= q*2; if (dd <= *r) {*r-=dd; q++;}
} return(q);
} ❒ Cual es la complejidad?
❒ El primer while duplica el den. hasta que sea mayor que el num.
❒ Para 8 bits, el mayor numerador será: 11111111bin o 255dec. Si d es 1, el while genera lo siguiente para dd: 2, 4, 8, 16, 32, 64 128, 256. Y se realiza 8 veces.
❒ Lo cual también se puede calcular mediante log2(256) = log2(28) = 8.
❒ En el peor caso con d=1 y con el máx valor para n, se tienen log2n repeticiones.
❒ El segundo while divide por 2 el tamaño de dd que despues del primer loop es = n, su costo es: O(log2n)
❒ O(log2n) + O(log2n) = O(log2n)
7: Complejidad 43
Relaciones de recurrencia: T(n)=O(nlog2n)
❒ Esto ocurre en programas que en cada pasada descartan la mitad de los datos de entrada pero con un costo proporcional a n en cada iteración. Aparece en métodos basados en particiones (dividir para conquistar).
❒ Si a partir del caso conocido se van evaluando casos más complejos, pueden obtenerse:
T(n) Tiempo
T(1) = c = c
T(2) = 2*T(1) + 2*c = 4c = 2c * 2
T(4) = 2*T(2) + 4*c = 12c = 4c * 3
T(8) = 2*T(4) + 8*c = 32c = 8c * 4
T(16) = 2*T(8) + 16*c = 80c = 16c * 5
…
T(2i) = 2*T(2i-1) + 2i c = (i+1)c= ic + c = 2i c*(i+1)
7: Complejidad 44
Relaciones de recurrencia: T(n)=O(nlog2n)
❒ Se han evaluado en números que son potencias de dos, para obtener con una expresión para el término general:
Con 2i = n, sacando logaritmo de dos en ambos lados:
log2(2i) = log2(n), pero log2(2
i) = i log2(2) = i.
Resultando: i = log2(n).
Reemplazando en la expresión T(n) = 2i c*(i+1): T(n) = nc * (log2(n) + 1)
7: Complejidad 45
Relaciones de recurrencia: T(n)=O(nlog2n)
❒ Las gráficas muestran que para T(n) existen c1 y c2 que la acotan por encima y por debajo, para n >= 2.
❒ Finalmente, se tiene que la solución de la ecuación de recurrencia es:
T(n) = O( nlog2(n) )
7: Complejidad 46
Comparación entre algunas complejidades