análisis de la eficiencia

49
1. Introducción al análisis de la eficiencia. Dados dos algoritmos que resuelven el mismo problema de forma diferente: ¿Cuál es el mejor? ¿Cómo se decide que un algoritmo es mejor que otro? Posibles respuestas: El que consuma menos memoria (número de variables, número y tamaño de estructuras de datos usadas). El que vaya más rápido (midiendo el tiempo que tarda en ejecutarse o el número de operaciones elementales que realiza). El que funcione adecuadamente. El más fácil para el humano de leer, escribir o entender. ¿Por qué deberíamos preocuparnos por la calidad de un algoritmo, si lo único que tenemos que hacer para que un programa vaya más rápido es utilizar un ordenador con más potencia, o si éste consume mucha memoria, añadirle algunos módulos de memoria más?

Upload: mejiaff

Post on 06-Jun-2015

6.073 views

Category:

Documents


6 download

DESCRIPTION

Apuntes sobre complejidad algorítmica

TRANSCRIPT

Page 1: Análisis de la eficiencia

1. Introducción al análisis de la eficiencia.

Dados dos algoritmos que resuelven el mismo problema de forma diferente:

¿Cuál es el mejor?

¿Cómo se decide que un algoritmo es mejor que otro?

Posibles respuestas:

• El que consuma menos memoria (número de variables, número y tamaño de estructuras de datos usadas).

• El que vaya más rápido (midiendo el tiempo que tarda en ejecutarse o el número de operaciones elementales que realiza).

• El que funcione adecuadamente.

• El más fácil para el humano de leer, escribir o entender.

¿Por qué deberíamos preocuparnos por la calidad de un algoritmo, si lo único que tenemos que hacer para que un programa vaya más rápido es utilizar un ordenador con más potencia, o si éste consume mucha memoria, añadirle algunos módulos de memoria más?

Page 2: Análisis de la eficiencia

2

Problema: El aumento de las prestaciones no implica una clara mejora en el tiempo de ejecución. Ejemplo: Ejecución de un programa con n datos en 10-42n segundos:

• Si n=10, el programa terminará en 0'1 segundos. • Si fuera 20, aproximadamente 10 minutos; • 30, un día y • 40, un año.

Si cambiamos el ordenador que estamos usando por uno 100 veces más rápido:

• Si n=45 datos, en un año. Sólo se mejoraría de manera importante si se modificara el diseño del programa, sustituyendo este algoritmo anterior por uno más eficiente.

Page 3: Análisis de la eficiencia

3

¿Cómo podemos realizar el estudio de la eficiencia de un algoritmo? • Empíricamente: programar los algoritmos y ejecutarlos

varias veces con distintos datos de entrada. • Teóricamente: determinar matemáticamente la cantidad

de recursos (tiempo de ejecución y memoria) requeridos por la implementación en función del tamaño de la entrada.

Problemas del estudio empírico:

• Dependencia de los resultados obtenidos del tipo de ordenador,

• del lenguaje de programación usado y • del traductor con el que se obtenga el código

ejecutable, y de la pericia del programador.

(si cambiamos alguno de estos elementos probablemente se obtengan resultados diferentes, con lo cual no podemos establecer una eficiencia empírica absoluta).

• Existen algoritmos que pueden ser comparados con

esta técnica sólo cuando el número de datos con los que se ejecutan es relativamente pequeño.

Page 4: Análisis de la eficiencia

4

Ejemplo: el problema del viajante de comercio. (encontrar una ruta que una un número de ciudades pero con coste mínimo, por ejemplo en kilómetros a recorrer.)

10 ó 20 poblaciones: solución en pocos segundos. 100 poblaciones: no existe ordenador para encontrar

la solución en un tiempo razonable. Ventajas del estudio teórico:

Independencia con todos los factores anteriores. Genérico para cualquier entrada

Depende sólo de las instrucciones que componen el

algoritmo y del tamaño del problema.

Ofrece como salida una expresión matemática que indica cómo se produce el crecimiento del tiempo de ejecución conforme aumenta el tamaño de entrada.

• De manera híbrida: realizar un estudio teórico y

posteriormente estimar ciertos parámetros de forma empírica.

Page 5: Análisis de la eficiencia

5

La entrada de un problema y su tamaño.

• Algoritmo de ordenación de un vector de n enteros: n es el tamaño de los datos de entrada.

• Algoritmo para el cálculo del factorial de un entero: el valor de ese número entero es el que nos determinaría el tamaño de nuestro problema.

• Algoritmo que resuelve el problema del viajante de comercio: el número de ciudades y el de carreteras que unen a estas.

De manera general, el tamaño de la entrada de un problema vendrá dado en función de un número entero que nos mide el número de componentes de dicho ejemplo. No existe una regla exacta para determinar el tamaño de un problema, sino que tendrá que obtenerse según las características de cada uno.

Page 6: Análisis de la eficiencia

6

2. El tiempo de ejecución de un algoritmo y su orden de eficiencia.

T(n) Unidades de tiempo (segundos, milisegundos,...) que un algoritmo tardaría en ejecutarse con unos datos de entrada de tamaño n.

Pero... ... tiempo de ejecución depende

• del ordenador utilizado • traductor con el que se genere el código objeto

Entonces... ...T(n) representará

• número de instrucciones simples (asignaciones,

comparaciones, operaciones aritméticas,...) ejecutadas. O de manera equivalente, • el tiempo de ejecución del algoritmo en un ordenador

idealizado, donde cada una de las instrucciones simples consume una unidad de tiempo.

T(n) no se expresa con unidades y n≥0 y T(n) es positivo

Page 7: Análisis de la eficiencia

7

Principio de invarianza: Dos implementaciones distintas de un mismo algoritmo, que toman t1(n) y t2(n) unidades de tiempo, respectivamente, para resolver un problema de tamaño n, no diferirán en eficiencia en más de una constante multiplicativa.

Existe un c>0 tal que t1(n) ≤ ct2(n).

Lo que implica que .... Un programa vaya 10 ó 1000 veces más rápido cambiando de máquina,

pero sólo un cambio de algoritmo permitirá obtener una mejoría mayor cuanto más crezca el tamaño de los ejemplos, lo que nos llevará a ignorar las constantes multiplicativas a todos los efectos.

Page 8: Análisis de la eficiencia

8

Tiempos de ejecución en diferentes situaciones: Un algoritmo se puede ejecutar con datos de entrada diferentes.

• Hay datos con los que tarde más, y • Datos con los que acabe antes.

Hay que indicar a qué situación pertenece la expresión T(n) calculada:

• Peor caso: límite superior en el tiempo (máximo valor). Generalmente es pesimista.

• Mejor caso: límite inferior en el tiempo (siempre se

ejecutará por encima de dicho tiempo).

• Caso promedio: media ponderada de ambos casos. Salvo que se diga lo contrario, siempre trabajaremos con el el peor caso.

Page 9: Análisis de la eficiencia

9

Ejemplo 1:

algoritmo de ordenación por selección.

Vector ordenado de forma creciente (mejor caso) Vector ordenado de manera decreciente (peor caso)

Ambos tiempos coinciden más o menos Ejemplo 2:

algoritmo de ordenación por inserción.

Vector ordenado de forma creciente proporcional a n. Vector ordenado de forma decreciente proporcional a n2

Los tiempo de ambos son diferentes

Page 10: Análisis de la eficiencia

10

¿Cómo se calcula el tiempo de ejecución de un algoritmo?

sobre la base de las instrucciones lo componen

• Tiempo de ejecución(Evaluación de una expresión) =

lo que se tarde en realizar las operaciones que contenga

• Tiempo de ejecución (asignación a una variable simple

de una expresión) =

tiempo de evaluación de la expresión +

tiempo de asignación

• Tiempo de ejecución (operación de escritura de una

expresión) =

tiempo de evaluación de la expresión +

tiempo del proceso de escritura. • Tiempo de ejecución (lectura de una variable) =

constante

Page 11: Análisis de la eficiencia

11

• Tiempo de ejecución (secuencia de instrucciones) =

la suma de los tiempos de cada una de las instrucciones. • Tiempo de ejecución (una sentencia condicional)

Tiempo de evaluar la condición +

máximo de los costes del bloque entonces y del bloque sino. • Tiempo de ejecución (bucle)

(tiempo del cuerpo del bucle +

tiempo de evaluar la condición del bucle) *

número de iteraciones Operaciones elementales: todas aquellas cuyo tiempo de ejecución esté limitado superiormente por una constante (sólo depende de la implementación) Ejemplo: operaciones de suma, multiplicación, resta, división, módulo y similares.

Page 12: Análisis de la eficiencia

12

Ejemplo: fución para calcular el valor mínimo de un vector.

/* 1 */ public int buscarMinimo(int vector [], int n)

/* 2 */ /* 3 */ int j, min; /* 4 */ min= 0; /* 5 */ for (j=1; j<n;j++) /* 6 */ if (vector[j] < vector[min]) /* 7 */ min= j; /* 8 */ return min; /* 9 */

T(n)= ca + (ci + ca+ ce)(n-1) + cr.

T(n)= 1+3*(n-1) + 1= 3n-1

Page 13: Análisis de la eficiencia

13

Eficiencia la forma en que el tiempo de ejecución (y en general cualquier recurso) necesario para procesar una entrada de tamaño n crece cuando se incrementa el valor de n.

Se especifica mediante una función de crecimiento

Orden de una función Se dice que un algoritmo necesita un tiempo de ejecución del orden de una función cualquiera f(n), cuando existe una constante positiva c y una implementación del algoritmo que resuelve cada instancia del problema en un tiempo acotado superiormente por cf(n), siendo la función f(n) la que marca cómo crecerá dicho tiempo de ejecución cuando aumente n. Un algoritmo será, por tanto, más eficiente que otro si el tiempo de ejecución del peor caso tiene un orden de crecimiento menor que el segundo.

Page 14: Análisis de la eficiencia

14

Ejemplo: Dos algoritmos ,A y B, resuelven un problema mediante las funciones TA(n)=100n y TB(n)=2n2, respectivamente.

¿Cuál deberíamos usar?

Si n<50, B es mejor que A.

Si n≥50, A es mejor que B, • si n= 100, A es el doble de rápido que B • si n=1.000, A es 20 veces más rápido que B

¡OJO!

más importante la forma de las funciones (n y n2), que las constantes (2 y 100).

En vez de decir que el tiempo de ejecución del algoritmo es T(n)=4n+3, por ejemplo, diremos que T(n) es del orden de n, lo que implica que el tiempo de ejecución es alguna constante multiplicada por n, para tamaños de problemas de dimensión n.

Page 15: Análisis de la eficiencia

15

Las funciones que nos marcan más comúnmente el orden de crecimiento de los algoritmos y los nombres por las que se les conocen son las siguientes:

f(n)= 1 => Constante. f(n)= log n => Logarítmica. f(n)= n => Lineal. f(n)= nlog n => Quasilineal. f(n)= n2 => Cuadrática. f(n)= n3 => Cúbica, y en general, f(n)= nk => polinómica. f(n)= kn => Exponencial.

Page 16: Análisis de la eficiencia

16

• Algoritmo eficiente: como mucho un coste

quasilineal.

• Mejor orden es el logarítmico: doblar el tamaño del problema, no afecta al tiempo y doblar el tiempo permite tratar problemas enormes.

• Órdenes quasilienal y lineal: duplicar el tamaño del problema se duplica aproximadamente el tiempo empleado, y análogamente, al duplicar el tiempo se pueden tratar problemas aproximadamente del doble de tamaño.

• Órdenes polinómicos: se necesita mucho tiempo para resolver un problema que ha crecido relativamente poco en tamaño.

• Problemas de orden polinomial se consideran tratables.

Page 17: Análisis de la eficiencia

17

Crecimiento de algunas funciones conforme crece el tamaño n del problema:

log n n nlog n n2 n3 2n

3 10 33 100 1.000 1.024

7 100 664 10.000 1.000.000 1'267650600228e+30

0 1.000 9.966 1.000.000 1.000.000.000

1'07150860718e+301

3 10.000 132.877 100.000.000 1'0e+12 Enorme

7 100.000 1.660.964 10.000.000.000 1'0e+15 Más enorme 10 1.000.000 19.931.569 1.000.000.000.000 1'0e+18 Sin comentarios

Representación gráfica de estas funciones.

Page 18: Análisis de la eficiencia

18

Los órdenes de eficiencia son clases de equivalencia (basadas en el principio de invarianza):

T1(n)= 2n3 + 4n2 + 5 y T2(n)= n3- 4n,

Pertenecen a la misma clase cuya representante es la función f(n)= n3.

Otros criterios para elegir o diseñar un algoritmo:

• Si un programa se va a ejecutar pocas veces, el costo de escritura es el principal, no influyendo en dicho costo el tiempo de ejecución, debiéndose elegir un algoritmo cuya aplicación sea la más fácil.

• Tamaño de las entradas: la velocidad de crecimiento

del tiempo de ejecución puede ser menos importante que las constantes multiplicativas

Ejemplo:

Dos algoritmos con T(n)= 100n2 y 5n3, respectivamente. Para n < 20, el segundo programa será más rápido que el primero si el programa se va a ejecutar normalmente con entradas menores que 20, conviene ejecutar el segundo.

Page 19: Análisis de la eficiencia

19

3. Las notaciones asintóticas.

Las notaciones 0 mayúscula y o minúscula. O(f(n)), representa el conjunto de funciones g que crecen como mucho tan rápido como f, o lo que es lo mismo, las funciones g tales que f llega a ser en algún momento una cota superior para g.

g(n) ∈∈∈∈ O(f(n)) (g es del orden de f).

Sea f:N→R+∪0 una función cualquiera, el conjunto de las funciones del orden de f(n), notado como O(f(n)), se define:

O(f(n)) = g ∃∃∃∃c0 ∈∈∈∈ R+ y ∃∃∃∃n0 ∈∈∈∈ N, ∀∀∀∀ n ≥≥≥≥ n0 g(n) ≤≤≤≤ c0f(n)

• T(n)= 3n + 2 ∈ O(n), ∃ n0= 2, y c0=4 / 3n+2 ≤ 4n.

• T(n)= 1.000n2 + 100n - 6 ∈ O(n2), ∃n0= 100, y c0=1.001 / 1.000n2 + 100n - 6 ≤ 1.001n2.

• T(n) = 6·2n + n2 ∈ O(2n), ∃ n0= 4, y c0=7 / 6·2n + n2 ≤

7·2n.

• T(n) = 3n ∉ O(2n). Supongamos ∃ n0 y c0 / ∀ n≥n0, 3n

≤ c02n. Entonces c0 ≥ (3/2)n ∀ n≥n0, pero no ∃ constante suficientemente grande que (3/2)n para todo n.

Page 20: Análisis de la eficiencia

20

La notación asintótica del orden generalizada a dos parámetros es la siguiente: sea f:N x N→R+∪0 una función cualquiera, el conjunto de las funciones del orden de f(n, m), notado como O(f(n, m)), se define como sigue:

O(f(n,m)) = g ∃c0 ∈ R+ y ∃n0,m0∈ N, ∀ n ≥ n0 y m ≥ m0, g(n,m) ≤ c0f(n,m)

Propiedades de O(f), ∀ funciones f, f', g, h: [Invarianza multiplicativa].

• ∀c ∈ R+, g ∈ O(f) si y sólo si c·g ∈ O(f)

[Invarizanza aditiva] • ∀c ∈ R+, g ∈ O(f) si y sólo si c+g ∈ O(f)

[Reflexividad]

• f ∈ O(f) [Transitividad]

• Si h ∈ O(g) y g ∈ O(f) entonces h ∈ O(f)

[Criterio de Caracterización]. • g ∈ O(f) si y sólo si O(g) ⊆ O(f)

Page 21: Análisis de la eficiencia

21

[Regla de la suma]

• Si g ∈ O(f) y h ∈ O(f'), entonces g + h ∈ O(max(f, f'))

Demostración: Si g(n) ∈ O(f(n)) entonces ∃c1, n1 / g(n) ≤ c1f(n),∀n ≥ n1. Si h(n) ∈ O(f'(n)) entonces ∃c2, n2 / h(n) ≤ c2f'(n), ∀n ≥ n2. ∀n ≥ max(n1, n2), f(n)+f'(n) ≤ (c1 + c2) max(f(n), f'(n)).

Dos trozos de código independientes, con eficiencias O(f(n)) y O(f'(n)), la eficiencia del trozo completo será O(max(f(n), f'(n)).

Ejemplo: O(n2), O(n3) O(nlgn) Orden del tiempo de ejecución de los tres:

O(max(n2, n3, nlgn))= O(n3).

Page 22: Análisis de la eficiencia

22

[Regla del producto].

• Si g ∈ O(f) y h ∈ O(f'), entonces g·h ∈ O(f·f') Demostración: Si g(n) ∈ O(f(n)) ∃c1, m1 / g(n) ≤ c1f(n), ∀ n ≥ n1. Si h(n) ∈ O(f'(n)) ∃c2, m2 / h(n) ≤ c2f'(n), ∀ n ≥ n2. ∀ n≥ n1·n2 se tiene que f(n)·f'(n) ≤ (c1·c2) f(n)· f'(n). Dos trozos de código anidados (no independientes), con eficiencias O(f(n)) y O(f'(n)), la eficiencia del trozo completo es O(f(n)*g(n)). Ejemplo: O(n) O(lg n) Orden del código completo pertenecerá a

O(nlgn).

Page 23: Análisis de la eficiencia

23

La notación o minúscula es también una cota superior:

o(f(n)) = g ∀c0 ∈ R+ y ∃n0 ∈ N, ∀ n ≥ n0 g(n) ≤ c0f(n)

Se cumple la siguiente cadena de inclusiones:

O(1) ⊂ O(log n) ⊂ O(n) ⊂ O(n2) ⊂ ... ⊂ O(na) ⊂... ...⊂ O(2n) ⊂ O(n!)

Page 24: Análisis de la eficiencia

24

Las notaciones ΩΩΩΩ

Ωk y Ω∞, dan cotas inferiores en el orden de eficiencia:

Ωk(f(n)) = g ∃c0 ∈ R+ y ∃n0 ∈ N, ∀ n ≥ n0 g(n) ≥ c0f(n) Ω∞(f(n)) = g ∃c0 ∈ R+ y ∃n0 ∈ N, ∃ n ≥ n0 g(n) ≥ c0f(n)

Si g(n) ∈ Ωk(f(n)) entonces f es una cota inferior de g desde un punto en adelante, o lo que es lo mismo, ofrece un mínimo del cual nunca baja (g crece más deprisa que f). Por otro lado, si g(n) ∈ Ω∞(f(n)), entonces en una cantidad infinita de ocasiones g crece lo suficiente como para alcanzar a f.

Las notaciones ΘΘΘΘ. Esta notación define las funciones con la misma tasa de crecimiento (crecen al mismo ritmo) que f, es decir, Θ(f) = O(f) ∩ Ωk(f). Formalmente:

Θ (f(n)) = g ∃c0, c1∈ R+ y ∃n0 ∈ N, ∀ n ≥ n0, 0 ≤ c0f(n) ≤ g(n) ≤ c1f(n)

Page 25: Análisis de la eficiencia

25

Relaciones entre las diferentes notaciones.

Page 26: Análisis de la eficiencia

26

4. Cálculo del tiempo de ejecución de un algoritmo iterativo y de su orden de eficiencia.

Objetivo: calcular el orden de eficiencia de un algoritmo. Dos formas diferentes:

• Obtener el tiempo de ejecución del programa, y posteriormente calcular su orden de eficiencia.

• Ir calculando el orden de eficiencia de las diferentes

sentencias y bloques existentes en el programa.

Sentencias simples. Cualquier sentencia simple (lectura, escritura, asignación, ...) tardará un tiempo O(1), ya que el tiempo constante que tarda realmente la ejecución de esa sentencia se puede acotar por una constante que multiplica a la función f(n)=1. Esto podremos hacerlo siempre y cuando no intervengan en dicha sentencia ni variables estructuradas,ni operandos aritméticos que dependan del tamaño del problema.

Page 27: Análisis de la eficiencia

27

/*1*/ public void main() /*2*/ /*3*/ int a, b, c; /*4*/ System.out.println("Introduzca los dos números a sumar: "); /*5*/ a= Console.in.readInt(); b= Console.in.readInt(); /*6*/ c= a+b; /*7*/ System.out.println("La suma de”+a+”y”+b+”es “+c); /*8*/ 1) O(1+1+1+1) = O(max(1,1,1,1))= O(1) (regla de la suma) 2) T(n)= te+tl+toa+te= c,

te = el tiempo que se tarda en ejecutar una escritura; tl,= el de la lectura y toa = el que se tarda en hacer una operación aritmética más una asignación. T(n)=c ∈ O(1).

Page 28: Análisis de la eficiencia

28

Sentencias de repetición. * En bucles controlados por contador: /*1*/ for (i=0; i<n;i++) /*2*/ System.out.println(" "+ i);

• La sentencia /*2/ tiene un tiempo de ejecución te. • El ciclo se repite n veces T(n) =(tc +te) * n ∈ O(n)

(tc el tiempo de evaluación de la condición más el incremento)

/*1*/ for (i=0; i<n;i++) /*2*/ for (j=0; j<n;j++) /*3*/ matriz[i][j]= 0;

• La línea 3 tiene un orden de ejecución constante; • El bucle de la línea 2 se repite n veces O(n), • El ciclo de la primera línea O(n). • Aplicando en este caso la regla del producto: O(n2).

Obteniendo el tiempo de ejecución del bloque completo :

• T2-3(n) = n(ta + tc), para las sentencias 2 y 3, y • T1-3(n)= n(nta+ntc) + tc = tan2 +tcn2 +tc ∈ O(n2)

Si la dimensión fuera n x m, en este caso, el orden de eficiencia sería O(n·m).

Page 29: Análisis de la eficiencia

29

* Bucles controlados por centinela. /*1*/ i=0; /*2*/ while (i<n && vector[i] != valor) /*3*/ i++; (Se supone el peor caso: recorrer todo el vector) Así, el bucle while repetirá n veces una sentencia de O(1), por lo que su orden de eficiencia será n.

• Despreciamos el tiempo constante de la evaluación de la expresión del bucle.

• Para bucles do...while la forma de proceder sería la misma.

Sentencias condicionales.

El tiempo de ejecución será el tiempo de evaluación de la condición, más el máximo del bloque a ejecutar cuando la condición se evalúe como verdadera y del bloque a ejecutar cuando se evalúe como falsa. Cuando estamos tratando con órdenes, el de la evaluación de la expresión booleana, que es constante, se desprecia.

• Bloque then tiene una eficiencia O(f(n)) • Bloque else posee una eficiencia O(g(n)) • el orden de eficiencia del if será O(max(f(n), g(n)).

Page 30: Análisis de la eficiencia

30

/*1*/ if (n> m) /*2*/ n= n *m; /*3*/ else /*4*/ for (i=0; i<n;i++) /*5*/ m= m * n;

• Orden del else = el del bucle for: O(n), • Orden del then: O(1), • Orden del if = O(max(n,1))= O(n)

• T(n) = te + max(tea, ntea)= te + ntea ∈ O(n), • te corresponde con el tiempo de evaluar la condición • tea, el correspondiente a la asignación más la suma.

Page 31: Análisis de la eficiencia

31

Llamadas a funciones no recursivas.

• Calcular el tiempo de ejecución de aquellas que no

llaman a otras. • Calcular el tiempo de ejecución de las que llaman a

otras a partir de sus tiempos correspondientes. Dependiendo de dónde estén situadas las llamadas a funciones, se deberá incluir su tiempo de ejecución de una u otra manera:

• En una asignación: T(n) asignación = T(n) de la función. Si hay más de una función invocada en la sentencia: T(n) asignación = T(n) función1+ ...+T(n) funciónN. (o el máximo de los órdenes)

• En una condición de un bucle (cualquier tipo): T(n) bucle = (T(n) función + T(n) cuerpo) * numIter

• En la condición de una sentencia condicional: T(n) if = T(n) función + T(n) if

• En una secuencia de sentencias: simplemente aplicar la regla de la suma, si tratamos con órdenes, o sumar tiempos si son tiempos de ejecución.

Page 32: Análisis de la eficiencia

32

Ejemplos public void Burbuja (int vector [], int n) int i, j; /*1*/ int aux; /*2*/ for (i = 0; i < n - 1; i++) /*3*/ for (j = n-1; j< i; j--) /*4*/ if (vector[j-1] > vector[j]) /*5*/ /*6*/ aux= vector[j-1]; /*7*/ vector[j-1]= vector[j]; /*8*/ vector[j]= aux; /*9*/

Bucle for de la línea 3: If de la línea 4:

Then de la línea 5: Tres sentencias O(1)

O(1) , por la regla de la suma. O(1) O(n-i), ya que se repite n-i veces.

El bucle de la línea 2: se ejecutará n-1 veces, por lo que el tiempo de ejecución del bucle será una constante multiplicada por:

−=−

=−−

= ∈ O(n2).

Page 33: Análisis de la eficiencia

33

public long SumaSubsecuenciaMaxima(int vector[], int n) int i, j, k; /*1*/ int sumMax=0, posInicioSec=0,

posFinSec=0, sumaActual; /*2*/ for (i=0;i<n; i++) /*3*/ for (j=i;j<n; j++) /*4*/ /*5*/ sumaActual=0; /*6*/ for (k=i;k<j; i++) /*7*/ sumaActual+= vector[k]; /*8*/ if (sumaAcutal > sumaMax) /*9*/ /*10*/ sumaMax= sumaActual; /*11*/ posInicioSec= i; /*12*/ posFinSec= j; /*13*/ /*14*/ /*15*/ return sumMax; Entre las líneas 4 y 14:

• Asignación línea 5: O(1); • Condicional línea 8: O(1). • Bucle línea 6: repetirá j-i+1 veces una asignación, lo

que implica que pertenecerá a O(j-i+1). (Peor de los caso, se repite n veces O(n)).

O(n), aplicando la regla de la suma.

El bucle de la línea 3, en el peor de los casos ejecutará n operaciones de O(n) O(n2). Y el de la línea 2, n veces O(n2) O(n3) .

Page 34: Análisis de la eficiencia

34

El tiempo de ejecución de la función vendrá dada por:

=

=

=

=

,

Segunda sumatoria:

+−+−=

=+−

,

Tercera sumatoria (primer bucle):

==

+++=

+−=

=

=+−+−

++=

+++

+

+−++

=

Teniendo en cuenta el orden de complejidad de la función, y observando la implementación, nos damos cuenta que se podría evitar ese orden si se elimina un bucle for, consiguiendo un orden de eficiencia cuadrático. De hecho, se puede reducir más su orden de complejidad mediante un algoritmo recursivo a O(nlgn), tarea que le proponemos al lector.

Page 35: Análisis de la eficiencia

35

5. Cálculo del tiempo de ejecución de algoritmos recursivos y de su orden de eficiencia.

Cuando se analiza la eficiencia de un programa recursivo, suele ocurrir habitualmente que las funciones del tiempo de ejecución que se obtengan sean también recursivas

relaciones recurrentes o recurrencias. Objetivo: buscar una expresión de T(n) no recursiva. public long factorial (long n) /*1*/ if (n <= 0) return 1; /*2*/ else return n * factorial(n-1); Si n ≤ 1, T(n)= c y si n > 1, se cumplirá que T(n) = d +T(n-1). ¿Cómo resolvemos esta recurrencia? Mediante la expansión de la misma: sustituyendo T(n-1) por su valor correspondiente, y así sucesivamente hasta que se elimine la recursividad T(n) = d + T(n-1)= d + d+ T(n-2)= d + d + d + T(n-3). i pasos: T(n)=i · d+T(n-i), n>i. Para i=n-1, T(n) = (n-1)d + T(n-(n-1)) =

= (n-1)d + T(1)= (n-1) d+c ∈ O(n).

Page 36: Análisis de la eficiencia

36

public void OrdenarVector (int vector[], int n) int i, maxPos; if (n>1) maxPos=0; for (i=1; i<n;i++) if (vector[i] > vector[maxPos]) maxPos= i; if (maxPos != 0) i= vector[0]; vector[0]= vector[maxPos]; vector[maxPos]= i; OrdenarVector(vector, n-1); Ecuación de recurrencia: Si n ≤ 1, T(1)=1, si no, T(n) = T(n-1) + n.

T(n)= T(n-1) +n =T(n-2)+(n-1) +n= T(n-3) + (n-2) + (n-1) + n, y en general:

+≥−+−= −

=

.

Para i=n-1, −

=

−+=

∈+

=−=+++= −

=

Page 37: Análisis de la eficiencia

37

6. Resolución de recurrencias homogéneas. Las recurrencias lineales homogéneas tienen la forma:

a0tn + a1tn-1 + ... + aktn-k = 0 [1]

donde los ti son los valores buscados (aplicamos el adjetivo de lineal por que no hay términos de la forma titi+j ó ti2, por ejemplo), los coeficientes ai son constantes y la recurrencia es homogénea por que la combinación lineal de los ti es igual a 0. Soluciones buscadas de la forma tn=xn, donde x es una constante. Si sustituimos esta solución en [1] obtenemos:

a0xn + a1xn-1 + ... + ak xn-k = 0 [2] Esta ecuación tiene dos soluciones: x=0, que no nos sirve, y

a0xk + a1xk-1 + ... + ak= 0, ecuación característica asociada a la ecuación recurrente inicial. Supongamos que las k raíces de esta ecuación característica son r1, r2, ..., rk, entonces cualquier combinación lineal

=

=

de términos rin es solución para la ecuación

recurrente lineal homogénea.

Page 38: Análisis de la eficiencia

38

≥−+−==

=

Cambio de notación [1]: tn= 3tn-1 - 4tn-2 => tn - 3tn-1 - 4tn-2 = 0

Hacer la sustitución tn=xn: xn - 3xn-1 - 4xn-2 =0

Dividimos cada término por xn-k= xn-2 , con objeto de eliminar la solución trivial y conseguimos la ecuación característica:

x2-3x-4=0

Resolver la ecuación: raíces -1 y 4. La solución general a la expresión recursiva tendrá la forma de la expresión [2], donde los ri son las soluciones de la recurrencia.

tn= c1(-1)n + c24n Sustituyendo n=0 y n=1 en la ecuación anterior, llegaremos al siguiente sistema de dos ecuaciones con dos incógnitas (en este caso c1 y c2):

c1 + c2 = 0 y -c1 + 4c2 =1

Solución: c1= -1/5 y c2= 1/5, Sustituyendo: tn= (1/5)(4n - (-1)n) ∈ O(4n).

Page 39: Análisis de la eficiencia

39

Dada la recurrencia:

tn= tn-1 + tn-2, n≥2, y t0= 0, t1= 1 Cambiando de notación, sustituyendo tn=xn y dividiendo por xn-k= xn-2 se obtiene la ecuación característica:

tn-tn-1-tn-2=0 => x2-x-1=0

Soluciones a la ecuación:

r1= (1+51/2)/2 y r2=(1-51/2)/2. Sustituyendo las condiciones iniciales n=0 y n=1, en la expresión que nos da la solución, tn=c1r1n +c2r2n,, obtenemos el sistema de dos ecuaciones con dos incógnitas:

c1 + c2 = 0 y c1r1 + c2r2 = 1.

Al solucionarlo,

c1=1/(51/2) y c2=-1/(51/2). Sustituir c1 y c2 en la expresión de la solución de la recurrencia.

T(n) ∈ O((1/(51/2))n)

Page 40: Análisis de la eficiencia

40

Dada la recurrencia:

tn= 5tn-1 - 8tn-2 + 4tn-3, n≥3, t0=0, t1=1, t2=2

La ecuación característica es:

x3 - 5x2+ 8x - 4= 0

Soluciones a la ecuación anterior:

• 1, con multiplicidad 1, y • 2, con multiplicidad 2, • es decir, (x-1)(x-2)2 =0.

La solución general será, por tanto:

tn= c11n + c22n + c3n2n

(Se tantos sumandos como multiplicidades tengan las raíces múltiples: si m es la multiplicidad de una raíz r, entonces se añadirán los sumandos rn, nrn, n2rn,..., nm-1rn, multiplicados por sus correspondientes constantes) Dadas las condiciones iniciales, se plantean las ecuaciones de un sistema lineal:

c1 + c2= 0, c1 + 2c2 + 2c3= 0 y c1 + 4c2 + 8c3= 0,

Soluciones: c1= -2, c2= 2 y c3=-1/2

tn= 2n+1 - n2n-1 - 2 ∈O(2n)

Page 41: Análisis de la eficiencia

41

7. Resolución de recurrencias no homogéneas. Las recurrencias:

a0tn + a1tn-1 +...+ aktn-k = bnp(n) [3]

Donde p(n) es un polinomio de grado d, y b es una constante (el resto es igual que [1]). Resolver la ecuación característica:

(a0xk + a1xk-1 +...+ ak)(x-b)d+1=0 [4]

Donde (a0xk + a1xk-1 +...+ ak) es la aportación de la parte homogénea y (x-b)d+1 de la parte no homogénea. Una vez en este punto, se concluye el proceso dando los mismos pasos que en la solución de recurrencias homogéneas.

Page 42: Análisis de la eficiencia

42

Dada la recurrencia:

tn= 1 + tn-1 + tn-2 => tn - tn-1 - tn-2 = 1

Como 1= 1np(n), siendo p(n)=1 con grado d=0 y b=1. Entonces, la ecuación característica quedaría:

(x2 -x-1)(x-1)=0

Dada esta otra recurrencia:

tn - 2tn-1 = 3n

En este caso, b=3, p(n) = 1 y, por tanto, d=0, obteniendo la ecuación característica:

(x-2)(x-3)=0

Dada esta tercera recurrencia:

tn - 2tn-1= (n+5)3n

En este caso, b=3, p(n)= n+5, con d=1. La ecuación característica sería:

(x-2)(x-3)2=0

Page 43: Análisis de la eficiencia

43

La recurrencia T(n) = 2T(n-1) + n, n ≥1 y T(0) = 0. Cambiando de notación, tendremos la siguiente ecuación lineal no homogénea:

tn - 2tn-1 = n Identificamos b=1 y p(n)=n, siendo d=1, con lo que se obtiene la ecuación característica siguiente:

(x-2)(x-1)2 = 0 Por tanto, la solución es:

tn= c12n + c21n + c3n1n = c12n + c2 +c3n

Con t0, se obtienen t1 y t2, y se sustituyen en la ecuación anterior, obteniendo el siguiente sistema de tres ecuaciones con tres incógnitas:

c1 + c2 = 0, 2c1 + c2 + c3 = 1, 4c1 + c2 + 2c3 = 4 Las soluciones son c1 = 2, c2 = -2 y c3 = -1, con lo que finalmente:

tn =2·2n - n -2 = 2n+1 - n - 2 ∈ O(2n+1)

Page 44: Análisis de la eficiencia

44

De esta misma manera, se pueden resolver recurrencias de la forma: a0tn + a1tn-1 +...+ aktn-k = b1np1(n) + b2np2(n) + b3np3(n) +... [5]

Siendo d1 el grado de p1(n), d2 el de p2(n) y así sucesivamente. Las ecuaciones características tendrán el siguiente patrón:

(a0xk + a1xk-1 + ...+ ak)(x - b1)d1+1(x - b2)d1+2...= 0 [6]

Dada la recurrencia: T(n)= 2T(n-1) + n + 2n, n≥1 y T(0)= 0. En primer lugar cambiemos la notación y reorganicemos la expresión:

tn = 2tn-1 + n + 2n => tn - 2tn-1 = n + 2n

La parte derecha de la igualdad debemos expresarla de la forma:

n + 2n = b1np1(n) + b2np2(n)

Cosa que conseguiremos si identificamos b1= 1, p1(n)= n (grado d=1) y b2= 1 , p2(n)= 2n (d=1). De esta manera, podemos obtener la ecuación característica:

(x-2)(x-1)2(x-1)2 = 0

Page 45: Análisis de la eficiencia

45

Su solución tiene la forma:

tn=c11n + c2n1n + c3n21n + c4n31n +c52n = c1 + c2n + c3 n2 + c4n3 + c52n

Sustituyendo las condiciones iniciales y resolviendo el sistema de cuatro ecuaciones con cuatro incógnitas, concluiremos que T(n) ∈ O(2n).

Page 46: Análisis de la eficiencia

46

8. Resolución de recurrencias mediante cambio de variable.

=

≥+=

Debido a que el tamaño del problema se divide en dos suponemos que n es potencia de dos, por lo que podemos hacer n=2m, quedando: T(2m)= T(2m-1) +1 , m ≥ 1, siendo el caso base T(20)= 1. Realizamos varias expansiones:

T(2m)= T(2m-1) +1 =T(2m-2) + 1 + 1 = T(2m-3) + 1 + 1 + 1,

y en general:

T(2m)= T(2m-i)+i, m ≥ i

Particularizando para m=i para así poder eliminar la recurrencia, tendríamos:

T(2m)= T(20) + m = m + 1

Como m= lg n, deshaciendo el cambio, finalmente concluimos que T(n)= lgn + 1 ∈ O(lgn).

Page 47: Análisis de la eficiencia

47

Un segundo ejemplo donde n es una potencia de 2, corresponde a la resolución de la recurrencia:

T(n)= 4T(n/2) + n2

Al hacer el mismo cambio de variable que en el ejercicio anterior, se llega a

T(2m)= 4T(2m-1) + 4m,

O lo que es lo mismo:

tm= 4tm-1 + 4m

La ecuación característica es:

(x-4)2=0

Y la solución buscada tendrá la forma:

tm= c14m + c2m4m.

Deshaciendo los cambios, el tiempo de ejecución es

T(n)= c1n2 + c2n2lg n ∈ O(n2 lg n).

Page 48: Análisis de la eficiencia
Page 49: Análisis de la eficiencia