Complejidad algorítmica

22 03 2008 Programación
    Como ya habíamos afirmado en el artículo sobre la medición de la complejidad, un algoritmo se puede clasificar en cuando a su eficiencia o coste en una de la siguientes categorías. De mayor eficiencia a menor, siendo n el tamaño del problema a tratar:

* Complejidad constante (la mejor y objetivo a buscar) O(1)
* Complejidad logarítmica (muy buena) O(log n)
* Complejidad lineal O(n)
* Complejidad lineal*logarítmica O(n log n)
* Complejidad cuadrática O(n2)
* Complejidad polinomial (mala) O(na) con a>2
* Complejidad exponencial (muy mala) O(an) con a>2
* Complejidad factorial (la peor, problemas generalmente intratables) O(n!)

    Tenemos pues que en cuanto a complejidad algorítmica:

1 << log n << n << nlog n << n2 << n3 << ... << 2n << n!

    A menor orden menos recursos se necesitarán para llevar a cabo la resolución del problema, menor coste.

    Lo que nos puede interesar a continuación es obtener un conjunto de reglas prácticas que no sirvan para determinar la complejidad de un algoritmo y así por ejemplo poder comparar varios algoritmos y determinar cual es el mejor. Estas reglas no pueden por desgracia, tras seguirlas detenidamente, llevarnos a determinar con exactitud el coste de un algoritmo pero son útiles para ayudarnos en este empeño.

    Estas reglas deben tener en consideración los costes de:

*Instrucciones simples
*Composición de instrucciones
*Instrucciones de selección
*Bucles
*Subprogramas

    Los 3 elementos a continuación tienen una ejecución en tiempo (coste) constante, O(1)

* Instrucciones simples: la evaluación de expresiones aritméticas siempre que estas sean de tamaño constante así como la comparación de datos simples.
*Las operaciones de asignación, lectura y escritura de datos simples
*Accesos a elementos de un array, a un campo de un registro y a la siguiente posición de un registro de un archivo

    La composición de instrucciones que dependan del tamaño n del problema el coste será igual a la suma de los costes.

    Para las instrucciones de selección en que varios partes del código pueden ser o no ejecutadas según la condición que defina la selección se toma como coste el coste de la peor rama.

    Para los bucles si estos dependen del tamaño del problema se considera orden lineal, pero si están anidados por cada anidación que dependa del tamaño del problema multiplicaremos por n el coste dando lugar a coste cuadráticos, polinómicos o incluso exponenciales

    Para subprogramas resulta de aplicar las anteriores reglas al subprograma, si el número de llamadas al subprograma depende del tamaño del programa (tener en cuenta posibles recursividades) el orden sera mayor dependiendo de como crezca el número de llamadas con el tamaño.

    Si se descompone el coste en partes tenemos que el coste de la unión de cada parte es igual a la que tiene un costo mayor. Es decir si tenemos una parte con un coste lineal y a continuación otra parte con coste cuadrático la unión de las dos nos dará un coste cuadrático (regla de la suma)

    Vamos a continuación a poner dos implementaciones en C de dos algoritmos diferentes para llevar a cabo el mismo problema. Se trata de, a partir de dos números enteros distintos, obtener la suma de todos los números que hay entre ellos incluidos ellos mismos. Por ejemplo si tenemos el 5 y el 10 el algoritmo debe ser capaz de calcular la suma:

5+6+7+8+9+10


    La primera implementación se basa en el algoritmo trivial de ir sumando uno tras otro tal y como aparece en la línea de arriba.


#include <stdio.h>
#include <limits.h>

int main(int argc,char** argv)
{
int min,max;
unsigned long long i;
if(argc!=3)
{
printf("Error Usage %s minNum maxNum\n",argv[0]);
return -1;
}
min = atoi(argv[1]);
max = atoi(argv[2]);
if(min<0 || max<0)
{
printf("Max number and min Number must be positive or zero\n");
return -2;
}
if(!(min<max))
{
printf("Min number must be less than max number\n");
return -3;
}
if(max>=INT_MAX)
{
printf("Sorry, max number must be less than %i\n",INT_MAX);
return -4;
}
for(i=0;min<=max;min++)
{
i+=min;
}
printf("El resultado es %llu\n",i);
return 0;
}


La segunda se basa en series aritméticas para el cálculo de esta suma


#include <stdio.h>
#include <limits.h>

int main(int argc,char** argv)
{
unsigned long long min,max;
unsigned long long i;
if(argc!=3)
{
printf("Error Usage %s minNum maxNum\n",argv[0]);
return -1;
}
min = atoi(argv[1]);
max = atoi(argv[2]);
if(min<0 || max<0)
{
printf("Max number and min Number must be positive or zero\n");
return -2;
}
if(!(min<max))
{
printf("Min number must be less than max number\n");
return -3;
}
if(max>=INT_MAX)
{
printf("Sorry, max number must be less than %i\n",INT_MAX);
return -4;
}
i=(max+min)*(max+1-min)/2;
printf("El resultado es %llu\n",i);
return 0;
}


    La primera pregunta que habrá que hacerse es ¿cual es el tamaño del problema?. La respuesta es clara, será el valor de la diferencia entre el número menor y mayor que introducimos. Analizando el programa vemos que en realidad ambas implementaciones difieren en las últimas lineas del programa las primeras son sólo comprobaciones de que se han introducido datos y que estos son correcto.

    Estas comprobaciones tienen coste constante O(1) pues son sólo instrucciones y comparaciones simples que no dependen del tamaño del problema. Además las condiciones que aparecen; si se cumplen entrarían en un bloque de coste constante O(1). Imprimir una cadena con la condición del error y salir.

Pues bien en donde se diferencia es en estos bloques de código










Implementación 1 Implementación 2

for(i=0;min<=max;min++)
{
i+=min;
}

i =(max+min)*(max+1-min)/2;



La implementación 1 tiene un coste lineal n porque el bucle depende del tamaño del problema, el número de iteraciones coincide con la diferencia entre el número máximo y el mínimo, es pues un algoritmo de orden lineal.

La implementación 2 tiene un conste constante ya que se trata sólo de una instrucción simple (operaciones aritméticas)

Puedes probar a compilar los dos programas y a ejecutarlos para tamaños diferentes, si empleas el comando time podrás ver cuanto tiempo le lleva tu ordenador realizar los cálculos.

Para mi viejo ordenador estos fueron los resultados en milésimas de segundo:











Tamaño100104106108109
Algoritmo 133125455312
Algoritmo 223333



Queda claro que el segundo algoritmo es mucho más eficiente que el primero.

    Me gustaría terminar con una pequeña anécdota muy relacionada con este algoritmo. Ocurrió en un clase hace ya mucho tiempo, hace más de 2 siglos de hecho:


    Un profesor le dijo a sus alumnos que sumaran todos los números del 1 al 100 y que le dieran la respuesta para mantenerlos ocupados. Casi al momento un alumno se la dio. Al profesor le pareció imposible que en tan poco tiempo hubiera sumado tal cantidad de números. Sus alumnos tenían 10 años y el alumno había tardado poquísimo en dársela. Pero estaba bien: 5050.

    La explicación fue que no lo había hecho como el resto de los niños sumando un número tras otro. Pensó en el problema por un momento y se dio cuenta de que si sumaba 1 + 100 obtenía 101, si sumaba 2 + 99 también obtenía 101 y 3 + 98 también daba el mismo resultado. El número de parejas que podía construir de esa manera era exactamente 50 y en ellas estarían todos los números que le pidió el profesor. 50 parejas que sumaban 101 significaba que la suma total debía ser 101 por 50 exactamente 5050.

    No hacia falta ser bueno calculando. Sólo había que emplear otro "método". El niño se llamaba Karl Friedrich Gauss uno de los mayores matemáticos de todos los tiempos.

Bookmark Complejidad algorítmica  at del.icio.us Digg Complejidad algorítmica Mixx Complejidad algorítmica Bloglines Complejidad algorítmica Technorati Complejidad algorítmica Fark this: Complejidad algorítmica Bookmark Complejidad algorítmica  at YahooMyWeb Bookmark Complejidad algorítmica  at Furl.net Bookmark Complejidad algorítmica  at reddit.com Bookmark Complejidad algorítmica  at blinklist.com Bookmark Complejidad algorítmica  at Spurl.net Bookmark Complejidad algorítmica  at NewsVine Bookmark Complejidad algorítmica  at Simpy.com Bookmark Complejidad algorítmica  at blogmarks Bookmark Complejidad algorítmica  with wists Bookmark Complejidad algorítmica  at Ma.gnolia.com wong it! Bookmark using any bookmark manager! Stumble It!

Referencias


22 03 2008
¿Como medir la complejidad?
    Con este pequeño artículo trataré de explicar como es posible medir la complejidad de algo, computacionalmente hablando. Aunque ciertamente estas ideas podrían ser utilizadas en otras muchas ramas en donde se presenta un problema q
Weblog: Novanebula blog
Tracked: Mar 22, 19:50

Comentarios

Mostrar comentarios como (Plano | Hilos)
No hay comentarios

Añadir comentario


Encerrando entre asteriscos convierte el texto en negrita (*palabra*), el subrayado es hecho así: _palabra_.
Smilies normales como :-) y ;-) son convertidos en imágenes.