3004597 – Estructura de Datos – Modulo 12 Universidad Nacional de Colombia – Sede Medellín MODULO 12 OBJETIVOS Por medio de este módulo se pretende: - Afiance sus conocimientos acerca de la medida del orden de los algoritmos. - Aplique métodos prácticos que le permitan calcular el orden de un algoritmo dado y aprenda a interpretar los resultados. - Compare la eficiencia de diferentes algoritmos que llevan a cabo la misma tarea por medio del cálculo del orden. CONTENIDO 1. Métodos de análisis de algoritmos 1.1 Reglas teóricas 1.2 Reglas prácticas 1.3 Barómetro 1.3.1 Selección de barómetro A Referencias B Taller 02-2003 1 3004597 – Estructura de Datos – Modulo 12 1 Universidad Nacional de Colombia – Sede Medellín MÉTODOS DE ANÁLISIS DE ALGORITMOS Realmente no existe una fórmula siempre aplicable para analizar la eficiencia de un algoritmo. Básicamente se trata de una cuestión de buen juicio, intuición y experiencia. A menudo un primer análisis da origen a una función de apariencia complicada, que envuelve sumatorias y recurrencias y el siguiente paso es simplificar esta expresión buscando una función que represente el orden del algoritmo de la manera más sencilla posible. A continuación se presentan algunos ejemplos prácticos. Es importante recordar que N representa el tamaño del programa y su principal característica es que varía en cada ejecución, por ejemplo, en un programa de ordenamiento de un arreglo, N sería el número de elementos del arreglo. 1.1 REGLAS TEÓRICAS A continuación se presentan varias reglas que son muy útiles para efectuar cálculos de órdenes de algoritmos: 1. Sea k una constante, si f es de orden O(g) entonces k*f es de orden O(g) 2. Si f1 es de orden O(h1), f2 es de orden O(h2) y fn es de orden O(hn), entonces f1 + f2 + …+ fn es de orden O( max(h1,h2, … , hn) ). 3. Si f es de orden O(h1) y g es de orden O(h2) entonces f*g es de orden O(h1*h2) 4. Sean los reales 0<a<b entonces O(na) es subconjunto (es decir, pertenece a) de O(nb) 5. Sea P(n) un polinomio de grado k entonces P(n) es de orden O(nk) 6. Sean los reales a, b > 1 entonces O(loga) = O(logb) La regla 6 permite olvidar la base en la que se calculan los algoritmos en expresiones de complejidad. La combinación de las reglas 1 y 5 es probablemente la más usada, permitiendo ignorar todos los componentes de un polinomio, menos su grado. Por último, la regla 2 es básica para analizar el concepto de secuencia en un programa: la composición secuencial de varios trozos de programa es de orden de complejidad igual a la suma de los órdenes de sus partes. 1.2 REGLAS PRÁCTICAS Las siguientes reglas son útiles al momento de analizar el orden un algoritmo, se basan en las reglas teóricas dadas en la sección anterior, pero ahora se muestran aplicadas a programación: Sentencias sencillas: Se conocen como sentencias sencillas a las sentencias de asignación, entrada/salida, cálculos aritméticos simples y en general toda instrucción básica que tome un tiempo de ejecución constante y que no trabaje sobre variables estructuradas cuyo tamaño esté relacionado con el tamaño N del problema. Todas las sentencias sencillas tienen orden de complejidad O(1) (conocido como orden constante). 02-2003 2 3004597 – Estructura de Datos – Modulo 12 Universidad Nacional de Colombia – Sede Medellín Secuencias: El orden de una secuencia de instrucciones en un programa, es la suma de los órdenes de las instrucciones que la componen. Decisión (IF … ELSE IF … ELSE, SWITCH … CASE): El orden de un bloque de decisiones es el orden del peor caso posible. (Es decir, se calcula el orden de cada bloque if, else if, else y el mayor de estos se establece como orden de todo el bloque de decisión). (Este es un ejemplo del criterio de peor caso). Bucles (WHILE, FOR) El orden de un bucle iterativo es igual al producto del orden de la función que indica el número de iteraciones que ejecutará el bucle según el tamaño del problema y del orden de las operaciones internas en el bucle. Este producto de órdenes es, a su vez, igual al orden del producto de la función que expresa el número de iteraciones y las que representan a las operaciones internas al bucle. Si el bucle se ejecuta un número fijo de veces, independiente de N, entonces la repetición sólo introduce una constante multiplicativa que puede absorberse. Llamadas a procedimientos (Funciones) La complejidad de llamar a un procedimiento (función) viene dada por la complejidad del contenido del procedimiento. El coste de llamar al procedimiento es una constante que podemos obviar inmediatamente de nuestro análisis de orden. El cálculo de la complejidad asociada a un procedimiento puede complicarse notablemente si se trata de procedimientos recursivos, en ocasiones se requiere utilizar técnicas propias de la matemática discreta y del cálculo de series. Ejemplo 1.1: Orden de un bucle Calcular el orden de la siguiente sección de programa: int c=N; while( c>1 ) { … sentencias de orden 1 … c=c/2; } El valor inicial de c es N, siendo igual a N/2k después de k iteraciones, el bucle se ejecutará un número de veces k dado por la ecuación N/2k = 1, así que para obtener el número de veces que se ejecutará el interior del ciclo para un tamaño de programa N dado, basta despejar k de esta ecuación: N = 2k sacando log2 a ambos lados: k = log2(N) así que de acuerdo a las reglas teóricas, se concluye que la sección de programa es de orden O(log(n)) 02-2003 3 3004597 – Estructura de Datos – Modulo 12 Universidad Nacional de Colombia – Sede Medellín Ejemplo 1.2: Aplicación de la regla del producto El orden de un proceso iterativo (ciclo) es igual al producto del orden de la función que indica el número de iteraciones en función del tamaño del problema y del orden de la operación interna en el bucle. El orden de este producto es, a su vez, igual al orden del producto de la función que expresa el número de iteraciones multiplicado por el orden de las operaciones internas al bucle. En este ejemplo se tiene un algoritmo para ordenar cada una de las filas de una matriz cuadrada de n x n elementos. El bucle iterativo es: for(int i=0; i<n; n++) { Ordenar fila i; } O su equivalente en la forma básica "mientras": int i=0; while( i<=n ) { Ordenar fila i; i++; } Es inmediato que el número de repeticiones del ciclo es n, que es una función de orden lineal O(n). Supóngase que para ordenar la fila se utiliza un algoritmo de tipo cuadrático. Entonces el orden de complejidad de todo el algoritmo será cúbico, ya que, por la regla del producto: O(n).O(n2) = O(n.n2) = O(n3) 1.3 BARÓMETRO En el ejemplo anterior se dieron todos los detalles del análisis. Los detalles como la inicialización de los ciclos (que se consideraron mediante una constante) raramente se consideran de manera explícita. (Sin embargo, en el siguiente ejemplo se muestra que no siempre pueden hacerse ignorarse las inicializaciones y control de los ciclos). Por lo general es suficiente escoger alguna instrucción representativa (que por regla general es la instrucción simple que más se repite en el algoritmo) y contar cuantas veces es ejecutada, de la ecuación resultante de esta operación se calcula el orden del algoritmo. A esta instrucción representativa se le conoce como barómetro, la selección del barómetro adecuado en el algoritmo es una de las fases más importantes del análisis de orden. Por supuesto, se considera que el tiempo de ejecución de la instrucción seleccionada como barómetro puede delimitarse con una constante. En el ejemplo anterior, un buen barómetro sería el if dentro del ciclo interior, en este caso, el barómetro se ejecuta n(n-1)/2 veces cuando se organiza un vector de n elementos. 1.3.1 02-2003 SELECCIÓN DEL BARÓMETRO 4 3004597 – Estructura de Datos – Modulo 12 Universidad Nacional de Colombia – Sede Medellín Cuando un algoritmo incluye varios ciclos anidados, como es el caso del sort de selección, cualquier instrucción del ciclo interior puede escogerse, usualmente, como barómetro. Sin embargo, existen casos donde es necesario tener en cuenta el control implícito de los ciclos. Consideremos el siguiente algoritmo: T es un arreglo de n enteros tales que 0<=T[i]<=i para todo i<n. Sea s la suma de los elementos de T. ¿Cuánto tiempo toma la ejecución del algoritmo? void ejemplo(int *T, int n) { int k=0; for(int i=0; i<n; i++) { for(int j=0; j<T[i]; j++) { k=k+T[j]; } } } contador 1 n+1 s+n s s n ----------------3n + 3s + 2 Orden: O(n+s) O(Max(n,s)) Para cada valor de i la instrucción “k=k+T[j]” se ejecuta T[i] veces. El número total de veces que es ejecutada “k=k+T[j]” es por tanto T[i] = s veces donde la sumatoria va de i=0 a i=n-1. Si esta instrucción fuera seleccionada como barómetro, se concluiría que el algoritmo toma un tiempo de ejecución de orden s. Un ejemplo simple es suficiente para convencernos de que este no es el caso. Supongamos que T[i] =1 cuando i tiene raíz cuadrada exacta y T[i] = 0 de lo contrario. En este caso s=sqrt(n) como fácilmente puede comprobarse y de acuerdo al barómetro anterior concluiríamos que el algoritmo es de orden O(sqrt(n)), Sin embargo, claramente el algoritmo toma un tiempo de orden O(n) ya que cada elemento de T se procesa al menos 1 vez (en la condición del ciclo for). Esta circunstancia ocurre debido a que la instrucción “k=k+T[j]” no siempre se ejecuta en el ciclo más interno, pero su condición sí. El análisis detallado del algoritmo es como sigue: Sea a, el tiempo necesario para ejecutar una vuelta del ciclo interior, incluyendo el tiempo consumido en el control del ciclo. Para ejecutar el ciclo interior completamente para un valor de i dado se requiere un tiempo b+aT[i], donde b es una constante que representa el tiempo de inicialización del ciclo. Este tiempo no es cero cuando T[i] = 0. A continuación, el tiempo necesario para ejecutar una vuelta del ciclo exterior es c+b+aT[i], donde c es una nueva constante. Finalmente, el algoritmo completo toma un tiempo d+[c+b+aT[i]], donde la sumatoria va desde i=0 hasta i=n-1. Cuando se simplifica, esta expresión lleva a (c+b)n+as+d. Por tanto, el tiempo depende de dos parámetros independientes n y s y no puede ser expresado como una función de sólo una de estas variables. Esta situación es típica en algoritmos de manejo de grafos, donde el tiempo de ejecución depende tanto del número de nodos como del número de enlaces. Volviendo a nuestro problema, podemos expresar el orden de este algoritmo de dos maneras: O(n+s) ó O(max(n, s)). 02-2003 5 3004597 – Estructura de Datos – Modulo 12 Universidad Nacional de Colombia – Sede Medellín ANEXOS A REFERENCIAS http://web.jet.es/jqc/progii.html. Pagina Web del curso Programación II de Ingeniería Informática de la UNED. Jeronimo Quesada 1998. B TALLER Crear un algoritmo que calcule el n-ésimo término de la serie de Fibonacci y calcular su orden. C CREDITOS Editor: PhD. Fernando Arango Colaboradores: Ing. Edwin Hincapie, Ms.C Francisco Moreno, Santiago Londoño, Alberto Jiménez, Juan Carlos Hernández, Carlos Andrés Giraldo, Diego Figueroa. 02-2003 6