Análisis asintótico: algoritmos recursivos e iterativos
Författare
Frank S. Franco H.
Last Updated
för 10 år sedan
Licens
LaTeX Project Public License 1.3c
Sammanfattning
Taller sobre análisis de algoritmos.
\documentclass[a4paper]{article}
\usepackage[spanish]{babel}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{graphicx}
\usepackage[colorinlistoftodos]{todonotes}
\usepackage{listings}
\title{Análisis asintótico: algoritmos recursivos e iterativos}
\author{Frank Sebastián Franco Hernández}
\date{\today}
\begin{document}
\maketitle
\section{Análisis de tres algoritmos de ordenamiento}
\subsection{Algoritmo BubbleSort}
Este algoritmo funciona como las burbujas dentro del agua: dejando las más pesadas (mayores) en los últimos lugares del arreglo a ordenar y las más livianas (menores) en los primeros lugares. En principio el fundamento es simple: declara dos iteradores: uno para el array completo y otro que reorganizará el arreglo. Si lo que está indicado por el primer iterador es menor que lo que tiene el segundo, se mantienen lugares, y de lo contrario se intercambian.
El simple hecho de tener dos iteradores implica que el orden de este algoritmo es de $O(n^{2})$.
\subsection{Algoritmo Insertion Sort}
Este algoritmo utiliza el mismo principio que cuando uno organiza cartas en una mano, organizando progresivamente las mismas de izquierda a derecha, de menor a mayor. Empieza con un iterador externo, fija un valor temporal y crea un nuevo iterador, que entrará siempre que se cumpla que el iterador sea mayor que cero y que el arreglo en el iterador sea mayor que el valor temporal. Una vez dentro de ese iterador, se asigna la posición en la que está, a la posición anterior y el temporal se pone en la posición en la que quedó. Como son dos iteradores, el orden sigue siendo de $O(n^{2})$.
\subsection{Algoritmo Merge Sort}
Se trata de un algoritmo de dividir y vencer. En principio se promedian los índices inicial y final del arreglo, con el fin de enviar eso a la recursión. Ya con el array dividido, se sigue dividiendo hasta que queden arreglos de tamaño 1. Se ordena cada pedazo y se va fusionando asimismo el arreglo.
El hecho de dividir el arreglo por mitades, ya hace el algoritmo de orden logarítmico; pero como se iteran sobre esas divisiones, ya hace que sea de orden $O(n\log n)$.
\section{Algoritmos en Python}
\subsection{Algoritmo Bubble Sort}
\lstinputlisting{recursividad.py}
Los tiempos de ejecución de dicho algoritmo en un procesador Intel Core i3 (el que se está utilizando) son los de crecimiento más rápido. Con arreglos de tamaño 250 tarda 18 milisegundos, con 500 tarda 66 milisegundos (3 y dos tercios de vez más), con 1000 tarda 275 milisegundos (4.16 veces más), con 1500 tarda casi 1 segundo (916 milisegundos siendo exactos, unas 3.3 veces más) y con 2000 tardó casi 2 segundos (1,985 segundos; dos veces y un octavo más).
Sabemos que un crecimiento cuadrático implica que cuando se duplica el número que se tiene en la entrada, se cuadruplica la salida, lo cual se hace patente en este procedimiento.
\subsection{Algoritmo Insertion Sort}
\lstinputlisting{insertsort.py}
Aquí ya empieza a mejorar un poco la situación, hablando de tiempos de ejecución. Con el procesador utilizado, con arreglos de tamaño 250 tardó 16 milisegundos, con 500 tardó 53 milisegundos (poco más de 3 veces más), con 1000 tardó 271 milisegundos (poco más de 5 veces más) y con 2000 tardó poco más de 1 segundo (1,021 s), teniendo en 1500 un tiempo cercano a las 500 milésimas de segundo (0,542 s).
En este caso, el algoritmo, aunque pareciera tener un comportamiento marcadamente cuadrático al principio, tiende a estabilizarse.
\subsection{Algoritmo Merge Sort}
\lstinputlisting{mergesort.py}
Es sin duda, el más rápido de los tres y es quien organizó los 10000 arreglos en un tiempo razonable. La magnitud de la rapidez de este algoritmo, comparándolo con los otros dos, es que un arreglo de tamaño en el rango de los 9000 lo organiza en tiempos en los que los otros dos organizarían apenas en el rango de los 1000, haciéndole aproximadamente 9 o más veces más rápido que los otros dos.
\section{Multiplicación dentro de un arreglo}
\lstinputlisting{multi.py}
Recibe una lista de tamaño cualquiera y un valor entero cualquiera. Itera sobre la lista preguntando si el módulo entre el valor entero y el elemento i-ésimo de la lista es cero y si el cociente entre el entero y ese mismo valor existe en la lista (Python provee un operador llamado \textit{in} que permite hacer esto en una sola línea). Con ambas condiciones verdaderas, el algoritmo devuelve por pantalla el resultado. De lo contrario, imprime que no existe número alguno dentro del arreglo que satisfaga la condición.
\section{Análisis de un algoritmo recursivo}
Con el procesador utilizado, el algoritmo terminaba con errores de desbordamiento de pila (o directamente se moría) en cualquier lenguaje (Python, Java o C++), así que se optó por el uso de Excel para hacer dicha función, porque se optó por imprimir todos los casos posibles. El promedio de llamadas para cada número es de casi 87 (86,9664), siendo el menor cuando apenas inicia (1) y el mayor situándose en 9225, teniendo 259 llamadas recursivas para llegar al caso base. La cantidad de llamadas por cada número es demasiado fluctuante, no sigue un patrón en específico, aunque se sabe al menos que los números pares requieren de menos llamadas recursivas que los impares. Es impracticable poner la tabla y el gráfico porque son de enormes dimensiones, por lo que se incluyen en archivo adjunto, llamado "función.xlsx".
\end{document}