TEMA 2 LA EFICIENCIA DE LOS ALGORITMOS Departamento de Lenguajes y Sistemas Informáticos UNIVERSIDAD DE ALICANTE La eficiencia de los algoritmos OBJETIVOS Proporcionar la capacidad para analizar con rigor la eficiencia de los algoritmos Distinguir los conceptos de eficiencia en tiempo y en espacio Introducir las bases matemáticas para poder aplicar el criterio asintótico a los conceptos de eficiencia Calcular la complejidad temporal o espacial de un algoritmo recursivo o iterativo Comparar, respecto a eficiencia, distintas soluciones algorítmicas a un mismo problema 2 1 La eficiencia de los algoritmos CONTENIDO 1. Noción de complejidad 2. Cotas de complejidad 3. Análisis asintótico 4. Cálculo de complejidades Algoritmos Iterativos Algoritmos Recursivos. Ecuaciones de recurrencia 5. Anexo 3 1. Noción de complejidad ¿QUÉ ES UN ALGORITMO? Un algoritmo es una serie finita de pasos que expresa una forma o estrategia de resolución de un problema. Importante: El número de pasos debe ser finito. El algoritmo debe terminar en un tiempo finito. El algoritmo debe ser capaz de determinar la solución del problema. Se trata de un método sistemático, susceptible de ser realizado mecánicamente, para resolver un problema dado. 4 2 1. Noción de complejidad DEFINICIÓN Complejidad de un algoritmo Medida de los recursos que un algoritmo necesita para su ejecución Complejidad temporal: Tiempo que un algoritmo necesita para su ejecución Complejidad espacial: Recursos espaciales (de almacén) que un algoritmo consume o necesita para su ejecución Posibilidad de hacer Valoraciones: el algoritmo A es “bueno”, “el mejor”, “prohibitivo” Comparaciones: el algoritmo A es mejor que el B Nos centraremos en el estudio de la complejidad temporal 5 1. Noción de complejidad COMPLEJIDAD TEMPORAL El tiempo de ejecución de un algoritmo depende de: Factores externos Internos La máquina en la que se va a ejecutar El compilador La experiencia del programador Los datos de entrada suministrados en cada ejecución El número de instrucciones asociadas al algoritmo ¿Cómo estudiamos el tiempo de ejecución de un algoritmo? 6 3 1. Noción de complejidad ¿CÓMO ESTUDIAMOS EL TIEMPO DE EJECUCIÓN? Análisis empírico (a posteriori): Generando ejecuciones del algoritmo para distintos valores de entrada y cronometrando el tiempo de ejecución J Se obtiene una medida real del comportamiento del algoritmo en el entorno de aplicación L El resultado depende de los factores externos e internos Análisis analítico (a priori): Obtener una función que represente el tiempo de ejecución del algoritmo para cualquier valor de entrada J El resultado depende sólo de los factores internos J Estima el comportamiento del algoritmo de forma independiente de los factores externos J No es necesario implementar y ejecutar los algoritmos L No obtiene una medida real del comportamiento del algoritmo en el entorno de aplicación Ambas medidas son importantes 7 1. Noción de complejidad ¿CÓMO ESTUDIAMOS EL TIEMPO DE EJECUCIÓN? Se expresa mediante funciones de coste. Permiten determinar el comportamiento de un algoritmo para cualquier entrada posible. Las entradas se expresan mediante lo que denominamos talla, tamaño de la entrada o tamaño del problema. Talla o tamaño de un problema: Valor o conjunto de valores asociados a la entrada del problema que representa una medida de su tamaño respecto de otras entradas posibles Denotaremos T(n) al tiempo de ejecución de un algoritmo para una entrada de tamaño n. 8 4 1. Noción de complejidad ¿CÓMO ESTUDIAMOS EL TIEMPO DE EJECUCIÓN? ¿Qué unidad de medida empleamos para T(n)? ¿segundos? No existe unidad concreta No existe ordenador de referencia para estas medidas El principio de Invarianza Dado un algoritmo y dos implementaciones I1 e I2 (máquinas o códigos distintos) que tardan en ejecutarse T1(n) y T2(n) , entonces ∃c ∈ \ + , ∃n0 ∈ ` / ∀n ≥ n0 T1 ( n) ≤ c·T2 ( n) Es decir, el tiempo de ejecución de dos implementaciones distintas de un algoritmo dado va a diferir como mucho en una constante multiplicativa Por tanto podemos decir que un algoritmo tarda un tiempo del orden de T(n) si ∃c ∈ \ + y una implementación I del algoritmo que tarda menos que c ·T(n) para cualquier entrada de tamaño n 9 1. Noción de complejidad ¿CÓMO ESTUDIAMOS EL TIEMPO DE EJECUCIÓN? ¿Unidad en función del tipo de análisis? Análisis empírico (a posteriori): Emplearemos los segundos siempre que se particularice el estudio a la ejecución de una implementación concreta de un algoritmo en una máquina determinada Análisis analítico (a priori): Necesitamos definir una unidad de medida teórica, independiente de factores externos Se define una unidad de medida (operación elemental o paso de programa) El tiempo de un algoritmo se puede así medir como el número de pasos de programa que realiza para una entrada dada. 10 5 1. Noción de complejidad CONSIDERACIONES ¿Porqué es importante conocer el coste de ejecución de los algoritmos? ¿Porqué se emplean funciones de coste para expresar el coste de un algoritmo? ¿Cómo definimos un paso de programa u operación elemental? ¿Cómo lo identificamos en un algoritmo? 11 1. Noción de complejidad CONSIDERACIONES ¿Porqué es importante conocer el coste de ejecución de los algoritmos? Ejemplo: El cuadrado de un número concreto (1000) 1 Paso de programa = 1operación simple funcion Cuad1():entero; devuelve(1000*1000); fin funcion Cuad2():entero; m=0; para i=1 hasta 1000 m=m+1000; fpara devuelve(m); fin // 2 // /* 2 */ // 1 // // 1001+1001 // // 1000+1000 // Número total de operaciones // 1 // /* 4004 */ • Diferente coste según algoritmo • Todos coste constante 12 6 1. Noción de complejidad CONSIDERACIONES ¿Porqué se emplean funciones de coste para expresar el coste de un algoritmo? Necesitamos que la expresión del coste del algoritmo sea válida para cualquier entrada al mismo (se expresa en función de su talla) Ejemplo: El cuadrado de un número cualquiera (n) funcion Cuad3(n:ent):entero; devuelve(n*n); // 2 // Fin /* 2 */ f ( n) = 2 funcion Cuad4(n:ent):entero; m=0; // 1 // para i=1 hasta n // n+1+n+1 // m=m+n; // n+n // fpara devuelve(m); // 1 // fin /* 4n+4 */ f (n) = 4n + 4 • Diferente coste según algoritmo • Primero: coste constante (No depende de n) • Segundo: coste variable en función de n (talla del problema) 13 1. Noción de complejidad CONSIDERACIONES Talla o tamaño de un problema: Valor o conjunto de valores asociados a la entrada del problema que representa una medida de su tamaño respecto de otras entradas posibles Cuad3: f ( n ) = 2 - Cuad4: f (n) = 4n + 4 tiempo 70 60 50 40 30 20 10 0 f(n)=2 f(n)=4n+4 1 5 10 tamaño problema 15 14 7 1. Noción de complejidad CONSIDERACIONES ¿Cómo definimos un paso de programa u operación elemental? ¿Cómo lo identificamos en un algoritmo? Paso de programa: Secuencia de operaciones con contenido semántico cuyo coste es independiente de la talla del problema. Ejemplo: Opción A: 1 Paso = operación simple Opción B: 1 Paso = secuencia máxima de operaciones cuyo coste es independiente de la talla del problema func SUMAR(A:vector[1..n]:ent):ent; (1) s=0 (2) para i=1 hasta n hacer (3) s=s+A[i]; Imprime(A[i]); fpara (4) devuelve(s); fin Línea (1) (2) (3) Opcion-A 1 (n+1)(1+1) (n+1)(1+1+1) (4) Suma 1 5n+7 Opcion-B 1 n (1) 1 n+2 ¿Cuál es la diferencia? 15 1. Noción de complejidad CÁLCULO DEL NÚMERO DE PASOS DE PROGRAMA Una operación elemental corresponde a un paso de programa. La complejidad de una secuencia consecutiva de instrucciones se calcula sumando los pasos de cada una de las instrucciones Estructuras de selección. El tiempo de ejecución de la sentencia “SI C ENTONCES S1 SINO S2 FINSI” es: T = T (C ) + max{T ( S1), T ( S 2)} Estructuras repetitivas. El tiempo de ejecución de un bucle “MIENTRAS C HACER S FIN” es: T = T (C ) + ( n º iteraciones ) * (T ( S ) + T (C )) donde T(C) y T(S) pueden variar en cada iteración, y por tanto habrá que tenerlo en cuenta para su cálculo. El resto de sentencias iterativas se asemeja al bucle MIENTRAS 16 8 1. Noción de complejidad CÁLCULO DEL NÚMERO DE PASOS DE PROGRAMA Funciones: El tiempo de ejecución de una llamada a un procedimiento o función F(P1, P2,..., Pn) es 1 (por la llamada), más el tiempo de evaluación de los parámetros P1, P2,..., Pn, más el tiempo que tarde en ejecutarse F, esto es: T = 1 + T ( P1 ) + T ( P2 ) + ... + T ( Pn ) + T ( F ) El paso de parámetros por referencia, por tratarse simplemente de punteros, no contabiliza El tiempo de ejecución de las llamadas a procedimientos recursivos va a dar lugar a ecuaciones en recurrencia, que veremos posteriormente. 17 1. Noción de complejidad EJERCICIOS funcion ejemplo1 (n:entero):entero; n=n+1; devuelve(n); fin funcion ejemplo2 (n:entero):entero; i=0; mientras (i ≤ 2000) n=n+1; i=i+1; fmientras; devuelve(n); fin 18 9 1. Noción de complejidad EJERCICIOS funcion ejemplo3 (n:entero):entero; j = 2; para i = 1 hasta 2000 j=j*j; fpara; i = 0; mientras (i ≤ n) j = j + j; j = j - 2; i = i + 1; fmientras; devuelve(j); fin 19 1. Noción de complejidad EJERCICIOS funcion ejemplo4 (n:entero):entero; k = 1; para i=0 hasta n para j=1 hasta n k = k + k; finpara finpara devuelve(k); fin funcion ejemplo5 (n:entero):entero; k = 1; para i=0 hasta n para j=i hasta n k = k + k; finpara finpara devuelve(k); fin 20 10 1. Noción de complejidad EJERCICIOS Cálculo de la traspuesta de una matriz función TRASPUESTA (var A:matriz) var i,j,filas: entero fvar filas=numfilas (A); para i=1 hasta filas-1 hacer para j=i+1 hasta filas hacer b=A[j,i]; A[j,i]=A[i,j]; A[i,j]=b; fpara fpara fin 21 1. Noción de complejidad EJERCICIOS Producto de dos matrices función PRODUCTO (var A,B:matriz; n,m:entero):matriz var C:matriz; i,j,k,suma:entero fvar para i=1 hasta n hacer para j=1 hasta n hacer suma=0; para k=1 hasta m hacer suma= suma + Ai,k * Bk,j; fpara Ci,j=suma; fpara fpara devuelve C; fin 22 11 2. Cotas de complejidad INTRODUCCION Dado un vector X de n números naturales y dado un número natural z Encontrar el índice i tal que Xi = z Calcular el número de pasos que realiza función BUSCAR (var X:vector[N]; z:N):devuelve N; var i:natural fvar; i=1; mientras (i≤⏐X⏐) ∧ (Xi≠z) hacer i=i+1; fmientras si (i=⏐X⏐+1) entonces devuelve(0) (*No encontrado*) si_no devuelve(i) fin 23 2. Cotas de complejidad EL PROBLEMA No podemos contar el número de pasos porque para diferentes entradas de un mismo tamaño de problema se obtienen diferentes complejidades Ejemplo: X ( 1, 0, 2, 4 ) ( 1, 0, 2, 4 ) ( 1, 0, 2, 4 ) z 3 0 1 Nº PASOS 1+5+1=7 1+2+1=4 1+1+1=3 ¿Qué podemos hacer? Acotar el coste mediante dos funciones que expresen respectivamente, el máximo y el mínimo coste del algoritmo (cotas de complejidad) 24 12 2. Cotas de complejidad LA SOLUCIÓN: Cotas de complejidad Cuando aparecen diferentes casos para una misma talla genérica n, se introducen las cotas de complejidad Caso peor: cota superior del algoritmo → Cs(n) Caso mejor: cota inferior del algoritmo → Ci(n) Caso promedio: cota promedio → Cm(n) Todas son funciones del tamaño del problema La cota promedio es difícil de evaluar a priori Es necesario conocer la distribución de probabilidad de la entrada No es la media de la inferior y de la superior 25 2. Cotas de complejidad EJEMPLO: Cotas superior e inferior función BUSCAR (var X:vector[N]; z:N):devuelve N var i: natural fvar; (1) i=1; (2) mientras (i≤⏐X⏐) ∧ (Xi≠z) hacer (3) i=i+1; fmientras (4) si i= ⏐X⏐+1 entonces devuelve 0 si_no devuelve i fin Tamaño del problema (n)= Número de elementos del vector X (⏐X⏐) Línea (1) (2,3) (4) Suma Mejor Caso 1 1 1 3 Peor Caso 1 n 1 n+2 Cs(n)=n+2 Ci (n)=3 26 13 2. Cotas de complejidad EJEMPLO: Cotas superior e inferior Complejidad función Buscar Cota Superior 18 16 14 Ci(n)=3 Cs(n)=n+2 ¿Cota promedio? 12 10 8 6 4 2 0 Cota Inferior 1 5 10 15 27 3. Análisis asintótico INTRODUCCION El estudio de la complejidad resulta realmente interesante para tamaños grandes de problema por varios motivos: Las diferencias “reales” en tiempo de ejecución de algoritmos con diferente coste para tamaños pequeños de problema no suelen ser muy significativas Es lógico invertir tiempo en el desarrollo de un buen algoritmo sólo si se prevé que éste realizará un gran volumen de operaciones Al estudio de la complejidad para tamaños grandes de problema se le denomina análisis asintótico Permite clasificar las funciones de complejidad de forma que podamos compararlas entre si fácilmente Para ello, se definen clases de equivalencia que engloban a las funciones que “crecen de la misma forma” (ver principio de invarianza). Se emplea la notación asintótica 28 14 3. Análisis asintótico NOTACION ASINTÓTICA Notación matemática utilizada para representar la complejidad cuando el tamaño de problema (n) es muy grande (n → ∞) Se definen tres tipos de notación Notación O (big-omicron) ⇒ Cota superior Notación Ω (omega) ⇒ Cota inferior Notación Θ (big-theta) ⇒ Cota promedio 29 3. Análisis asintótico COTA SUPERIOR. NOTACION O ≥0 Sea f : ` → \ se define el conjunto O(f) como el conjunto de funciones acotadas superiormente por un múltiplo de f : Ο( f ) = {g : ` → \ ≥0 | ∃c > 0, ∃n0 ∈ ` / ∀n ≥ n0 g( n ) ≤ c· f ( n )} Dada una función t : ` → \ ≥0 se dice que múltiplo de f que es cota superior de t t ∈ Ο( f ) si existe un 30 15 3. Análisis asintótico COTA SUPERIOR. NOTACION O ≥0 Sea f : ` → \ se define el conjunto O(f) como el conjunto de funciones acotadas superiormente por un múltiplo de f : Ο( f ) = {g : ` → \ ≥0 | ∃c > 0, ∃n0 ∈ ` / ∀n ≥ n0 g( n ) ≤ c· f ( n )} Dada una función t : ` → \ ≥0 se dice que múltiplo de f que es cota superior de t t ∈ Ο( f ) si existe un c· f (n) t (n) Ejemplos: 3n + 1 ¿pertenece a O(n) ? 3n2 + 1 ¿pertenece a O(n) ? 3n2 + 1 ¿pertenece a O(n2)? 31 3. Análisis asintótico NOTACION O : PROPIEDADES Veamos algunas propiedades de esta notación: 1. Dada una función f, entonces f ∈ Ο( f ) 2. 3. f ∈ Ο( g ) ⇒ Ο( f ) ⊂ Ο( g ) Ο( f ) = Ο( g ) ⇔ f ∈ Ο ( g ) ∧ g ∈ Ο ( f ) ¿Para qué sirven? Nos permiten agrupar en clases aquellas funciones con el mismo crecimiento Ejemplo: ¿ O(3n) = O(15n) ? ¿ O(349n2) = O(2n2) ? Las clases resultantes se representan con la función más simple que contienen Ejemplo: O(3n) = O(15n) - Representante : O(n) O(349n2) = O(2n2) - Representante : O(n2) 32 16 3. Análisis asintótico NOTACION O : PROPIEDADES Veamos algunas propiedades de esta notación: 1. Dada una función f, entonces f ∈ Ο( f ) 2. 3. f ∈ Ο( g ) ⇒ Ο( f ) ⊂ Ο( g ) Ο( f ) = Ο( g ) ⇔ f ∈ Ο ( g ) ∧ g ∈ Ο ( f ) ¿Para qué sirven? Nos permiten agrupar en clases aquellas funciones con el mismo crecimiento 33 3. Análisis asintótico NOTACION O : MAS PROPIEDADES Veamos más propiedades interesantes de esta notación: 4. 5. 6. 7. 8. f ∈ Ο ( g ) ∧ g ∈ Ο( h ) ⇒ f ∈ Ο ( h ) f ∈ Ο( g ) ∧ g ∈ Ο(h) ⇒ f ∈ Ο(min( g , h)) Regla de la suma: Si f1 ∈ Ο( g1 ) ∧ f 2 ∈ Ο( g 2 ) ⇒ f1 + f 2 ∈ Ο(max( g1 , g 2 )) Regla del producto: Si f1 ∈ Ο ( g1 ) ∧ f 2 ∈ Ο( g 2 ) ⇒ f1 · f 2 ∈ Ο( g1 · g 2 ) lim n →∞ f ( n) = 0 ⇒ f ∈ Ο( g ) g ( n) 9. f (n) = am ·n m + am-1 ·n m-1 + ... + a1 ·n + a0 ∈ Ο(n m ) 10. Ο( f ) ⊂ Ο( g ) ⇔ f ∈ Ο( g ) ∧ g ∉ Ο( f ) 34 17 3. Análisis asintótico COTA INFERIOR. NOTACION Ω ≥0 Sea f : ` → \ se define el conjunto Ω(f) como el conjunto de funciones acotadas inferiormente por un múltiplo de f : Ω( f ) = { g : ` → \ ≥0 | ∃c > 0, ∃n0 ∈ ` / ∀n ≥ n0 g(n) ≥ c· f (n)} Dada una función t : ` → \ ≥0 se dice que múltiplo de f que es cota inferior de t t ∈ Ω( f ) si existe un 35 3. Análisis asintótico COTA INFERIOR. NOTACION Ω ≥0 Sea f : ` → \ se define el conjunto Ω(f) como el conjunto de funciones acotadas inferiormente por un múltiplo de f : Ω( f ) = { g : ` → \ ≥0 | ∃c > 0, ∃n0 ∈ ` / ∀n ≥ n0 g(n) ≥ c· f (n)} Dada una función t : ` → \ ≥0 se dice que múltiplo de f que es cota inferior de t t ∈ Ω( f ) si existe un t (n) Ejemplos: c· f (n) 3n + 1 ¿pertenece a Ω(n) ? 3n2 + 1 ¿pertenece a Ω(n) ? 3n2 + 1 ¿pertenece a Ω(n2)? 36 18 3. Análisis asintótico NOTACION Ω : PROPIEDADES Veamos algunas propiedades de esta notación: 1. Dada una función f, entonces f ∈ Ω( f ) 2. 3. 4. 5. 6. 7. 8. 9. f ∈ Ω( g ) ⇒ Ω( f ) ⊂ Ω ( g ) Ω( f ) = Ω ( g ) ⇔ f ∈ Ω( g ) ∧ g ∈ Ω ( f ) f ∈ Ω ( g ) ∧ g ∈ Ω( h) ⇒ f ∈ Ω( h) f ∈ Ω( g ) ∧ g ∈ Ω(h) ⇒ f ∈ Ω(max( g , h)) Regla de la suma: Regla del producto: lim n →∞ Si f1 ∈ Ω( g1 ) ∧ f 2 ∈ Ω( g 2 ) ⇒ f1 + f 2 ∈ Ω( g1 + g 2 ) Si f1 ∈ Ω( g1 ) ∧ f 2 ∈ Ω( g2 ) ⇒ f1· f 2 ∈ Ω( g1·g2 ) f ( n) = 0 ⇒ g ∈ Ω( f ) g ( n) f (n) = am ·n m + am-1 ·n m-1 + ... + a1 ·n + a0 ∈ Ω(n m ) si am > 0 37 3. Análisis asintótico COTA PROMEDIO. NOTACION Θ ≥0 Sea f : ` → \ se define el conjunto Θ(f) como el conjunto de funciones acotadas superior e inferiormente por un múltiplo de f : Θ( f ) = { g : ` → \ ≥0 | ∃c, d > 0, ∃n0 ∈ ` / ∀n ≥ n0 c· f ( n) ≤ g( n) ≤ d · f ( n)} O lo que es lo mismo: Θ( f ) = Ο( f ) ∩ Ω( f ) Dada una función t : ` → \ ≥0 se dice que t ∈ Θ( f ) si existen múltiplos de f que son cota superior y cota inferior de t 38 19 3. Análisis asintótico COTA PROMEDIO. NOTACION Θ ≥0 Sea f : ` → \ se define el conjunto Θ(f) como el conjunto de funciones acotadas superior e inferiormente por un múltiplo de f : Θ( f ) = { g : ` → \ ≥0 | ∃c, d > 0, ∃n0 ∈ ` / ∀n ≥ n0 c· f ( n) ≤ g( n) ≤ d · f ( n)} O lo que es lo mismo: Θ( f ) = Ο( f ) ∩ Ω( f ) Dada una función t : ` → \ ≥0 se dice que t ∈ Θ( f ) si existen múltiplos de f que son cota superior y cota inferior de t d · f ( n) t (n) c· f (n) Ejemplos: 3n + 1 ¿pertenece a Θ(n) ? 3n2 + 1 ¿pertenece a Θ(n) ? 3n2 + 1 ¿pertenece a Θ(n2)? 39 3. Análisis asintótico NOTACION Θ : PROPIEDADES Veamos algunas propiedades de esta notación: 1. Dada una función f, entonces f ∈ Θ( f ) 2. 3. 4. 5. 6. 7. 8. f ∈ Θ( g ) ⇒ Θ( f ) = Θ( g ) Θ ( f ) = Θ ( g ) ⇔ f ∈ Θ ( g ) ∧ g ∈ Θ( f ) f ∈ Θ ( g ) ∧ g ∈ Θ ( h) ⇒ f ∈ Θ ( h ) Regla de la suma: Si f1 ∈ Θ( g1 ) ∧ f 2 ∈ Θ( g 2 ) ⇒ f1 + f 2 ∈ Θ(max( g1 , g 2 )) Regla del producto: Si f ∈ Θ( g ) ∧ f ∈ Θ( g ) ⇒ f · f ∈ Θ( g · g ) 1 1 2 2 1 2 1 2 f ( n) lim = k /(k ≠ 0 ∧ k ≠ ∞ ) ⇒ Θ( f ) = Θ ( g ) n →∞ g ( n ) f (n) = am ·n m + am -1 ·n m-1 + ... + a1 ·n + a0 ∈ Θ(n m ) si am > 0 40 20 3. Análisis asintótico JERARQUIA DE FUNCIONES Los conjuntos de funciones están incluidos unos en otros generando una ordenación de las diferentes funciones Ο( f 2 (n)) Ο( f 3 ( n)) Ο( f1 (n)) Las clases más utilizadas en la expresión de complejidades son: Ο(1) ⊂ Ο(lg lg n) ⊂ Ο(lg n) ⊂ Ο(lga>1 n) ⊂ constantes sublogarítmicas logarítmicas ⊂ Ο( n ) ⊂ O(n) ⊂ Ο(n lg n) ⊂ sublineales ⊂ Ο(n ) ⊂ " ⊂ Ο(n 2 lineales a >2 lineal-logarítmicas ) ⊂ Ο(2n ) ⊂ Ο(n!) ⊂ Ο(nn ) polinómicas exponenciales 41 3. Análisis asintótico JERARQUIA DE FUNCIONES ¿Cuál es la diferencia real entre complejidades? Ejemplo: Máximo tamaño, por hora, que pueden resolver algoritmos cuyo tiempo de ejecución está acotado superiormente por algunas de las funciones anteriores. Complejidad 1 paso=1 ms. 1 paso=0’1 ms. log n 10 106 10 n 3’6 ⋅ 10 n log n 2 ⋅ 105 2 ⋅ 106 n2 1.897 6000 n3 153 330 21 25 6 107 3’6 ⋅ 107 2 n La complejidad logarítmica es mucho mejor que la lineal. No requiere mucho esfuerzo plantear soluciones logarítmicas a problemas aparentemente lineales (p.e. la búsqueda de un elemento en un vector ordenado). Ante una complejidad elevada no tiene sentido invertir en hardware para acelerar el tiempo de proceso. 42 21 3. Análisis asintótico EJERCICIOS 1. f (n) ∈ O( g (n)) ∧ g ( n) ∈ O( h(n)) ⇔ f (n) ∈ O(h( n)) 2. Ο( f ( n)) = O( g ( n)) ⇔ f (n) ∈ O( g (n)) ∧ g ( n) ∈ O( f ( n)) k ⎛ k ⎞ 3. fi (n) ∈ O( gi (n))1..k ⇒ ∏ fi (n) ∈ O ⎜ ∏ gi ( n) ⎟ i =1 ⎝ i =1 ⎠ ⎛ k ⎞ 4. fi ( n) ∈ O( gi ( n))1..k ⇒ ∑ f i (n) ∈ O ⎜ max gi ( n) ⎟ i =1 ⎝ ⎠ i =1 k k ⎛ k ⎞ 5. fi (n) ∈ O( gi (n))1..k ⇒ ∑ fi ( n) ∈ O ⎜ ∑ gi (n) ⎟ i =1 ⎝ i =1 ⎠ 6. O(lg a n) = O(lg b n) ∀a, b > 1 n 7. ∑i k ∈ O(n k +1 ) i =1 43 4. Calculo de Complejidades INTRODUCCION Cálculo de la complejidad espacial y temporal Nos centraremos en la complejidad temporal Etapas para obtener las cotas de complejidad 1. Determinar la talla o tamaño del problema 2. Determinar el caso mejor y peor: instancias para las que el algoritmo tarda más o menos No siempre existe mejor y peor caso ya que existen algoritmos que se comportan de igual forma para cualquier instancia del mismo tamaño 3. Obtención de las cotas para cada caso Algoritmos iterativos Algoritmos recursivos 44 22 4. Calculo de Complejidades ALGORITMOS ITERATIVOS función SUMAR (A:vector[1..n]:entero):entero; (1) s=0; (2) para i=1 hasta n hacer (3) s=s+A[i]; Imprime(A[i]); fpara (4) devuelve(s); La talla es n y no existe caso mejor ni peor. fin Línea (1) (2,3) (4) Suma Pasos 1 n(1) 1 n+2 C. Asintótica Θ(1) Θ(n) Θ(1) Θ(n) Correlación entre ambas expresiones 45 4. Calculo de Complejidades ALGORITMOS ITERATIVOS función BUSCAR (var X:vector[N]; z:N):N var i:natural fvar; (1) i=1; (2) mientras (i≤⏐X⏐) ∧ (Xi≠z) hacer (3) i=i+1; fmientras (4) si i= ⏐X⏐+1 entonces devuelve 0 si_no devuelve i fin • La talla es el número de elementos del vector (n) • Existe mejor y peor caso ¿cuándo se produce cada uno de ellos? Línea (1) (2,3) (4) Suma Cuenta Pasos Mejor Peor Caso Caso 1 1 1 n 1 1 3 n+2 C.Asintótica Mejor Peor Caso Caso Ω(1) Ο(1) Ω(1) Ο(n) Ω(1) Ο(1) Ω(1) Ο(n) Cs(n)=n+2 Ci (n)=3 Cs(n)=Ο(n) Ci (n)=Ω(1) 46 23 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS Cálculo del máximo de un vector función MÁXIMO (var v:vector[n]):entero var i,n,max: entero fvar n=|v|; max=v[1]; para i=2 hasta n hacer si v[i]>max entonces max=v[i] fsi fpara devuelve max; fin 47 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS Búsqueda de un elemento en un vector ordenado función BUSCA (var v:vector[N]; x,pri,ult:natural):natural var m:natural fvar repetir m= (pri+ult)/2; si v[m]>x entonces ult= m-1 si_no pri= m+1 fsi hasta (pri>ult) ∨ v[m]=x; si v[m]=x entonces devuelve m si_no devuelve 0 fsi fin 48 24 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS función F1 (a,n:entero) var i,j:natural fvar para i=1 hasta n hacer para j=i hasta 2 hacer F2(a); fpara fpara fin (*decreciente*) F2 ∈ Θ(a2) función SEP (x,y:entero):entero var i,j,r:natural fvar para i=0 hasta (x+y) hacer j=i; mientras (j≠0) hacer j=j div 2; r=r+j; fmientras fpara devuelve r; fin 49 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS función EJEMPLO (var X:vector[carácter]); var i,j,n:natural fvar n=|X|; i=2; mientras (n>1) ∧ (i≤n) hacer j=i; mientras X[j] ≠ X[1] hacer j=j div 2; fmientras i=i+1; fmientras fin 50 25 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS función CALC (var v:vector[natural); var i,j,n,x:natural fvar n=|v|; si n>0 entonces j=n; x=0; mientras x<100 hacer j=j div 2; x=0; i=j; repetir i=i+1; x=x+v[i]; hasta i=n; si j=0 entonces x=100 fsi fmientras Imprimir(j); fsi fin 51 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS funcion PICO (n,m:N):N var i,j,x:N; x=0; si (n<=m) entonces i=n; repetir para j=1 hasta m*m hacer x=x+1 fpara; i=i+2; hasta (i>m); finsi; devuelve x; fin 52 26 4. Calculo de Complejidades ALGORITMOS ITERATIVOS. EJERCICIOS función ECTO(a: vector[N]; n:N): entero var i,j:N; permuta:bool; permuta=CIERTO; i=1; mientras (permuta) hacer i=i+1; permuta=FALSO; para j=n hasta i hacer si (a[j]<a[j-1]) entonces x=a[j]; permuta=CIERTO; a[j]=a[j-1]; a[j-1]=x; fsi fpara fmientras fin 53 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Dado un algoritmo recursivo: función factorial (n:entero):entero si n=1 entonces si_no fsi devuelve(1) devuelve(n*factorial(n-1)) fin Podemos obtener el coste de la mayoría de las instrucciones pero, ¿cuál es la contribución al coste total del algoritmo de cada una de las llamadas recursivas? Sólo se puede asegurar que el coste será una función del tamaño del problema f(n) cuyo valor dependerá del coste de las sucesivas llamadas recursivas. n ≤1 ⎧Θ(1) f ( n) = ⎨ ⎩Θ(1) + f (n − 1) n > 1 54 27 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Una relación de recurrencia es una expresión que relaciona el valor de una función f definida para un entero n con uno o más valores de la misma función para valores menores que n. ⎧a· f ( F (n)) + P(n) n > n0 f ( n) = ⎨ n ≤ n0 ⎩ P '(n) • a∈` constante • P(n), P '(n) Funciones de n. No tienen por que ser polinomios • F (n ) < n ⎧n - b b∈` Función estrictamente decreciente con n. Normalmente ⎨ ⎩n / b 55 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Las ecuaciones de recurrencia se usan para expresar la complejidad de un algoritmo recursivo aunque también son aplicables a los iterativos Si el algoritmo dispone de mejor y peor caso, habrá una función para cada caso La complejidad de un algoritmo se obtiene en tres pasos: 1. 2. 3. Determinar la talla del algoritmo Obtención de las ecuaciones de recurrencia del algoritmo Resolución de las ecuaciones Para resolverlas, usaremos el método de sustitución: Es el método más sencillo Sólo para funciones lineales (sólo una vez en función de sí mismas) Consiste en sustituir cada f(n) por su valor al aplicarle de nuevo la función hasta obtener un término general 56 28 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Ejemplo: Calcular la complejidad del siguiente algoritmo función máximo (a:vector[enteros];i:entero):entero si i=1 entonces devuelve(a[1]) si_no para j=1 hasta i ESCRIBIR(a[i]) fpara devuelve(MAYOR(máximo(a,i-1),a[i])) fsi ESCRIBIR ∈ Θ(1) fin MAYOR ∈ Θ(1) 1. 2. Obtener talla (n) del algoritmo (n=i – Num. de elementos del vector) Obtener ecuación de recurrencia a partir del algoritmo n =1 ⎧Θ(1) f ( n) = ⎨ ⎩Θ(n) + f (n − 1) n > 1 Coste instrucciones no recursivas Coste base de recurrencia (NO hay recursión) Coste caso general de recurrencia (hay recursión) Coste llamada recursiva 57 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Ejemplo: Calcular la complejidad del siguiente algoritmo Obtener talla (n) del algoritmo (n=i – Num. de elementos del vector) 2. Obtener ecuación de recurrencia a partir del algoritmo 3. Resolver la recurrencia por sustitución 1. f (n) = n + f ( n − 1) = (1ª rec) = n + ( n − 1) + f (n − 2) = (2ª rec) = n + ( n − 1) + ( n − 2) + f (n − 3) = ... (3ª rec) i −1 = ∑ (n − j ) + f (n − i) (i-esima rec) j =0 La recursión termina cuando (n-i)=1 Por tanto, habrá i=n-1 llamadas recursivas i −1 n −2 f (n) = ∑ ( n − j ) + f (n − i) = ∑ (n − j ) + f (1) j =0 j =0 (n − 2)(n − 1) = ∑ (n) − ∑ ( j ) + f (1) = n( n − 1) − +1 2 j =0 j =0 n −2 n −2 ⇒ f ( n) ∈ Θ( n 2 ) 58 29 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT Elemento pivote: Nos sirve para dividir en dos partes el vector Al azar Primer elemento Elemento central Elemento mediana → Quicksort primer elemento → Quicksort central → Quicksort mediana Pasos: Elección del pivote Se hacen dos recorridos del vector: ascendente (i) y descendente (j) El vector queda dividido en dos partes: parte izquierda del pivote → elementos menores parte derecha del pivote → elementos mayores Se hacen dos llamadas recursivas. Una con cada parte del vector. 59 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT primer elemento funcion QUICKSORT_CENTINELA (var a:vector; pi,pf:indice) var i,j: indice; x: elemento fvar si pi<pf entonces x=a[pi]; i=pi+1; j=pf; repetir mientras a[i]<x hacer i=i+1 fmientras mientras a[j]>x hacer j=j-1 fmientras si i≤j entonces SWAP(a[i],a[j]); i=i+1; j=j-1; fsi hasta i>j; SWAP(a[pi],a[j]); QUICKSORT_CENTINELA (a,pi,j-1); QUICKSORT_CENTINELA (a,j+1,pf); fsi fin 60 30 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT Tamaño del problema: n Mejor caso: subproblemas (n/2, n /2) n ≤1 ⎧ Θ(1) ⎪ f (n ) = ⎨ ⎛n⎞ ⎛n⎞ ⎪ Θ( n ) + f ⎜⎝ 2 ⎟⎠ + f ⎜⎝ 2 ⎟⎠ n > 1 ⎩ Peor caso: subproblemas (0, n -1),(n -1,0) n ≤1 ⎧ Θ(1) f (n) = ⎨ ⎩ Θ( n ) + f ( 0 ) + f ( n − 1) n > 1 61 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT Resolución recurrencia mejor caso f ( n) = n + 2 f ( n ) = 2 ( (1ª rec) ) = n + 2 n + 2 f ( n ) = 2n + 4 f ( n ) = 2 4 4 ( (2ª rec) ) = 2n + 4 n + 2 f ( n ) = 3n + 8 f ( n ) = ... (3ª rec) 4 8 8 = in + 2i f ( n i ) 2 (i-esima rec) La recursión termina cuando (n/2 i)=1 Por tanto, habrá i=log2 n llamadas recursivas f (n ) = in + 2i f ( n i ) = 2 = n log 2 n + nf (1) = n log 2 n + n ⇒ f (n ) ∈ Ω(n log 2 n ) 62 31 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT Resolución recurrencia peor caso f (n ) = n + f (n − 1) = (1ª rec) = n + ( n − 1) + f (n − 2) = (2ª rec) = n + ( n − 1) + (n − 2) + f (n − 3) = ... (3ª rec) i −1 = ∑ (n − j ) + f (n − i ) (i-esima rec) j =0 La recursión termina cuando (n-i)=1 Por tanto, habrá i=n-1 llamadas recursivas i −1 n−2 f ( n ) = ∑ ( n − j ) + f ( n − i ) = ∑ ( n − j ) + f (1) j =0 j =0 n −2 n −2 j =0 j =0 = ∑ ( n ) − ∑ ( j ) + f (1) = n (n − 1) − ( n − 2)( n − 1) +1 2 ⇒ f ( n ) ∈ Ο( n 2 ) 63 4. Calculo de Complejidades ALGORITMOS RECURSIVOS Coste QUICKSORT 8 7 1 8 4 2 6 1 4 2 2 2 1 1 1 1 1 1 1 1 5 1 Caso peor 4 1 Ο(n2) Caso mejor 3 1 Ω(n lg2n) 2 1 1 1 64 32 4. Calculo de Complejidades ALGORITMOS RECURSIVOS QUICKSORT mediana (el pivote es la mediana) En la versión anterior se cumple que el caso mejor es cuando el elemento seleccionado es la mediana. En este algoritmo estamos forzando el caso mejor. Obtener la mediana Coste menor que Ο(nlgn) Se aprovecha el recorrido para reorganizar elementos y para encontrar la mediana en la siguiente subllamada. Su complejidad es por tanto de Θ(nlgn). 65 4. Calculo de Complejidades ALGORITMOS RECURSIVOS: EJERCICIOS función DESPERDICIO (n:natural) para i=1 hasta n hacer para j=1 hasta i hacer ESCRIBIR i,j,n ; fpara fpara si n>0 entonces para i=1 hasta 4 hacer DESPERDICIO(n/2); fpara fsi fin ESCRIBIR ∈ Θ(1) 66 33 4. Calculo de Complejidades ALGORITMOS RECURSIVOS: EJERCICIOS función EJEMPLO (n, a:entero):entero; var r:entero fvar; si a2 > n entonces devuelve 0; si_no r=EJEMPLO(n,2a); opción n<(r+a)2 devuelve r; n≥(r+a)2 devuelve r+a; fopción fsi fin 67 4. Calculo de Complejidades ALGORITMOS RECURSIVOS: EJERCICIOS funcion ORD(n: N ; v:vector[N]); si (n>0) entonces ORD(n div 2,v); QuickSortMediana(v,1,n); fsi; fin 68 34 4. Calculo de Complejidades ALGORITMOS RECURSIVOS: EJERCICIOS función PAL (A:vector[N]; iz,de:N):bool var n,i:N; n=de-iz+1; opcion (n <=1): devuelve (CIERTO) ; (n > 1): para i=1 hasta n hacer Imprimir("A") fpara; si (A[iz]=A[de]) entonces devuelve(PAL(A,iz+1,de-1)); sino devuelve(FALSO) ; finsi; fopcion fin 69 5. Anexo EXPRESIONES FRECUENTES 1. c ∈ Θ(1), ∀c ∈ \ ≥0 n 2. n ∑ i = 2 (n + 1) ∈ Θ(n n 7. 2 i =1 ) i =1 n n 1 3. ∑ i 2 = (n + 1)( n + ) ∈ Θ(n3 ) 3 2 i =1 4. ∑i n 8. k ∈ Θ(n k +1 5. ), ∀k ∈ ` 10. k ∑ i2 n −1 6. ∑ r i = ∈ Θ( r n ), r ≠ 1 r −1 i =0 r i −1 = n 2 n − 2 n + 1 ∈ Θ( n 2 n ) i =1 ∈ Θ( n k +1 ), ∀k ∈ ` n +1 lg n) i =1 i =1 n ∑ lg i ∈ Θ(n n ∑ (n − i ) ∈ Θ(1) si r > 1 i n i =1 n 1 ∑r i =1 9. n 1 ∑ i ∈ Θ(lg n) 11. ∑ i2 −i ∈ Θ(1) i =0 70 35 5. Anexo EXPRESIONES FRECUENTES n 12. ∑ ir i −1 ∈ Θ(nr n ), ∀r > 1 i =1 n 13. ∑ ir −i ∈ Θ(1), ∀r > 1 i =1 n 14. 1 ∑ i ! ∈ Θ(1) i =1 n 15. ⎛n⎞ ∑ ⎜⎝ i ⎟⎠ = 2 n ∈ Θ(2n ) i =1 n ⎛n⎞ 16. n !∈ Θ( n ⎜ ⎟ ) ⎝e⎠ 71 5. Anexo SUMA DE SERIES Donde: progresión aritmética a + an S n = n· 0 2 a0 = Primer elemento de la serie an = n-esimo elemento de la serie S n = Suma de los n primeros elementos de la serie r = Razon de la serie geometrica progresión geométrica S n = a0 · Sn = r n −1 r −1 a0 1− r r >1 r < 1, n → ∞ 72 36 5. Anexo SUMA DE SERIES progresión aritmético-geométrica S n = 1r + 2r 2 + " + nr n rS n = 1r 2 + 2r 3 " + ( n − 1)r n + nr n+1 S n − rS n = r + r 2 + r 3 " + r n − nr n +1 (1 − r ) S n = r + r 2 + r 3 " + r n − nr n+1 n ∑r i r + r 2 + r 3 " + r n − nr n+1 nr n+1 i =1 n·r n +1 Sn = − = − 1− r 1− r 1− r 1− r 73 5. Anexo RECURRENCIAS FRECUENTES REDUCCIÓN DEL PROBLEMA POR RESTAS (c>0) n ≤ n0 ⎧b ' f ( n) ⎨ ⎩ a· f (n − c) + b n > n0 n ≤ n0 ⎧b ' f ( n) ⎨ ⎩ a· f (n − c) + b·n n > n0 ⎧b ' f ( n) ⎨ k ⎩ a· f (n − c) + b·n n ≤ n0 n > n0 a < 1 → Θ(1) Solución: a = 1 → Θ(n) a > 1 → Θ( a n / c ) a < 1 → Θ( n ) Solución: a = 1 → Θ( n 2 ) a > 1 → Θ( a n / c ) a < 1 → Θ( n k ) Solución: a = 1 → Θ(n k +1 ) a > 1 → Θ( a n / c ) 74 37 5. Anexo RECURRENCIAS FRECUENTES REDUCCIÓN DEL PROBLEMA POR DIVISIONES (c>1) n ≤ n0 ⎧b ' f ( n) ⎨ ⎩a· f (n / c) + b n > n0 n ≤ n0 ⎧b ' f ( n) ⎨ ⎩a· f (n / c) + bn n > n0 ⎧b ' f ( n) ⎨ k ⎩ a· f (n / c ) + b·n n ≤ n0 n > n0 a < 1 → Θ(1) Solución: a = 1 → Θ(lg n) a > 1 → Θ(nlg a ) a < 1 → Θ ( n) Solución: a = 1 → Θ(n lg n) a > 1 → Θ(nlg a ) a < c k → Θ( n k ) Solución: a = c k → Θ(n k lg n) a > c k → Θ(n lg a ) 75 5. Anexo ALGORITMOS DE ORDENACION Directos Inserción directa Inserción binaria Selección directa Intercambio directo (burbuja) Avanzados Mergesort Quicksort Heapsort Shell 76 38 5. Anexo ALGORITMOS DE ORDENACION INSERCIÓN DIRECTA función INSERCION_DIRECTA (var a:vector[natural]; var i,j: entero; x:natural fvar para i=2 hasta n hacer x=a[i]; j=i-1; mientras (j>0) ∧ (a[j]>x) a[j+1]=a[j]; j=j-1; fmientras a[j+1]=x; fpara n: natural) hacer fin 77 5. Anexo ALGORITMOS DE ORDENACION INSERCIÓN BINARIA función INSERCION_BINARIA (var a:vector[natural]; var i, j, iz, de, m: entero; x:natural fvar n: natural) para i=2 hasta n hacer x=a[i]; iz=1; de=i-1; mientras (iz≤de) hacer m= (iz+de)/2; si a[m]>x entonces de= m-1 si_no iz=m+1 fsi fmientras para j=i-1 hasta iz hacer (*decreciente*) a[j+1]=a[j]; fpara a[iz]=x; fpara fin 78 39 5. Anexo ALGORITMOS DE ORDENACION SELECCIÓN DIRECTA función SELECCION_DIRECTA (var a:vector[natural]; n:natural) var i, j, posmin: entero; min:natural fvar para i=1 hasta n-1 hacer min=a[i]; posmin=i; para j=i+1 hasta n hacer si a[j]<min entonces min=a[j]; posmin=j; fsi fpara a[posmin]=a[i]; a[i]=min; fpara fin 79 5. Anexo ALGORITMOS DE ORDENACION INTERCAMBIO DIRECTO (Burbuja) función INTERCAMBIO_DIRECTO var i,j:entero fvar (var a:vector[natural]; n:natural ) para i=2 hasta n hacer para j=n hasta i hacer si a[j]<a[j-1] entonces SWAP(a[j],a[j-1]); fsi fpara fpara fin 80 40 5. Anexo ALGORITMOS DE ORDENACION QUICKSORT central función QUICKSORT_SIN_CENTINELA (var a:vector; pi,pf:indice) var i,j: indice; x: elemento fvar comienzo si pi<pf entonces x=a[(pi+pf)/2]; i=pi; j=pf; repetir mientras a[i]<x hacer i=i+1 fmientras mientras a[j]>x hacer j=j-1 fmientras si i≤j entonces SWAP(a[i],a[j]); i=i+1; j=j-1; fsi hasta i>j; QUICKSORT_SIN_CENTINELA (a,pi,j); QUICKSORT_SIN_CENTINELA (a,i,pf); fsi fin 81 41