Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch...

23
Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F. _____________________________________________________________________________ Escuela de Ingeniería en Computación, Universidad de La Serena. 1 CAPITULO 2 - Introducción al Análisis de Algoritmos Contenidos: 1. Introducción al Análisis de Algoritmos. 2. Análisis de Algoritmos, 3. Diseño de Algoritmos, 4. Notación, Operadores y cálculo Asintótico. 5. Relación entre estructuras de control y operadores asintóticos. 6. Medida empírica del tiempo de ejecución de un algoritmo 7. Aplicaciones. 1.- Introducción al Análisis de Algoritmos. Muhammad ibn Musa al-Jwarizmi que vivió entre los siglos VIII y IX Un algoritmo es un conjunto finito de instrucciones o pasos que sirven para ejecutar una tarea y/o resolver un problema. De un modo más formal, un algoritmo es una secuencia finita de operaciones realizables, no ambiguas, cuya ejecución da solución a un problema en un tiempo finito. Un algoritmo debe tener las siguientes características: · Preciso, es decir, debe indicar orden de realización. · Definido, es decir, si se repite, debe arrojar el mismo resultado. · Finito, es decir, debe terminar en algún momento. Diagrama de flujo sencillo con los pasos a seguir si una lámpara no funciona.

Transcript of Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch...

Page 1: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

1

CAPITULO 2 - Introducción al Análisis de Algoritmos Contenidos:

1. Introducción al Análisis de Algoritmos. 2. Análisis de Algoritmos, 3. Diseño de Algoritmos, 4. Notación, Operadores y cálculo Asintótico. 5. Relación entre estructuras de control y operadores asintóticos. 6. Medida empírica del tiempo de ejecución de un algoritmo 7. Aplicaciones.

1.- Introducción al Análisis de Algoritmos. Muhammad ibn Musa al-Jwarizmi que vivió entre los siglos VIII y IX

Un algoritmo es un conjunto finito de instrucciones o pasos que sirven para ejecutar una tarea y/o resolver un problema. De un modo más formal, un algoritmo es una secuencia finita de operaciones realizables, no ambiguas, cuya ejecución da solución a un problema en un tiempo finito. Un algoritmo debe tener las siguientes características: · Preciso, es decir, debe indicar orden de realización. · Definido, es decir, si se repite, debe arrojar el mismo resultado. · Finito, es decir, debe terminar en algún momento.

Diagrama de flujo sencillo con los pasos a seguir si una lámpara no funciona.

Page 2: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

2

La verificación de un algoritmo es la evaluación de la solución y su potencial uso como herramienta en diversas aplicaciones. Un Algoritmo es Correcto, si se obtiene la respuesta para la cual fue creado. Es una “prueba formal” de la exactitud de los resultados obtenidos, este tipo de verificación no la realizaremos, pues escapa de los objetivos del curso. La Eficiencia nos la da el Análisis de Algoritmos:

o Dimensión Temporal: Medida del tiempo empleado. o Dimensión Espacial: medida de los recursos invertidos.

encontrar Algoritmos eficientes puede definir si existe o no una solución al problema. Notar que no se puede realizar un análisis del número de instrucciones ni el tiempo empleado, pues ellas son dependientes de las tecnologías (Por ejemplo, RISK, CISC). Respecto de los recursos invertidos o dimensión espacial digamos que la cantidad de memoria para alcanzar la solución, dependerá del lenguaje de programación elegido. Por ejemplo, si se declaran 2 variables del tipo char en el lenguaje C, ocupa 1 byte c/u, es decir M = 2bytes. Mientras que en el lenguaje Pascal, usando Char o Integer, usará 2bytes c/u, es decir M = 4bytes. De manera que el algoritmo implementado en lenguaje C tiene menor complejidad espacial. Se llama tiempo de ejecución (Runtime en inglés) al intervalo de tiempo en el que un programa de computadora se ejecuta en un sistema operativo. Este tiempo se inicia con la puesta en memoria principal del programa, por lo que el sistema operativo comienza a ejecutar sus instrucciones. Y termina con un mensaje de error o cumpliendo su funcionalidad. 2.- Análisis de Algoritmos El análisis de algoritmos forma parte de un campo mucho más amplio, como es la Teoría de complejidad computacional, la que provee estimaciones teóricas para los recursos que necesita cualquier algoritmo que resuelva un problema computacional dado. Estas estimaciones resultan ser bastante útiles en la búsqueda de algoritmos eficientes. A la hora de realizar un análisis teórico de algoritmos es corriente calcular su complejidad con una estimación asintótica, es decir, para un tamaño de entrada suficientemente grande. La cota superior asintótica, y la notación de omega y theta se usan con esa finalidad, entre otras. Al mismo tiempo digamos que las estimaciones asintóticas se utilizan porque diferentes implementaciones del mismo algoritmo, (incluido el uso de ciertas estructuras de datos) no tienen porque tener la misma eficiencia. La medida exacta (no asintótica) de la eficiencia a veces puede ser computada a) usando un modelo teórico, como la Máquina de Turing, b) o estandarizarlo a través de un modelo de computador conocido como RAM (Random-Access-

Maschine), cuyos códigos están escritos en un lenguaje emsamblador (assembler), c) o postulando que ciertas operaciones se ejecutan en una unidad de tiempo. d) o realizando un cálculo empírico, a través de herramientas propias de los lenguajes de

programación. ¿Por qué el tiempo de ejecución de un algoritmo es importante?. La respuesta es muy simple, si conocemos o al menos tenemos una idea del tiempo de ejecución de un algoritmo, podremos saber que tanto va a tardar en entregarnos la respuesta y, así decidir, si la esperamos, nos vamos a tomar un café, o si mejor regresamos en una semana….o más. Cuando se analiza el tiempo de ejecución de un algoritmo, más que el tiempo exacto que tardará el algoritmo en milisegundos o nanosegundos, lo que importa es la función de crecimiento del algoritmo, es decir la función que nos da una idea de que tanto aumentará el tiempo de ejecución

Page 3: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

3

según aumenta el tamaño de la entrada. Por lo general, la función de crecimiento de un algoritmo se expresa a través de operadores que luego veremos. Ejemplo: El problema de ordenar una lista de números. Se tiene un conjunto de números A=x1, x2, ... , xn un algoritmo de ordenación debe entregar como salida una permutación del conjunto A tal que xi ≤ xj para cualquier i < j. Es decir ordenar de menor a mayor. Supongamos que en este contexto un super computador A ejecuta cien millones de instrucciones por segundos y el computador B ejecuta solamente un millón de instrucciones por segundos. Para hacer la diferencia más dramática supongamos que se requiere implementar el InsertionSort en un lenguaje de máquina para el computador A, y el resultado requiere o toma 2n2

instrucciones para ordenar n números. De aquí que tomemos, c1 = 2. Por otro lado, MergeSort es programado por el computador B el que en promedio usa un lenguaje de alto nivel con un compilador ineficiente, cuyo resultado toma 50nlg n instrucciones (es decir, c2 = 50). De manera que para ordenar un millón de números con el computador A toma 20000segundos, casi 6 horas, vea los cálculos

mientras que el computador B toma casi 17 minutos,

de manera que usando un algoritmo en extreme ineficiente el computador B corre la aplicación 20 veces más rápido que el computador A!. La ventaja de MergeSort es aún más pronunciada para cuando ordenemos 10 millones de números. Por eso vale la pena analizar un algoritmo antes de implementarlo. Las medidas exactas de eficiencia son útiles para quienes verdaderamente implementan y usan algoritmos, (como nosotros, los Ingenieros de Software!!), porque tienen más precisión y así les permite saber cuanto tiempo pueden suponer que tomará la ejecución y, esto hace la diferencia entre profesionales y aficionados. Las estimaciones de tiempo dependen de cómo definamos un paso. Para que el análisis tenga sentido, debemos garantizar que el tiempo requerido para realizar un paso esté acotado superiormente por una constante. Hay que mantenerse precavido en este terreno; por ejemplo, algunos análisis cuentan con que la suma de dos números se hace en un paso. Este supuesto puede no estar garantizado en ciertos contextos, como por ejemplo, si los números involucrados en la computación pueden ser arbitrariamente grandes. En tal caso, dejamos de poder asumir que la adición requiere un tiempo constante (sólo basta que use papel y lápiz, para luego comparar el tiempo que necesitas para sumar dos enteros de 2 dígitos cada uno y el necesario para hacerlo con enteros de 1000 dígitos). Otro ejemplo, en donde se tienen 2 algoritmos, los cuales son puestos a prueba entre ellos, para medir su performance.

//Alg1 set up el algoritmo, toma 50 unidad de tiempo; leer los n elementos del array A; /*3 unid. por elemento */ for ( i = 0; i > n; i++) do oper1 en A [ i ]; /* toma 10 unid. */ do oper2 en A [ i ]; /* toma 5 unid. */

Page 4: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

4

do oper3 en A [ i ]; /* toma 15 unid. */ //Alg2 set up el algoritmo, toma 200 unidad de tiempo; leer los n elementos del array A; /*3 unid. por elemento */ for ( i = 0; i < n; i++) do oper1 en A [ i ]; /* toma 10 unid. */ do oper2 en A [ i ]; /* toma 5 unid. */ El tiempo de ejecución son respectivamente: TAlg1 (n) = 50 + 3n + (10 + 5 + 15)n = 50 + 33n TAlg2 (n) = 200 + 3n + (10 + 5)n = 200 + 18n.

Por otra parte, notar que la recursividad puede ser implementada fácilmente a través de ciclos, por ejemplo, Fibonacci(n) recursivo.

public static long fibo(int n) /* 1 */ if (n <= 1) /* 2 */ return 1; else /* 3 */ returm fibo(n-1) + fibo(n-2);

Para el análisis supongamos que T(n) es el tiempo tras la llamada de fibo(n): Para n = 0, n = 1 es T(0) = T(1) = 1 (constante). En línea 3, fibo(n-1) es llamada, resultando un tiempo de T(n -1). fibo(n-2) es llamada, resultando T(n-2). Luego, se tiene que T(n) = T(n -1) + T(n -2) + 2. Resolviendo esta recurrencia lineal, se puede concluir que fibo(n) < (5/3)n. Lo que significa que el tiempo de ejecución de este programa es exponencial ( no puede ser peor!!). Sin embargo, existen otro tipo de algoritmos, o estrategias que NO son recursivos y su costo de ejecución se abarata substancialmente, entre ellos están los algoritmos iterativos o los algoritmos

Page 5: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

5

con la estrategia dividir para conquistar, programación dinámica u alguna otra. Lo importante en todo caso es encontrar la más óptima implementación (usando la estructura de datos más adecuada) para solucionar el problema, en la medida de lo posible, ya que no existe LA o LAS estructuras o algoritmos MAS OPTIMOS. El ejemplo que se muestra No es de los mejores como se puede apreciar y comprobar, pero servirá para mostrar como un problema tiene variadas formas de ser resuelto y con ello se analiza su tiempo de ejecución. Este árbol nos muestra el árbol que representa las llamadas a Fibo(6).

Se puede ver que Fibo(n -1) es llamado solamente una vez, pero Fibo(n -1) es llamado 2-veces, Fibo(n -3) es llamado 3-veces, Fibo(n - 4) es llamado 4-veces, es decir, en el proceso existe una recurrente llamada a cálculos ya realizados. ¿Porque no ahorrar en este sentido, es decir utilizar los datos ya calculados?. De allí entonces, que el calculo de Fibo(n) sea el calculo de Fibo(n -1) y Fibo(n -2), y por ende genere tan mal resultado. Sin embargo, una implementación iterativa fibo_It() mejora considerablemente la performance de Fibo(n), reduciéndolo a O(n), como se puede apreciar.

public static int fibo_It(int n) if (n <= 1) return 1; int ult_Valor = 1; int penult_Valor = 1; int resulta = 1; for (int i = 2;i <= n; i++) resulta = ult_Valor + penult_Valor; penult_Valor = ult_Valor; ult_Valor = resulta; return resulta;

El tiempo total para una función se puede determinar por el tiempo requerido para cada instrucción, multiplicadas por las veces que se ejecutan. Por ejemplo, supongamos que se tienen ciertas declaraciones que ya se sabe que ocupan un tiempo ti, luego el tiempo total se calcula como sigue:

i=0 ocupa un tiempo de t1 while i<n “ t2

i++ “ t3 fin while M “ t4

Page 6: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

6

T(n)= t1+n*t2+n*t3+t4= n(t1+t2)+t3+t4. Sin embargo, ya veremos otra forma más general para estimar el tiempo total a través de operadores asintóticos. 3.- Diseño de Algoritmos, a) Algoritmos voraces o ávidos: seleccionan los elementos más prometedores de un conjunto de

candidatos hasta encontrar una solución. En la mayoría de los casos la solución no es óptima. Ejemplos típicos de algoritmos voraces Algoritmo de Kruskal , Algoritmo de Prim , Algoritmo de Dijkstra, Código Huffman.

b) Algoritmos paralelos: permiten la división de un problema en subproblemas de forma que se puedan ejecutar de forma simultánea en varios procesadores. Cambiando un tanto lo que se pretende en este curso.

c) Algoritmos probabilísticos: algunos de los pasos de este tipo de algoritmos están en función de valores pseudoaleatorios.

d) Algoritmos determinísticos: sus pasos están perfectamente definidos y aportan una solución exacta.

e) Divide y vencerás: dividen el problema en subconjuntos disjuntos obteniendo una solución de cada uno de ellos para después unirlas, logrando así la solución al problema completo.

f) Heurísticas: encuentran soluciones a problemas basándose en un conocimiento anterior (a veces llamado experiencia) de los mismos.

g) Programación dinámica: intenta resolver problemas disminuyendo su coste computacional aumentando el coste espacial.

h) Ramificación y acotación: se basa en la construcción de las soluciones al problema mediante un árbol implícito que se recorre de forma controlada encontrando las mejores soluciones.

i) Vuelta Atrás (o Backtracking): se construye el espacio de soluciones del problema en un árbol que se examina completamente, almacenando las soluciones menos costosas.

4.- Notación, Operadores y cálculo Asintótico. Eficiencia, es la medida del uso de los recursos en función del tamaño de las entradas T(n): Tiempo empleado para una entrada de tamaño n. Sea un problema P, que se puede resolver empleando dos algoritmos distintos: Algoritmo 1: T(n) = 10-4 × 2n s

n 10 20 30 38 T(n) 0.1s 2 m > día 1 año

Algoritmo 2: T(n) = 10-2 × n3 s n 200 1000 T(n) 1 día 1 año

Como pueden ver la mejora en hardware no es la solución, de ahí que sea necesario un análisis asintótico. Por otra parte, la elección de una buena estructura de datos es fundamental para crear un buen algoritmo. Lo que a nosotros nos interesa es el análisis asintótico de algoritmos, que se refiere a estudiar el comportamiento de una función f(n) a medida que n se hace más grande, en donde f: N N representa la complejidad del algoritmo, siendo N el conjunto de los números naturales. En este sentido se tiene que tener especial consideración con la ejecución de determinadas operaciones, tales como multiplicaciones, sumas, comparaciones u otras.

Page 7: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

7

Por ejemplo, que tan a menudo la declaración “x = x + 1;” es realizada, en el siguiente segmento de código?

x = x + 1; // 1-vez

for (i=1; i <= n; i++) x = x + 1; // n-veces for (i=1; i <= n; i++) for (j = 1; j <= n; j++)

x = x + 1; // n2-veces

Operación Elemental, es una operación de un algoritmo cuyo tiempo de ejecución se puede acotar superiormente por una constante. Para nuestro análisis sólo contará el número de operaciones elementales ejecutadas y no el tiempo exacto necesario para cada una de ellas. Algunas operaciones matemáticas no deben ser tratadas como operaciones elementales. Por ejemplo, el tiempo necesario para realizar sumas y productos crece con la longitud de los operando. Sin embargo, en la práctica se consideran elementales siempre que los datos que se usen tengan un tamaño razonable. No obstante, desde el punto de vista teórico, consideraremos las operaciones suma, diferencia, multiplicación, división, módulo, operaciones booleanas, comparativas y asignaciones como elementales y por tanto de costo unitario, a no ser que explícitamente se establezca otra cosa. Una de las funciones más frecuentes a considerar como solución al problema de medir la eficiencia de los algoritmos son:

Operadores asintóticos En este segmento se definen algunos operadores, los cuales nos servirán para expresar el costo asintótico de los algoritmos. El operador O-Notación se puede formalizar a través de la definición: O(g(n)) = f(n), tal que, si existe c, n0, de manera que ∀ n ≥ n0, se cumple que f(n) ≤ cg(n), con f, g: N→ N . Dicho de otro modo f(n)/g(n) está acotado por una constante c, para n suficientemente grande.

Page 8: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

8

La expresión f(n) = O(g(n)) es un abuso de notación, pues en rigor debería escribirse como f(n) ε O(g(n)), interpretándose como que f(n) está acotada por una familia de funciones g(n), que varían según constante.

Otro operador es Ω-Notación que se puede formalizar a través de la definición: Ω(g(n)) = f(n), tal que, existen c, n0, de manera que ∀ n ≥ n0, se cumple que f(n) ≥ cg(n), con f, g: N→ N. Dicho de otro modo f(n)/g(n) no está acotado por una constante c, para n suficientemente grande. La notación f(n) = Ω(g(n)) es un abuso, pues en rigor debería escribirse como f(n) ε Ω(g(n)) y se interpreta como que f(n) no está acotada por una familia de funciones g(n), que varían según constante.

Otro operador es Θ-Notación que se puede formalizar a través de la definición: Θ(g(n)) = f(n), tal que existen c1, c2 y n0 , de manera que ∀ n ≥ n0, se cumple que 0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n), con f, g: N→ N . Dicho de otro modo f(n) está acotado por “arriba” y por “abajo”, para n suficientemente grande.

Con estas consideraciones es posible recurrir a los conceptos más básicos de cálculo elemental para conjeturar, si una función está o no acotada asintóticamente por otra y así concluir si pertenece o no, a alguno de los operadores antes descrito.

Page 9: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

9

Por ejemplo, ¿2n2 = O(n3 )?. Para demostrar lo anterior sin recurrir a la definición, bastaría con calcular, Lím(2n2 /(n3) = 0, cuando n tiende a infinito. Análogamente para saber si ¿n2 = Ω(nlogn)?, bastaría con calcular, Lím(n2 /(nlogn)= infinito, luego n2 = Ω(nlogn). Teorema: Si f y g son funciones asintóticamente positivas y Lím(f(n) /g(n) (para cuando n tiende a infinito)existe y es ya sea +infinito o es una constante positiva(esto es, estrictamente mayor que 0), entonces f pertenece a Ω(g). Teorema: Si f y g son funciones asintóticamente positivas y Lím(f(n) /g(n) (para cuando n tiende a infinito)es 0, entonces f no pertenece a Ω(g). Teorema: Si f y g son funciones asintóticamente positivas y Lím(f(n) /g(n) (para cuando n tiende a infinito)existe y es una constante finita que es estrictamente mayor que 0, entonces f pertenece a Ө(g). Teorema: Si f y g son funciones asintóticamente positivas y Lím(f(n) /g(n) (para cuando n tiende a infinito)existe y es ya sea 0, o +infinito, entonces f no pertenece a Ө(g). Para observar el crecimiento de las funciones más típicas, hemos considerado la siguiente tabla:

Bajo el supuesto “1 paso dura 1 ss 610−=µ ,(*) “, se tiene

(*) un nanosegundo es de 10-9 segundos. Ejemplo: 1.- Veremos el uso de la definición para demostrar que g(n) = 2n + n3 ε O( f(n) = 2n ). Intuitivamente, se ve que g(n)=2n + n3 y f(n) =2n, n3 crece más lentamente que 2n según aumente n, por lo tanto podemos descartar el término de más bajo orden y concluir que g(n) ∈ O(2n). Ahora, para probar formalmente lo anterior, debemos considerar que para constantes

2.- Sea f(n) = 3n2 + 4n + 12, mostrar si es O(n2). Use K=4.

)O(2 2 ,100010y 10242 :cumple se 10 cuando

2

lados) ambosen 2 (restando 2.22 tienese , nn quemostrar para 2c 10

n3

310

3

n3n000

∈+∴

==≥

≤+≥∀==

nn

n

nyn

n

n

n

Page 10: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

10

3n2 + 4n + 12 ≤ 4n2, 4n + 12 ≤ n2, n2 - 4n – 12 ≥ 0, (n + 2)(n - 6) ≥ 0, lo que es cierto, si n ≥ 6 .

Algunas propiedades y formulas de los Operadores Transitividad: f(n) = O(g(n)) y g(n) = O(h(n)) ⇒ f(n) = O(h(n). Idem para Θ y Ω. Refleja : f(n) = O(f(n)). Lo mismo para Θ y Ω. Simetría : f(n) = Θ(g(n)) ssi g(n) = Θ(f(n)). Si T1(n) = O(f(n)) y T2(n) = O(g(n)), entonces (a) T1(n) + T2(n) = max(O(f(n)), O(g(n))), (b) T1(n) * T2(n) = O(f(n)*g(n)). Si T(n) es polinomial de grado k, entonces T(n) = Θ(nk). logkn = O(n) para cualquier constante k. Si f∈O(g) y g∈O(h) ⇒ f∈O(h), Si f∈Ω(g) y g∈Ω(h) ⇒ f∈Ω(h), Si f∈O(g) ⇒ O(f) ⊆ O(g), Si f∈Ω(g) ⇒ Ω(f) ⊆ Ω(g), Si O(f) ⊆ O(g)⇔ f∈O(g) , Si Ω(f) ⊆ Ω(g)⇔ f∈Ω(g), Si O(f) = O(g)⇔ f∈O(g) y g∈O(f) , Si Ω(f) = Ω(g)⇔ f∈Ω(g) y g∈Ω(f), Si O(f) = O(g)⇔Ω(f)=Ω(g) ⇔ θ(f)=θ(g) ⇔ f∈θ(g), O(f+g)=O(max(f,g)), Ω(f+g)=Ω(max(f,g)).Para calcular los órdenes en un programa sólo es necesario hacerlo de la parte con mayor orden. Si limn-->inf f(n)/g(n)∈R+ ⇒ O(f)=O(g), Ω(f)=Ω(g), θ(f)=θ(g), Si limn-->inf f(n)/g(n)=0 ⇒ O(f)⊆O(g), Ω(g) ⊆Ω(f), Si limn-->inf f(n)/g(n)=+∞ ⇒ O(g)⊆O(f), Ω(f)⊆Ω(g) Veamos el mismo problema anterior pero usando esta otra propiedad que f(n) es O(g(n)) si: f(n) = 3n2 + 4n + 12, y se mostrara que f(n) es O(n2). Para alguna constante c se tiene que,

en nuestro caso es O(1)⊂O(log n)⊂O(n1/2) ⊂O(n) ⊂O(n log n) ⊂ O(n log n log n) ⊂O(n2) ⊂O(n3) ⊂O(2n) ⊂O(n!) ⊂O(nn). como se puede ver a continuación.

Page 11: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

11

Debemos recordar que los órdenes de complejidad son medidas asintóticas. Para datos pequeños, y si las constantes multiplicativas son distintas, nos podemos encontrar con situaciones, tales como,

Algunas Formulas útiles.

Page 12: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

12

13)

14)

Page 13: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

13

Algunas cotas importantes a considerar 15)

16)

17)

18)

19)

20)

21)

Page 14: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

14

Ejemplo Mostrar si converge la serie

Se tiene que

la que podemos escindir en bloques

donde el k-ésimo bloque es

por lo tanto si es acotada. Ejemplos: 1.- Determinar un operador O(n) para el número de veces que se ejecuta la instrucción “x = x+1”. 1. for (i = 1; i <= n; i++) 2. for (j = 1; j <= i; j++) 3. x = x + 1; En primer lugar, i toma el valor de 1, y cuando j corre de 1 a 1, la línea 3 se ejecuta una vez. A continuación, i es igual a 2, j corre de 1 a 2, y la línea 3 se ejecuta 2 veces, etc. Así, el número total

de veces que se ejecuta la línea 3 es ∑=

N

ii

1, que corresponde exactamente a n(n+1)/2, de donde se

tiene O(n2).

2.- Calcular el nº de pasos en el cálculo de ∑=

N

i

i1

3 , y expréselo en términos de un operador O(f(n)).

public static int sum(int n)

/* 1 */ int parcSuma; /* 2 */ parcSuma = 0; /* 3 */ for (int i = 1; i <= n; i++) /* 4 */ parcSuma += i * i * i; /* 5 */ returm parcSuma;

que corresponde a O(n), y que coincide con la información asintótica del nº de multiplicaciones que se realizan en este proceso.

Page 15: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

15

5.- Relación entre estructuras de control y operadores asintóticos Para tipos de segmentos del tipo declaraciones o número constante de instrucciones, tal como b = a * 25; c = ( b/2 ) * (a – b); a++; tienen un tiempo constante, tanto caso peor como mejor, de manera que en términos de O, se dice que es O(1).

• if-else(else if, switch, case) Consideremos ahora segmentos de programa que tienen cálculos simples y declaraciones del tipo if-else, tal como if (( a > 0) && (b == 5)) e= e/2; f--; else n = n * (a + b/2 + f); m = n – 1; e = e * 2; ; la regla general para una declaración if: caso peor, es el máximo de las 2 ramas o bifurcación que se generan con el if, en este ejemplo, ambos son constantes de manera que el máximo es una constante, es decir O(1). En general, la condición suele ser de O(1), complejidad a sumar con la peor posible, bien en la rama THEN, o bien en la rama ELSE. En decisiones múltiples (ELSE IF, SWITCH CASE), se tomara la peor de las ramas. Para programas con muchos if-else, la expresión puede ser difícil de entender por la cantidad de bifurcaciones, pero el resultado será siempre constante, en la medida que no existan ciclos.

• Ciclos(while, for, do-while, repeat) Consideremos ahora un programa con un ciclo simple for (i = 0; i < n; i++) e = e + a[i]; if (c < a[i]) then c = a[i]; ; el tiempo para este tipo de programas esta acotado por n-veces el tiempo del caso peor del cuerpo del loop. Ya que el cuerpo del loop tiene caso peor constante, el costo total del programa será acotado por n*K, para alguna constante K, resultando que el programa tenga costo O(n) caso peor.

El mismo razonamiento se extiende para los loops anidados, por ejemplo, for (i=0; i < n; i++) for (j = 0; j < n; j++)

a[i] = a[i] + b[j]; el tiempo caso peor es O(n2), por la siguiente razón. El loop interno tiene n-iteraciones y, cada iteración tiene tiempo constante, de manera que el tiempo caso peor del loop interno es n*K para alguna constante K. Por otra parte el loop externo tiene n- iteraciones y, cada iteración tiene costo

Page 16: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

16

n*K, de manera que el tiempo es n*(n*K), obteniéndose así O(n2), al expresarlo en notación asintótica, pues de otra manera sería que el nº de pasos de esta propuesta es n2*K. En un programa que tenga una combinación de declaraciones if, llamadas a funciones o procedimientos, el tiempo caso peor es analizado usando las siguientes reglas generales. Una sucesión de declaraciones (o bloques) de la forma Grupo1; Grupo2; ..; Grupon; es analizado considerando la suma de los casos peor de los n- grupos. Por lo tanto se procede a analizar cada grupo en forma independiente primeramente, ahora si ese grupo es un if o un loop o llamada a función use las reglas anteriores para acotarlo, análogamente lo hace con el otro grupo, para luego hacer la suma de los grupos de declaraciones, con esto se logra aplicar una propiedad del operador O, es decir si O(f(n) + g(n)) = O(max(f(n), g(n)), en el caso que se considerasen solamente dos grupos de declaraciones. Un loop (for, while, repeat, etc) es analizado por la suma de los tiempos caso peor de los loop’s de iteraciones. Si el loop es anidado, primero se acota el tiempo de ejecución para el loop más interno. Si ahora el número de términos en la suma esta relacionada al tamaño de la entrada, entonces se obtiene un efecto multiplicador.

Ejemplos 1.- Considere el segmento de programa, for (i=1; i < n; i++) x[i]++;

tiene tiempo de ejecución ∑=

N

i

K1

= n*K = O(n).

2.- Otro ejemplo es for (i=1; i <= n; i++) for (j=1; j < i; j++) x[i][j]=1;

que se expresa como ∑∑=

=

i

j

n

iK

11

, que corresponde a ∑=

N

iiK

1= Kn(n+a)/2 = O(n2).

Una llamada a función es analizada independientemente de los tiempos de ejecución de las llamadas a las funciones, además una declaración if-else es tratada tomando el máximo de las dos ramas.

3.- Otro ejemplo típico, ahora con la forma O(n2). for (int i=1; i <= n; i++) a[i] = 0; // O(n) for (int i=1; i <= n; i++) for (int j=1; j <= n; j++) a[i] += i + j; // O(n2) 4.- Calcular el nº de pasos en este segmento de programa

for(i =1; i < n; i++) for(j = i+1; j <= n; j++) for(k =1; k <= j; k++) sentencias constantes

Page 17: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

17

entonces T(n) ∈ O(n3). (Use las formulas para verificar el resultado). 5.- Calcular el nº de pasos en este segmento de programa. Notar que m es una variable arbitraria.

i=1; while(i<=m) j=n; while(j != 0) j=j/2; i++;

Entonces debemos hallar la función que nos defina la cantidad de iteraciones del while interno:

Por lo tanto,

( )

( )

∑ ∑∑

∑ ∑ ∑

∑ ∑

∑ ∑ ∑

=

=

=

=

=

= = =

= +=

= += =

−−−+

++−≤

+

+−+

++−≤

+

−+

+−+≤

−+−+−+≤

++≤

++≤

1

1

1

1

31

1

232321

1

132321

1

133221

1

13

1 121

1

1 1321

1

1 1 1321

22)

2)1()(1(

2)1()

2)1()(1(

2)1(

2)1(

1)1(

)(

n

i

n

i

n

i

n

i

n

i

n

i

n

j

i

j

n

i

n

ij

n

i

n

ij

j

k

ic

ic

icnncnccn

iicicnncnccn

iicnncicncc

cjjincc

jccc

cccnT

( )( )

( )

n) de y m de (depende )log O(m ),(

log

1log n)T(m,

2

2221

1221

nnmT

cnccm

nccm

i

++≤

++≤ ∑=

1log esfunción la entonces 2 +n

Page 18: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

18

En los bucles con contador explícito, podemos distinguir dos casos, que el tamaño N forme parte de los límites o que no. Si el bucle se realiza un número fijo de veces, independiente de N, entonces la repetición sólo introduce una constante multiplicativa que puede absorberse. Ej.- for (int i= 0; i < K; i++) algo_de_O(1) => K*O(1) = O(1) Si el tamaño N aparece como límite de iteraciones, Ej.- for (int i= 0; i < N; i++) algo_de_O(1) => N * O(1) = O(n) Ej.- for (int i= 0; i < N; i++) for (int j= 0; j < N; j++) algo_de_O(1) tendremos N * N * O(1) = O(n2) Ej.- for (int i= 0; i < N; i++) for (int j= 0; j < i; j++) algo_de_O(1) el bucle exterior se realiza N veces, mientras que el interior se realiza 1, 2, 3, ... N veces respectivamente. En total, 1 + 2 + 3 + ... + N = N*(1+N)/2 -> O(n2) A veces aparecen bucles multiplicativos, donde la evolución de la variable de control no es lineal (como en los casos anteriores) Ej.- c= 1; while (c < N) algo_de_O(1) c= 2*c; El valor inicial de "c" es 1, siendo "2k" al cabo de "k" iteraciones. El número de iteraciones es tal que 2k >= N => k= eis (log2 (N)) [el entero inmediato superior] y, por tanto, la complejidad del bucle es O(log n). Ej.- c= N; while (c > 1) algo_de_O(1) c= c / 2; Un razonamiento análogo nos lleva a log2(N) iteraciones y, por tanto, a un orden O(log n) de complejidad. Ej.- for (int i= 0; i < N; i++) c= i; while (c > 0) algo_de_O(1) c= c/2; tenemos un bucle interno de orden O(log n) que se ejecuta N veces, luego el conjunto es de orden O(n log n)

Page 19: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

19

Por último el siguiente ejemplo int x, y, p; p = 0; while ( y != 0 ) if ( (y % 2) == 0 ) x = x + x; y = y / 2; else p = p + x; y = y – 1; La acción característica en este segmento de programa es el cuerpo del bucle, que tiene complejidad constante O(1). Por tanto, la complejidad del bucle será el número de vueltas T

A(n) ∈ O(t(n)). Esto

significa que en cada vuelta el tamaño de los datos se divide por 2 hasta llegar a 1, ¿cuántas vueltas da el bucle?. El tamaño de los datos en las sucesivas iteraciones son: n, n/2, n/4, ... , 1. O equivalentemente n/20, n/21, n/22, ... , n/2k , siendo k el número de veces que se ha ejecutado el bucle, de forma que al final se tendrá, 1 = n/2k , es decir k = log n en base 2. Lo que significa que luego de despejar k en términos de n, se tiene que t(n) ∈ O(log n) entonces T

A(n) ∈ O(log n). ¿

Este razonamiento es válido para cualquier valor de n ?

6.- Medida empírica del tiempo de ejecución de un algoritmo Otra forma más “realista” de medir el tiempo de ejecución del algoritmo es a través de las

herramientas que pone a disposición los lenguajes de programación. Por ejemplo, la medición de un algoritmo de ordenación, aplicado sobre datos de distinto tamaño, en C, para medir el tiempo utilizamos la función clock() que devuelve un valor de tipo clock_t con el número de ciclos de reloj en ese instante.

const int N = 50; clock_t t1, t2; int v[N]; double tiempo; for ( i = 0; i < N; i++ ) v[i] = rand(); t1 = clock(); ordena(v, N); t2 = clock(); tiempo = double(t2-t1)/CLOCKS_PER_SEC;

El problema de este método es que la precisión del reloj del sistema suele estar entorno a los milisegundos y el tiempo a medir es inferior o de ese orden. La solución es repetir la ejecución del procedimiento que pretendemos medir un cierto número de veces hasta que el tiempo medido sea significativo. Como pretendemos tomar medidas para distintos tamaños de datos, repetiremos también el proceso para cada valor de N, y utilizaremos un número de repeticiones distinto dependiendo de dicho valor. Otra forma de medir el tiempo de ejecución es a través de la siguiente experiencia: dejando variables que se encarguen de contabilizar los ciclos o pasadas. Para ilustrar esta idea, he sacado de un libro de calculo la fórmula de evaluar la función tang(x), para tal efecto me encontré que debía

Page 20: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

20

conocer la función sin(x) y cos(x). En ese sentido busque una función definida en forma recursiva tanto para sin(x) como para cos(x), tal como se muestra, para definirme finalmente la función tang(x). Posteriormente construí la implementación y posterior ejecución con el fin de comprobar empíricamente el tiempo que demora en calcular sin(45º), cos(45º), además de calcular el tiempo que usa el método Math( ) y finalmente el nº de veces que el programa llamó a sin(x).La propuesta se hace en Java y el método encargado de medirlo es currentTimeMillis().

public class test_recursividad static int num_llamadas = 0; static double sin (double x) num_llamadas++; if (x < 1e-2) return x - (x * x * x) / 6; else double t = tan(x / 3); return sin(x / 3) * (3 - t * t) / (1 + t * t); // end else // end sin static double cos (double x) double s = sin(x); return Math.sqrt(1 - s * s); // end cos static double tan (double x) return sin(x)/cos(x); // end tan public static void main (String[] args) double test_Angulo = Math.PI / 4; // 45 grados double respuesta; long parteTiempo, diferenciaTiempo; parteTiempo = System.currentTimeMillis(); respuesta = sin(test_Angulo); diferenciaTiempo = System.currentTimeMillis() - parteTiempo; System.out.println("El método sin responde: " + respuesta + " en " + diferenciaTiempo + " milisegundos."); parteTiempo = System.currentTimeMillis(); respuesta = Math.sin(test_Angulo); diferenciaTiempo = System.currentTimeMillis() - parteTiempo; System.out.println("El método Math para el cos responde: " + respuesta + " en " + diferenciaTiempo + " milisegundos."); System.out.println("Finalmente el método recursivo sin fue llamado " + num_llamadas + " veces."); // end main // end test_recursividad

Page 21: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

21

Frente a procesos recursivos veremos otras técnicas para determinar el costo o el operador asociado, las que resumiré en este segmento de programa.

public void metodo1(int n) if (n < 3) System.out.println("*"); else System.out.print("x"); Metodo1(n/3); // end if // end metodo1

Notar que si n ≥ 3, este método imprime (en tiempo constante) y llama en forma recursiva al método con argumento n/3. La relación de recurrencia que se le asocia esta dada por,

T(n) = a, n < 3 , T(n) = T(n/3) + b, n ≥ 3

La que es similar a la solución de la búsqueda binaria (o binary search). La que detallamos

En general,

Supongamos que T(1) = a, escogemos k = log3(n), luego que 3k = n. Entonces,

es decir el método tiene un costo de O(log(n)).

Por otro lado se nos complica un tanto más si queremos determinar la “forma cerrada”, (es decir que dependa de n), la expresión recurrente

T(0) = 7 T(n) = T(n-1) + 3n + 2, para todo n > 0

En todo caso esto se atacará con otros métodos, pero que utiliza las “formulas útiles”, antes señaladas.

7.- Aplicaciones. Suponga que se tiene un AlgoritmoA cuyo costo es Ө(n2), pero al utilizar un AlgoritmoB bajo la metodología DpR (Dividir para Reinar, la que procede a dividir la entrada en dos mitades, realizando nlogn - etapas para dividir el problema y finalmente nlogn - etapas para combinar las soluciones obteniendo la solución al problema). ¿Es esta forma de resolverla más eficiente que la de AlgoritmoA?

Page 22: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

22

Sol: AlgoritmoB se puede plantear de la siguiente forma T(n) = 2T(n/2) + costo de dividir + costo de combinar = 2T(n/2) + nlogn + nlogn = 2T(n/2) + 2nlogn resolviendo esta recurrencia por sustitución, se tiene = 2 (2T(n/4) + 2(n/2)log(n/2)) + 2nlogn = 4 T(n/4) + 2n (log(n) + log(n/2)) = 4 (2T(n/8) + (2(n/4)log(n/4)) + 2n (log(n) + log(n/2)) = 8 T(n/8) + 2n (log(n) + log(n/2) + log(n/4)) en general la r-ésima fila tiene la forma = 2r T(n/(2r)) + 2n (log(n/20) + log(n/21) + ... + log(n/2k-2)) si consideramos n = 2k entonces k = logn (en base 2) y la k-ésima fila será, = 2log(n)T(n/(2log(n)) + 2n (log(n/20 ) + log(n/221 ) + ... + log(n/2(k-2)) = nT(1) + 2n LOGSUM, donde LOGSUM es < klogn. Luego LOGSUM < (logn)2, de esta forma DpR tiene complejidad O(n(logn)2). Ya que O(logn)2 < O(n), se tiene O(n(logn)2) < Ө(n2), entonces en este caso DpR resulto más eficiente que la del AlgoritmoA. EJERCICIOS 1. Considere el siguiente programa, el que considera enteros positivos n como entrada. for i := 1 . . . n do for j := 1 . . . 2i do for k := 1 . . . j do DAA(k) end for end for end for Suponga que el nº de etapas usada por DAA(k) es TDAA(k). (a) Escribir una expresión (que incluya sumas, y TDAA(k) para el nº de etapas T(n), usada por el programa sobre la entrada n. (b) Hallar y probar una cota asintótica expresada en forma cerrada para T(n), asumiendo que TDAA(k) es Θ(k2 ln k). (c) Hallar y probar una cota asintótica expresada en forma cerrada para T(n), asumiendo que TDAA(k) es Θ(ek). 2. Hallar una cota en término de operadores asintóticos, para las siguientes series.

3. Resolver las recurrencias

Page 23: Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.dns.uls.cl/~ej/web_daa_2011/Lect_daa_2011/Cap_2_daa09_Dis_alg_sinRES.pdf · Relación entre estructuras de control y operadores

Diseño y Análisis de Algoritmos. Dr. Eric Jeltsch F.

_____________________________________________________________________________Escuela de Ingeniería en Computación, Universidad de La Serena.

23