410 likes | 596 Views
ESTRUCTURA DE DATOS: Análisis de algoritmos. ------------------------------------------------- Mitchell Paulo Blancas Núñez . Estudiante de Informática de la UNT Octavo ciclo -------------------------------------------------. Análisis de Algoritmos.
E N D
ESTRUCTURA DE DATOS: Análisis de algoritmos ------------------------------------------------- Mitchell Paulo Blancas Núñez . Estudiante de Informática de la UNT Octavo ciclo -------------------------------------------------
Análisis de Algoritmos • El análisis de algoritmos estima el consumo de recursos de un algoritmo. • Esto nos permite comparar los costos relativos de dos o más algoritmos para resolver el mismo problema. • El concepto de razón de crecimiento, es la razón a la cual el costo de un algoritmo crece conforme el tamaño de la entrada crece.
Análisis de Algoritmos • El concepto de razón de crecimiento es extremadamente importante. Nos permite comparar el tiempo de ejecución de dos algoritmos sin realmente escribir dos programas y ejecutarlas en la misma máquina. • Una razón de crecimiento de cn se le llama a menudo razón de crecimiento lineal. • Si la razón de crecimiento tiene el factor n2, se dice que tiene una razón de crecimiento cuadrático. • Si el tiempo es del orden 2n se dice que tiene una razón de crecimiento exponencial.
Mejor, peor y caso promedio • Para algunos algoritmos, diferentes entradas (inputs) para un tamaño dado pueden requerir diferentes cantidades de tiempo. • ¿Cuál es la ventaja de analizar cada caso?. Si examinamos el peor de los casos, sabemos que al menos el algoritmo se desempeñara de esa forma. • En cambio, cuando un algoritmo se ejecuta muchas veces en muchos tipos de entrada, estamos interesados en el comportamiento promedio o típico. Desafortunadamente, esto supone que sabemos cómo están distribuidos los datos.
Mejor, peor y caso promedio • Si conocemos la distribución de los datos, podemos sacar provecho de esto, para un mejor análisis y diseño del algoritmo. Por otra parte, sino conocemos la distribución, entonces lo mejor seria considerar el peor de los casos.
Análisis Asintótico Función O. • Se dice que T(n) es O( f(n) ) , si existen dos constantes positivas c y n0 tales que: |T(n)| <= c|f(n)| con n>=n0
Análisis Asintótico Función . • La cota inferior de un algoritmo, denotada por el símbolo , pronunciado “Omega”, tiene la siguiente definición: T(n) está en el conjunto (g(n)), si existen dos constantes positivas c y n0 tales que: |T(n)| c|g(n)| para todo n > n0.
Análisis Asintótico Función . • Una mejor definición para acotar funciones, es la función que define simultáneamente cotas superior e inferior para T(n). • Se dice que T(n) es ( f(n) ) , si existen c1, c2 y n0 tales que: c1 f(n) <=T(n) <= c2 f(n) con n>=n0 • La definición de encuentra una f(n) que acota tipo sándwich a la función T(n).
Costo unitario. • Aplicando la definición puede comprobarse que las funciones constantes tienen complejidad O(1). • Ejemplo. Sea T(n) = 5. • Se puede escribir: c1*1 <= 5 <= c2*1 con n>=n0 • Y es simple encontrar: c1 = 4, c2 = 6 y n0 cualquiera. • Es decir f(n) =1. Con lo cual, se puede afirmar que : T(n) =5 es O(1) para todo n. • El tiempo de ejecución de un algoritmo que no depende significativamente del tamaño de la entrada es de complejidad O(1).
Costo unitario. • Una de las mayores simplificaciones para cálculos de complejidad es considerar que las acciones primitivas del lenguaje son de costo unitario. • Por ejemplo es usual considerar que el cálculo de una expresión, la asignación, son de complejidad O(1). Se cuentan aparte las comparaciones, y los movimientos o copias de datos.
Regla de concatenación de acciones. Regla de sumas: • Se realiza la acción A, como la secuencia dos acciones A1 y A2 de complejidades temporales T1(n) y T2(n) respectivamente. • Teorema de sumas. • Si T1(n) es O(f(n)) y T2(n) es O(g(n)) entonces: A es de complejidad O( max( f(n), g(n) ).
Regla de concatenación de acciones. Regla de sumas: • Demostración: Por definición: T1(n) <= c1 f(n) para n >= n1 T2(n) <= c2 g(n) para n >= n2 Sea n0 = max(n1, n2) Para n>=n0 se tiene: T(n) = T1(n) + T2(n) <= c1 f(n) + c2 g(n) (1)
Regla de concatenación de acciones. Regla de sumas: • Caso a) Seaf(n) > g(n) Entonces el lado derecho de la relación (1): c1 f(n) + c2 g(n) = (c1 +c2)f(n) - c2( f(n) - g(n) ) • Y como c2( f(n) - g(n) ) es positivo, se puede escribir: c1 f(n) + c2 g(n) < = (c1 +c2)f(n)
Regla de sumas: • Caso b) Seaf(n) < g(n) Entonces el lado derecho de la relación (1): c1 f(n) + c2 g(n) = (c1 +c2)g(n) – c1( g(n) - f(n) ) • Y como c1( g(n) - f(n) ) es positivo, se puede escribir: c1 f(n) + c2 g(n) < = (c1 +c2)g(n) • De los dos resultados anteriores, se obtiene la desigualdad: T(n) = T1(n) + T2(n) <= (c1 +c2) max(f(n), g(n)) para n0 = max(n1, n2). • Aplicando la definición, de la función O, se reconoce que: T(n) = O( max(f(n), g(n) )
Regla de sumas: Corolario. • Si se conoce que f > g, entonces: O( f + g ) es O(f). Ejemplo: O(n2 + n ) = O (n2) para valores de n tales que n2 > n.
Regla de productos. • Si T1(n) es O(f(n)) y T2(n) es O(g(n)) entonces: T1(n)T2(n) es O( f(n) g(n) ). • Demostración. Por definición: T1(n) <= c1 f(n) para n >= n1 T2(n) <= c2 g(n) para n >= n2 • Sea n0 = max(n1, n2) Para n>=n0 se tiene: T(n) = T1(n) T2(n) <= c1c2 f(n)g(n) Con c=c1c2 y aplicando la definición de la función O(n) se logra que T(n) es O( f(n) g(n) ) para n>=n0.
Regla de productos. • Ejemplos: O( 3 n2 ) = O (n2) ya que 3 es O(1), y n2 es O(n2). • La regla del producto también puede aplicarse en: n*O( n ) = O (n2) • Si c es una constante y n el tamaño de la entrada: O(c) = c*O(1) = O(1) O(cn) = c*O(n) = O(n)
Regla de alternativa. • if (c) a1; else a2; • En cálculos de peor caso se toma la complejidad de la acción de mayor orden. Luego se considera la regla de sumas para el cálculo de la condición y la acción. • Considerando de costo unitario el cálculo de la condición, la Figura siguiente muestra la complejidad de la sentencia if.
Regla de iteración. for ( i=0; i< n; i++) a; • Por regla de sumas se tiene n veces la complejidad temporal de la acción a. • Si la acción del bloque a es O(1) entonces el for es de complejidad n*O(1) = O(n) • La siguiente Figura, considera costos unitarios para la inicialización, reinicio, y cálculo de la condición; la complejidad del bloque es O(f(n)); el número de veces es de complejidad O(g(n)).
Evaluando la complejidad en función del tiempo. • Si T(n) refleja el número de instrucciones de costo unitario que deben realizarse para resolver para n entradas, puede tenerse una medida en unidades de tiempo conociendo el valor aproximado de la duración de una instrucción. • Si O(1) es equivalente a 1 [μseg], se puede construir la siguiente tabla, en cada columna se tiene una complejidad temporal diferente:
Costo Temporal Usando teoremas sobre comparación de funciones, se tiene que: O(3 n2 + 7n) = O(n2). La tabla muestra que las complejidades de los dos algoritmos cuadráticos son comparables en el orden de magnitud.
Calcular la complejidad del segmento que obtiene el mínimo elemento de un arreglo. min = A[0]; for(i=1; i<n; i++) if(A[i] < min) min = A[i]; • Si la comparación y la asignación son de costo O(1), entonces el if, en peor caso, es de costo O(1) + O(1) = O(1). • El for se realiza (n-1) veces, su costo es: (n-1) O(1) = O(n-1) = O(n). • La concatenación de la asignación con el for es de costo: O(1) + O(n) = O(n) • Finalmente el segmento es de orden de complejidad O(n).
Comparación de complejidad entre dos algoritmos. • Ejercicio: Comparar dos algoritmos para calcular la suma de los primeros n enteros. • Algoritmo 1. suma= 0; for(i=1; i<=n; i++) suma+=i; • Algoritmo 2. suma = n*(n+1)/2;
Comparación de complejidad entre dos algoritmos. Algoritmo 1. La primera asignación a la variable suma es O(1). • El for realiza una vez la asignación inicial y n veces: test de condición, suma, e incremento de variable de control; más un test de condición con el que se termina el for. • Para el for, entonces, se tiene: O(1) + n*(O(1)+O(1) +O(1)) + O(1) = O(n) • El total es: O(1)+O(n) = O(n). Algoritmo 2. • Costo de la suma, más costo de la multiplicación, más costo de la división. Es decir: O(1)+O(1)+O(1), lo cual resulta O(1). Por lo tanto conviene emplear el algoritmo 2.
Búsqueda en arreglos. • Un problema básico es buscar si un valor está presente en una de las componentes de un arreglo. • Con las siguientes definiciones: typedefint Tipo; /* tipo de item del arreglo */ typedefintIndice; /* tipo del índice */ #define noencontrado -1 #define verdadero 1 #define MaxEntradas 10 Tipo Arreglo[MaxEntradas]; //Define el arreglo en donde se busca
Búsqueda secuencial. • La búsqueda secuencial compara la clave con cada una de las componentes. La primera vez que encuentre un elemento del arreglo igual al valor buscado se detiene el proceso. No encuentra claves repetidas y no se requiere que el arreglo esté ordenado. • Si lo recorre en su totalidad, cuidando de no exceder los rangos del arreglo, y no lo encuentra debe indicarlo con algún valor específico de retorno. En el diseño se considera retornar el índice de la componente que cumple el criterio de búsqueda, se decide entonces que un retorno con valor -1 (ya que no es un índice válido), indicará que el valor buscado no fue hallado.
Búsqueda secuencial. IndiceBusquedaSecuencial(Tipo A[], IndiceInf, IndiceSup, Tipo Clave) { Indice i; for(i = Inf; i<=Sup; i++) if (A[i] == Clave) return(i); return (noencontrado) ; }
Búsqueda secuencial. • La evaluación de la condición del if es O(1), también el retorno es O(1). El bloque que se repite es entonces O(1). • La iniciación del for es O(1). El test de la condición del for es O(1), también el incremento de i es O(1). El bloque se repite: (Sup-Inf +1) veces en el peor caso. • La complejidad es: O(1) + (Sup-Inf +1)*(O(1) +(O(1) + O(1)) +O(1) ) • Simplificando: (Sup-Inf+1) O(1) = O(Sup-Inf+1) • La entrada, en este caso, es el número de componentes en las que se busca. • Si n= Sup-Inf+1, se tiene finalmente: T(n) = O(n).
Búsqueda binaria (BinarySearch) • Se requiere tener un arreglo ordenado en forma ascendente. El algoritmo está basado en ubicar, mediante la variable auxiliar M, la mitad del arreglo aproximadamente. Entonces o se lo encuentra justo en el medio; o en la mitad con índices menores que M si el valor buscado es menor que el de la componente ubicada en la mitad; o en la mitad con índices mayores que M si el valor buscado es mayor. El costo de encontrar la mitad es de costo constante. El ajustar los índices también es de costo constante, corresponde a los dos ifthenelse anidados. Si se tienen n componentes, la complejidad puede describir según: T(n) = T(n/2) +c
Búsqueda binaria (BinarySearch) • El costo, en un arreglo con una componente, es constante; es decir T(1) = O(1). La solución de esta ecuación de recurrencia es: T(n) = O(log(n)). • A continuación se muestra la implementación del algoritmo:
Búsqueda binaria (BinarySearch) intBusquedaBinaria(Tipo A[], IndiceInf, IndiceSup, Tipo Clave){ Indice M; while (verdadero){ M = (Inf + Sup)/2; if (Clave < A[M]) Sup = M - 1; else if (Clave > A[M]) Inf = M + 1; else return M; if (Inf > Sup) return (noencontrado) ; } }
Análisis de complejidad y mejor, medio y peor caso con un algoritmo de ordenación Algoritmo de Selección: Descripción. • El algoritmo de ordenación por el método de selección directa es un algoritmo relativamente sencillo y uno de los más fáciles de recordar e implementar. • Se basa en realizar varias pasadas, intentando encontrar en cada una de ellas el elemento que según el criterio de ordenación es mínimo y colocándolo posteriormente en su sitio. • A efectos prácticos, no suele dar resultados buenos si se compara con otros métodos de ordenación. Realiza una enorme cantidad de comparaciones, pero en contrapartida, muy pocos intercambios. Eso hace que su utilización se restrinja en general a dos situaciones: o bien necesitamos un algoritmo sencillito para ordenar unos pocos datos y cogemos éste mismo que no está mal y es facil de recordar, o bien tenemos una situación en la cual escribir en el array es mucho más gravoso que leer, como puede ser un escenario en el que intervengan determinados dispositivos de almacenamiento o memorias tipo flash, eeprom, etc. para el soporte de los datos. • Este algoritmo se basa en hacer comparaciones, así que para que realice su trabajo de ordenación son imprescindibles dos cosas: un array o estructura similar de elementos comparables y un criterio claro de comparación, tal que dados dos elementos nos diga si están en orden o no.
Algoritmo de Selección: Funcionamiento: 1.- Seleccionar el elemento menor del arreglo de n elementos. 2.- Intercambiar dicho elemento con el primero. 3.- Repetir estas operaciones con los n-1 elementos restantes, seleccionando el segundo elemento; continuar con los n-2 elementos restantes hasta que solo quede el mayor.
Algoritmo de Selección: Un ejemplo aclarara todo: Sea el arreglo: El método comienza buscando el número más pequeño: Entonces la lista nueva será:
Algoritmo de Selección: A continuación se busca el siguiente numero mas pequeño, 640, y se realizan las operaciones 1 y 2. La nueva lista seria: Si se siguen realizando las iteraciones o recursiones y se encontraran las siguientes líneas:
Código Selección: Voidseleccion(int A[],int n){ intmenor,posmin,aux; for(inti=0;i<n-1;i++){ menor=A[i]; posmin=i; for(int j=i+1;j<n;j++){ if(A[j]<menor){ menor=A[j]; posmin=j; } } aux=A[i]; A[i]=A[posmin]; A[posmin]=aux; } }
Complejidad Selección: Peor caso Caso medio Mejor caso