TEMA 1.2. REPASO DE COMPLEJIDAD DE ALGORITMOS Estructura de Datos y de la Información Ingeniería Técnica en Informática de Gestión. LADE + Ingeniería Técnica en Informática de Gestión URJC EDI (ITIG) Complejidad URJC 1 / 20 ÍNDICE 1 Introducción 2 Recursos Informáticos: tiempo y espacio 3 Casos para el estudio de la complejidad 4 Medidas asintóticas. Órdenes de complejidad 5 Reglas prácticas para el cálculo de complejidades EDI (ITIG) Complejidad URJC 2 / 20 Introducción 1. INTRODUCCIÓN I Definición La complejidad o eficiencia de un algoritmo se define como el orden de magnitud de los recursos informáticos que requiere el algoritmo para ser ejecutado. Utilidad El estudio de la complejidad de los algoritmos permite evaluar su coste, y así: 1 Comparar algoritmos entre sí. 2 Averiguar si un algoritmo es factible. EDI (ITIG) Complejidad URJC 3 / 20 Introducción 1. INTRODUCCIÓN II Aspectos que intervienen en el cálculo de complejidades Cuáles son los recursos informáticos que hay que estimar (apartado 2). En qué casos (para qué tipo de entradas) hay que estimar la complejidad (apartado 3). Qué significa calcular el orden de magnitud (apartado 4). Qué reglas prácticas existen para calcular complejidades (apartado 5). EDI (ITIG) Complejidad URJC 4 / 20 Recursos Informáticos: tiempo y espacio 2. RECURSOS INFORMÁTICOS I Recursos considerados Los recursos informáticos a evaluar son el tiempo (de ejecución) y/o el espacio (de memoria) requeridos por los algoritmos. Ambos dependen claramente del tamaño de la entrada, por lo que se trata de calcular las funciones TA y EA tales que: TA (n) = tiempo requerido para la ejecución del algoritmo A con una entrada de tamaño n. EA (n) = espacio de memoria requerido para la ejecución del algoritmo A con una entrada de tamaño n. EDI (ITIG) Complejidad URJC 5 / 20 Recursos Informáticos: tiempo y espacio 2. RECURSOS INFORMÁTICOS II Cómo estimar los recursos El cálculo del tiempo (similar para el espacio) no debe depender ni del lenguaje de programación utilizado, ni del compilador, ni de la máquina elegida. Solución: TA (n) = número de operaciones básicas que realiza el algoritmo A con una entrada de tamaño n donde una operación básica es aquella cuyo tiempo de ejecución se puede acotar superiormente por una constante, independientemente del tamaño de la entrada, la máquina, etc. EDI (ITIG) Complejidad URJC 6 / 20 Casos para el estudio de la complejidad 3. CASOS PARA EL ESTUDIO DE LA COMPLEJIDAD Las funciones TA y EA no sólo dependen del tamaño de la entrada, sino también de su contenido: ¡no se tarda lo mismo en ordenar 10 números ya ordenados que en ordenar 10 números totalmente desordenados! Los casos de estudio más habituales son los siguientes: El peor caso, caso extremo en el que el número de operaciones a ejecutar (o el espacio requerido) es el mayor posible. El caso medio o caso probabilístico, que representa una situación intermedia (su evaluación suele requerir técnicas estadísticas). El cálculo en el peor caso asegura que los recursos reales consumidos, sea cual sea el contenido de la entrada, nunca rebasarán el valor calculado. EDI (ITIG) Complejidad URJC 7 / 20 Medidas asintóticas. Órdenes de complejidad 4. MEDIDAS ASINTÓTICAS Y ÓRDENES DE COMPLEJIDAD I La complejidad de un algoritmo es importante fundamentalmente cuando el tamaño de la entrada es grande (si hay que ordenar un conjunto de 5 elementos, la eficiencia del algoritmo no importa demasiado, puesto que en cualquier caso será razonable). Por ello, para analizar las funciones TA o EA bastará con estudiar su comportamiento asintótico, es decir, cómo se comportan cuando el tamaño de la entrada, n, es grande. Existen varias medidas para estudiar el comportamiento asintótico de una función. Una de las más habituales es la medida O(f ) (O grande de f ) que se describe a continuación, que permite obtener cotas superiores. EDI (ITIG) Complejidad URJC 8 / 20 Medidas asintóticas. Órdenes de complejidad 4. MEDIDAS ASINTÓTICAS Y ÓRDENES DE COMPLEJIDAD II Definición Sean f , g : N → R + . Se dice que g es del orden de f , y se denota g ∈ O(f ), si existen c ∈ R + y n0 ∈ N tales que ∀n ≥ n0 , g(n) ≤ c.f (n) Algunas propiedades útiles O(logb n) = O(logc n) g(n) = am nm + am−1 nm−1 + . . . a1 n + a0 ∈ O(nm ) si am 6= 0 P g(n) = ni=1 c = c.n ∈ O(n) P g(n) = ni=1 i = n(n+1) ∈ O(n2 ) 2 P g(n) = ni=1 i 2 = n(n+1)(2n+1) ∈ O(n3 ) 6 EDI (ITIG) Complejidad URJC 9 / 20 Medidas asintóticas. Órdenes de complejidad 4. MEDIDAS ASINTÓTICAS Y ÓRDENES DE COMPLEJIDAD III Conjuntos O(f ) más importantes y su ordenación O(1) O(log n) O(n) O(np ) O(2n ) O(n!) complejidad constante complejidad logarítmica complejidad lineal complejidad polinómica (p=2: cuadrática, p=3: cúbica) complejidad exponencial complejidad factorial O(1) ⊂ O(log n) ⊂ O(n) ⊂ O(n log n) ⊂ O(np ) ⊂ O(2n ) ⊂ O(n!) EDI (ITIG) Complejidad URJC 10 / 20 Reglas prácticas para el cálculo de complejidades 5. REGLAS PRÁCTICAS I Resumen de los apartados anteriores Calcular la complejidad de un algoritmo A = estudiar el comportamiento asintótico de las funciones TA /EA (dependientes del tamaño n de la entrada al algoritmo) en el peor caso o en un caso medio. = encontrar una función f : N → R + tal que en el peor caso/caso medio se tenga TA /EA ∈ O(f ). Observación Dado que los conjuntos O(f ) proporcionan cotas superiores, se tratará de encontrar la menor de todas las funciones f : N → R + tales que TA /EA ∈ O(f ) (por ejemplo, si TA /EA ∈ O(n2 ), evidentemente también es TA /EA ∈ O(n3 ), pero ... ¡lo segundo no aporta nada!). EDI (ITIG) Complejidad URJC 11 / 20 Reglas prácticas para el cálculo de complejidades 5. REGLAS PRÁCTICAS II Consideraciones previas Todo algoritmo está compuesto por una secuencia de instrucciones. Existen unas reglas prácticas para calcular el coste de cada uno de los posibles tipos de instrucciones. La complejidad de un algoritmo se basa en el coste de las instrucciones que lo componen pero el cálculo depende de si el algoritmo es o no recursivo. EDI (ITIG) Complejidad URJC 12 / 20 Reglas prácticas para el cálculo de complejidades Reglas para calcular el tiempo de ejecución en el peor caso de los distintos tipos de instrucciones REGLA 1: INSTRUCCIONES ELEMENTALES Se llama instrucción elemental a las operaciones de entrada/salida, asignaciones y expresiones aritméticas en las que no está involucrada ninguna variable que dependa del tamaño de la entrada del algoritmo. [I ≡ instrucción elemental] ⇒ TI ∈ O(1) REGLA 2: SECUENCIAS DE INSTRUCCIONES I ≡ I1 ; I2 TI ∈ O(f1 ) ⇒ TI ∈ O(max(f1 , f2 )) 1 TI2 ∈ O(f2 ) EDI (ITIG) Complejidad URJC 13 / 20 Reglas prácticas para el cálculo de complejidades REGLA 3: INSTRUCCIONES DE SELECCIÓN I ≡ SI B ENTONCES I1 SI NO I2 TB ∈ O(fB ) ⇒ TI ∈ O(max(fB , f1 , f2 )) TI ∈ O(f1 ) 1 TI2 ∈ O(f2 ) I ≡ CASO E EN caso-1: I1 . . . caso-k: Ik TE ∈ O(fE ) ⇒ TI ∈ O(max(fE , fi )) TIi ∈ O(fi ), i ∈ {1, . . . , k } EDI (ITIG) Complejidad URJC 14 / 20 Reglas prácticas para el cálculo de complejidades REGLA 4: INSTRUCCIONES DE REPETICIÓN fiter I ≡ MIENTRAS B HACER J X fiter ≡ núm. iteraciones (peor caso) ⇒ TI ∈ O fi i=1 TB;J ∈ O(fi ) en la iteración i Nota: la complejidad de un bucle PARA i DESDE a1 HASTA a2 HACER J se calcula igual que la de un bucle MIENTRAS salvo que en el caso PARA el coste de la condición B se puede ignorar puesto que es siempre constante. EDI (ITIG) Complejidad URJC 15 / 20 Reglas prácticas para el cálculo de complejidades REGLA 5: LLAMADAS A SUBPROGRAMAS En el caso de instrucciones que sean llamadas a otros algoritmos (subprogramas), el coste de la instrucción será el coste del subprograma: I ≡ llamada a un subprograma P ⇒ TI = TP EDI (ITIG) Complejidad URJC 16 / 20 Reglas prácticas para el cálculo de complejidades Cálculo del tiempo de ejecución en el peor caso de un algoritmo Depende de si el algoritmo es o no es recursivo. SI EL ALGORITMO ES NO RECURSIVO Su complejidad es el máximo de las complejidades de las instrucciones que lo componen (la complejidad de cada una de estas instrucciones se calcula aplicando las reglas anteriores). EDI (ITIG) Complejidad URJC 17 / 20 Reglas prácticas para el cálculo de complejidades SI EL ALGORITMO ES RECURSIVO Para calcular la complejidad de un algoritmo recursivo es necesario realizar los dos siguientes pasos: 1 2 Plantear la ecuación recurrente asociada con el tiempo de ejecución T del subprograma. Resolver la ecuación recurrente anterior (es decir, encontrar una expresión no recursiva para el valor de T). Las familias de recurrencias más habituales son: 1 2 Ecuaciones recurrentes obtenidas mediante sustracción Ecuaciones recurrentes obtenidas mediante división EDI (ITIG) Complejidad URJC 18 / 20 Reglas prácticas para el cálculo de complejidades Recurrencias obtenidas mediante sustracción T(n) = cnk aT(n − b) + cnk si 0 ≤ n < b si n ≥ b a = número de llamadas recursivas que se realizan n − b = tamaño de los subproblemas generados cnk = coste de las instrucciones que no son llamadas recursivas Solución: ( T(n) ∈ EDI (ITIG) O(nk +1 ) O(an div b ) Complejidad si a = 1 si a > 1 URJC 19 / 20 Reglas prácticas para el cálculo de complejidades Recurrencias obtenidas mediante división T(n) = cnk aT(n/b) + cnk si 1 ≤ n < b si n ≥ b a = número de llamadas recursivas que se realizan n/b = tamaño de los subproblemas generados cnk = coste de las instrucciones que no son llamadas recursivas Solución: O(nk ) T(n) ∈ O(nk log n) O(nlogb a ) EDI (ITIG) Complejidad si a < bk si a = bk si a > bk URJC 20 / 20