Entender la complejidad de un algoritmo es importante, a la hora de resolver
muchos problemas, utilizamos algoritmos ya diseados. Saber valorar su valor de complejidad puede ayudarnos a conocer cmo se va a comportar el algoritmo e incluso a escoger uno u otro. Podemos entender por algoritmo una secuencia de instrucciones cuyo objetivo es la resolucin de un problema. El trmino clave aqu es el de problema. Saber si un algoritmo es mejor que otro puede estudiarse desde dos puntos de vista: un algoritmo es mejor cuanto menos tarde en resolver un problema, o bien es tanto mejor cuanta menos memoria necesite. A la idea del tiempo que consume un algoritmo para resolver un problema le llamamos complejidad temporal y a la idea de la memoria que necesita el algoritmo le llamamos complejidad espacial. Cada problema tiene uno o varios valores que determinan su talla.
La complejidad no es un nmero: es una funcin
Otra consideracin a tener en cuenta a la hora de tratar con la complejidad es que si estamos contando el tiempo que tarda un algoritmo en resolver un problema En qu ordenador lo ejecutamos? Parece obvio que el mismo algoritmo ejecutado en un ordenador el doble de rpido que otro tardar la mitad en encontrar la solucin. Bueno... pues vamos a adoptar una simplificacin que nos permita no tener en cuenta en qu ordenador se ejecutar el algoritmo: en lugar de medir tiempos, vamos a contar las instrucciones que debe realizar el algoritmo. Supondremos que cada instruccin se ejecuta en un tiempo constante. Nos podemos permitir esa simplificacin porque lo que realmente queremos saber es cmo crece el nmero de instrucciones necesarias para resolver el problema con respecto a la talla del problema. Eso es realmente la complejidad. lo que nos importa realmente de un algoritmo: saber cmo crece el nmero de instrucciones a realizar conforme lo apliquemos cada vez a problemas ms grandes, ms que el tiempo medido en segundos. Cmo se comparan unos algoritmos con otros? Bueno... la idea de la complejidad de un algoritmo, es conocer cmo se comporta el tiempo de ejecucin conforme la talla del problema va creciendo.... especialmente para valores muy grandes... lo ms grandes que podamos imaginar, y especialmente en el peor de los casos.
Entendiendo la notacin Big-O
La notacin Big-O nos proporciona una manera de saber cmo se va a comportar un algoritmo en funcin de los argumentos que le pasemos y la escala de los mismos. una funcin que se utiliza para localizar un elemento dentro de una lista de elementos previamente guardados. Si la documentacin de la misma nos dice que es una operacin de tipo O(1), quiere decir que da igual cuntos elementos haya en la lista, la operacin siempre tarda lo mismo. Para ser ms exactos deberamos decir que el esfuerzo de cmputo necesario es el mismo. por eso, por ejemplo, que en la documentacin el indizador Item de un ArrayList que vimos antes, tenga complejidad O(1) quiere decir que da exactamente igual que la coleccin tenga uno o un milln de elementos: insertar o recuperar un elemento siempre tarda ms o menos lo mismo. . O sea, el tiempo necesario para ejecutar la funcin es funcin directa y lineal del nmero de elementos que le pasemos. Es muy importante conocer el tipo de funcin/curva que se asocia con la ejecucin de una funcin ya que nos permitir saber de antemano si el rendimiento de nuestra aplicacin se puede resentir en caso de que el tamao de los datos a manejar aumente mucho. As, entre dos algoritmos/funciones que hagan lo mismo, deberamos elegir el que tenga una complejidad asinttica menor, para lo cual consultaremos la notacin Big-O asociada al mismo. Atendiendo a su complejidad, las notaciones Big-O ms comunes para todo tipo de algoritmos y funciones son las que se muestran en esta lista: O(1): constante. La operacin no depende del tamao de los datos. Es el caso ideal, pero a la vez probablemente el menos frecuente. No se ve en la grfica de ms abajo porque la logartmica le pasa justo por encima y la tapa. O(n): lineal. El tiempo de ejecucin es directamente proporcional al tamao de los datos. Crece en una lnea recta. O(log n): logartmica. por regla general se asocia con algoritmos que "trocean" el problema para abordarlo, como por ejemplo una bsqueda binaria. O(nlogn): en este caso se trata de funciones similares a las anteriores, pero que rompen el problema en varios trozos por cada elemento, volviendo a recomponer informacin tras la ejecucin de cada "trozo". Por ejemplo, el algoritmo de bsqueda Quicksort. O(n2): cuadrtica. Es tpico de algoritmos que necesitan realizar una iteracin por todos los elementos en cada uno de los elementos a procesar. Por ejemplo el algoritmo de ordenacin de burbuja. Si tuviese que hacer la iteracin ms de una vez seran de complejidad O(n3), O(n4), etc... pero se trata de casos muy raros y poco optimizados. O(2n): exponencial. Se trata de funciones que duplican su complejidad con cada elemento aadido al procesamiento. Son algoritmos muy raros pues en condiciones normales no debera ser necesario hacer algo as. Un ejemplo sera, por ejemplo, el clculo recursivo de la serie de Fibonacci, que es muy poco eficiente (se calcula llamndose a s misma la funcin con los dos nmeros anteriores: F(n)=F(n-1)+F(n-2)). O(n!); explosin combinatoria. Un algoritmo que siga esta complejidad es un algoritmo totalmente fallido. Una explosin combinatoria se dispara de tal manera que cuando el conjunto crece un poco, lo normal es que se considere computacionalmente inviable. Solo se suele dar en algoritmos que tratan de resolver algo por la mera fuerza bruta. No deberas verlo nunca en un software "real".