Facultad de Ingeniería de la Universidad de Buenos Aires Teoría de algoritmos – Curso Echevarría Trabajo Práctico 0 Elaborado Por: Omar Alejandro Flores Cabrera Padrón: 112186 Índice Índice Introducción..........................................................................................................................................3 Contexto del problema.....................................................................................................................3 Metodología..........................................................................................................................................4 Análisis de Complejidad..................................................................................................................4 Análisis de optimización..................................................................................................................5 Refactorización................................................................................................................................5 Supuestos....................................................................................................................................6 Análisis de Complejidad.............................................................................................................6 Diseño.........................................................................................................................................6 Ejemplo de seguimiento..............................................................................................................7 Tiempos de ejecución..................................................................................................................7 Resultados.............................................................................................................................................9 Referencias.........................................................................................................................................10 2 Introducción En este trabajo práctico, nos enfocaremos en la refactorización de un código existente que busca cuartetos de números primos dentro de una misma decena. El objetivo principal es introducir mejoras que optimicen su rendimiento sin alterar la lógica original del programa. Estas mejoras incluirán tanto optimizaciones específicas para el problema planteado como técnicas generales para la búsqueda eficiente de números primos. Como resultado, se espera reducir significativamente el tiempo de ejecución y, en lo posible, la complejidad algorítmica del programa. Contexto del problema Un cuarteto de números primos es un conjunto de cuatro números primos que se encuentran dentro de la misma decena. Por ejemplo, los números 11, 13, 17 y 19 forman un cuarteto, al igual que 101, 103, 107 y 109. El programa original es capaz de encontrar todos los cuartetos de números primos menores a 1,000,000; sin embargo, su implementación actual no es eficiente, ya que tarda aproximadamente 31 horas en completar la ejecución. 3 Metodología A continuación vemos el código fuente original al cual le haremos un análisis de complejidad algorítmica y evaluaremos las mejoras a implementar. Análisis de Complejidad La complejidad algorítmica es de O(n2) siendo n la diferencia entre el fin y el inicio del rango donde queremos encontrar los cuartetos primos, que en este caso son 1000000 y 11 respectivamente. Tenemos un bucle for donde iteramos n veces y dentro hacemos uso de la función esprimo en la cual también iteramos desde 2 hasta n – 1, que usando la notación big O esto se traduce a O(n), entonces tenemos una función de complejidad lineal corriendo dentro de un bucle que se ejecuta n veces, dando por resultado que el programa en total tenga una complejidad algorítmica cuadrática. 4 Análisis de optimización Analizando el código podemos observar lo siguiente: • El bucle for principal innecesariamente avanza de a 1 número, cuando ya sabemos de antemano que los números pares excepto por el 2 no son primos, en cambio podríamos ir de 2 en 2. • En caso de que un número no sea primo el programa continuará con el siguiente número inmediato dentro de la misma decena, esto se podría mejorar debido a que como solo queremos los primos que estén en un cuarteto dentro de una decena, en caso de que falle uno de los candidatos a primo en la decena descartamos los números restantes de la misma y saltamos a la siguiente decena, esto se podría lograr usando un else. • En línea con el punto anterior, cuando la cláusula if que verifica que el cuarteto es primo sea verdadera, podemos saltar directamente a la siguiente decena. • Se podría agregar una condición de corte en while de la función esprimo para que corte la ejecución una vez haya encontrado un divisor. • La manera de verificar si el n es primo se puede optimizar: En vez de buscar secuencialmente entre todos los números anteriores a n, podemos aplicar un test de primalidad más sencillo: buscar entre todos los números primos anteriores que sean menores a √ n reduciendo así drásticamente la cantidad de posibles divisores de n. Es importante recalcar que para esto necesitaremos guardar en una lista todos los números primos que vayamos encontrando y adicionalmente, como estamos buscando los primos a partir del 11, debemos incluir en el valor inicial de la lista los primos anteriores a 11, para hacer esto podríamos hacer uso de un algoritmo eficiente como la criba de Eratóstenes pero debido a que únicamente son 4 primos que existen antes del 11 no es necesario complicarnos y simplemente colocaremos los valores iniciales a mano. Refactorización Aplicando las optimizaciones listadas anteriormente podemos llegar a la siguiente aproximación: 5 Supuestos Dado que en la primera decena de números hay un cuarteto que no encaja con el patrón del resto de cuartetos, me vi forzado a hardcodear estos valores en la lista primos. Es importante que el bucle principal se mueva de 2 en 2 porque aunque sabemos que si por ejemplo en 21 falla ya no se cumpliría la condición de cuarteto, de todas maneras necesitamos avanzar con 23 porque lo necesitamos tener en la lista de números primos. Análisis de Complejidad Hemos agregado una clausula else solo son operaciones aritméticas y de asignación constantes, en la función esprimo hemos cambiado el criterio para encontrar los primos reduciendo las iteraciones a solamente √ n por lo que tendríamos una complejidad de O(√ n) y sumado a que esto se ejecuta dentro de un bucle for en el cuál si bien le hemos reducido la cantidad de veces que se ejecuta a la mitad(por ir recorriendo de 2 en 2) sigue siendo lineal, por lo que la nueva complejidad algorítmica es O(n√ n). Diseño A continuación un pseudocódigo con la lógica esencial: Como podemos observar la única estructura de datos utilizada es la lista en donde guardamos los primos y la que recorremos para verificar divisores. 6 Ejemplo de seguimiento Vamos a realizar un seguimiento con los números del 1 al 30, inicialmente la lista con los números primos tiene le primer cuarteto de primos 2,3,5,7 que pertenecen a la primera decena. Ahora empezaremos a iterar desde el 11 y entramos en el if acá hacemos las siguientes validaciones a los cuatro posibles candidatos a primos de la decena que son los números impares sin incluir al 5 que den antemano sabemos que todo numero que termine en 5 no es primo: • ¿El 11 es primo?: Como no es divisible entre ninguno de los primos anteriores concluimos que sí lo es y lo añadimos a la lista de primos quedando así la lista: [2,3,5,7,11] • Y hacemos lo mismo para el 13,17,19 los cuales no resultan primos y terminamos con los siguientes primos: [2,3,5,7,11] La clausula if termina siendo verdadera, se imprime el cuarteto de números y nos movemos 8 lugares (desde el 11 hasta el 19) para que termine esta iteración y en la siguiente empecemos desde 21. Ya en la siguiente iteración el numero actual es el 21, vamos al if, verificamos si es primo y efectivamente lo es porque es divisible entre 3, por lo que la condición del if termina siendo falsa y pasamos al else, acá sacamos el ultimo dígito de nuestro número para saber cuánto desplazarnos para llegar a la siguiente decena y verificamos si es 1,3,7 que son los posibles valores en donde el if pudo terminar siendo falso. En este caso obviamente es 1, hacemos la suma de 21 más 9 menos 1 (9 - ultimo_digito). Tiempos de ejecución A continuación mostramos un gráfico con diferentes pruebas, en el eje de las ordenadas tenemos el tiempo, y en el eje de las abscisas el limite del rango. 7 8 Resultados Como ya vimos, hay una diferencia abismal entre los resultados de tiempo arrojados por el algoritmo original y el algoritmo refactorizado, esto se debe a las pequeñas optimizaciones que hicimos y al cambio de criterio al confirmar los numero primos con lo cual pudimos mejorar notablemente la complejidad algorítmica del código, de una complejidad de O(n2) a (n√ n) lo cual es una gran diferencia especialmente con grandes cantidades. 9 Referencias Número Primo. (25 de febrero del 2025). En Wikipedia. https://es.wikipedia.org/wiki/Número_primo 10
0
Puede agregar este documento a su colección de estudio (s)
Iniciar sesión Disponible sólo para usuarios autorizadosPuede agregar este documento a su lista guardada
Iniciar sesión Disponible sólo para usuarios autorizados(Para quejas, use otra forma )