En el fascinante mundo de la Computación, comparar algoritmos es una habilidad esencial para cualquier desarrollador o científico. Sin embargo, es vital saber que existen métodos incorrectos y métodos adecuados para hacer estas comparaciones. No basta con medir el tiempo de ejecución, ya que esta métrica puede ser engañosa al depender del hardware del que disponemos. Implementaciones antiguas pueden ser más lentas en equipos recientes, lo que nos lleva a preguntarnos: ¿cuál es la mejor manera de comparar algoritmos?
¿Por qué el tiempo de ejecución no es una buena métrica?
El tiempo de ejecución varía significativamente entre diferentes sistemas y configuraciones de hardware. Por ejemplo, un algoritmo puede ejecutarse de manera completamente diferente en un procesador antiguo frente a uno reciente, o entre una computadora personal y un servidor en la nube. Además, el entorno de producción puede ser radicalmente distinto al de desarrollo, introduciendo más variables en la mezcla.
¿Por qué no usar el número de instrucciones del programa?
A simple vista, podría parecer que contar las líneas de código o instrucciones es una buena métrica para comparar algoritmos. No obstante, esta aproximación es errónea, ya que depende del lenguaje de programación escogido. Escribir en Java podría requerir menos líneas que en C, pero eso no indica que Java sea mejor en cuanto a eficiencia y desempeño.
¿Cuál es la solución ideal para comparar algoritmos?
La mejor solución es representar nuestro algoritmo como una función que depende del tamaño de la entrada. Al hacer esto, nos permite comparar el comportamiento del algoritmo de manera independiente a la plataforma o al lenguaje utilizado. Esto nos lleva a un concepto crucial: el ritmo de crecimiento.
¿Qué es el ritmo de crecimiento?
El ritmo de crecimiento, o rate of growth, aclara el comportamiento del algoritmo respecto al incremento de la entrada. Al igual que al comprar un coche miramos el costo total en lugar de desglosar cada gasto menor, en algoritmos nos centramos en el término de mayor peso en la función de complejidad.
Ejemplo básico
Supongamos que consideramos un polinomio como n^4 + 2n^2 + 100n + 500. Aquí el término dominante es n^4, que define el ritmo de crecimiento del algoritmo. Esto se traduce directamente a saber cómo se comportará el algoritmo con entradas cada vez más grandes.
¿Cómo medir el crecimiento con un ejemplo real?
Imagina que implementamos un algoritmo de ordenamiento sencillo en C, como el Insertion Sort. Al ejecutarlo, variamos la cantidad de datos ordenados para observar cómo cambia el número de movimientos. Con 10 elementos, el algoritmo es rápido. Sin embargo, con 200 elementos, vemos un aumento drástico, pasando de 23 movimientos a 7575. Esto se ilustra mediante la función de crecimiento que acompaña al algoritmo.
Código de implementación (C)
Veamos un segmento de código que ilustra este punto:
voidinsertionSort(int arr[],int n){for(int i =1; i < n; i++){int key = arr[i];int j = i -1;while(j >=0&& arr[j]> key){ arr[j +1]= arr[j]; j = j -1;} arr[j +1]= key;}}
Así, al probar con diferentes tamaños de entrada, observamos el efecto del ritmo de crecimiento en el rendimiento.
¿Cuáles son los tipos de crecimiento y sus implicaciones?
Entender los diferentes tipos de crecimiento nos ayuda a anticipar cómo un algoritmo manejará grandes volúmenes de datos:
Constante (O(1)): Tiempo de ejecución fijo, independiente del tamaño de la entrada.
Logarítmico (O(log n)): Comúnmente usado en búsqueda binaria de datos organizados.
Lineal (O(n)): El sistema recorre todos los elementos una vez, como en recorridos simples de listas.
Lineal logarítmico (O(n log n)): Típico en algoritmos de ordenamiento eficientes como Merge Sort.
Cuadrático (O(n^2)) y Cúbico (O(n^3)): Implícitos en métodos que involucran ciclos o agrupaciones de datos complejas.
Exponencial (O(2^n)): Aparente en algoritmos de complejidad combinatoria, como las Torres de Hanoi.
Embarcarte en el análisis de ritmos de crecimiento abrirá las puertas a un entendimiento más profundo de la eficiencia algorítmica. Con un enfoque cuidadoso y crítico, mejorarás tus habilidades y contribuirás a crear software más óptimo y escalable. ¡Sigue explorando y perfeccionando tus técnicas para enfrentar cualquier desafío algorítmico!
Comparación de Algoritmos: Complejidad y Ritmo de Crecimiento
Me parece genial que hablen acerca de estos temas, son vitales en el conocimiento de cualquier programador. Si quieren leer más a fondo sobre el tema, les comparto este post que escribí acerca de Complejidad Computacional, la disciplina encargada del análisis de algoritmos:
++Tipos de análisis de ejecución de un programa: ++
Análisis empírico: (poco recomendado)
• Requiere tener el código y ejecutarlo.
• La ejecución del programa se mide en unidades de tiempo.
• Se mide solo sobre una colección de entradas particulares.
• Lo que se mide depende de la computadora, el sistema operativo y el compilador, es decir depende de una implementación particular.
**Análisis teórico: **
• Análisis predictivo de eficiencia integrado al proceso de desarrollo de un programa.
• No depende de características de implementación.
• Establece cotas para el tiempo de ejecución en el "peor caso", "mejor caso" y "caso promedio".
++Notación matemática en las que expresamos el tiempo de ejecución:++
La solución ideal es que vamos a representar el algoritmo como una función que va a depender del tamaño de la entrada, esto va a permitir comparar los diferentes tipos de ejecución independiente del tiempo que consume la maquina en resolver el algoritmo del estilo de programación o el lenguaje que estemos usando.
Vamos a ver algunos materiales que nos van a ayudar a comparar estos algoritmos sin importar el sistema o lenguaje que estemos usando.
Debemos tener en cuenta como se va a medir esto para eso vamos a usar un concepto importante que se llama ANÁLISIS DEL RITMO DE CRECIMIENTO DE LOS ALGORITMOS.
Un ejemplo seria cuando vamos a comprar un carro o una bicicleta y debemos ir directamente a las entradas mas caras, no a las menores.
Ejemplo: en el caso de comprar coche o bicicleta, compraríamos el coche porque es mucho mas costoso, de eso se trata el ANÁLISIS DEL RITMO DE CRECIMIENTO DE LOS ALGORITMOS.
1 CONSTANT= Imagínate que tienes que agregar cada vez mas un elemento a un stack de información o una lista, y esa lista consiste en agregar un dato mas en cada operación. Esto seria un tiempo de ejecución de uno y seria constante.
LOGN LOGARITHMIC= imaginemos que tenemos un arreglo ordenado de una forma que nosotros especificamos y tenemos que buscar en ese arreglo un dato especifico, esto tiene una complejidad de tiempo de tipo logarítmico, esta operación se puede realizar en cualquier calculadora, en este caso seria el numero de información que vas a meter al logaritmo.
N LINEAR= en este caso tenemos un arreglo no ordenado donde los datos van a estar ordenados aleatoria mente, el tiempo es de n al numero que tienes el dato por ejemplo: va a empezar a buscar desde la posición cero hasta la posición n y n va a ser el tiempo de ejecución, otro ejemplo seria si empieza desde cero y necesitas hallar un dato en la posición 20, 20 va a ser el tiempo de ejecución.
NLOGN LINEAR LOGARITHMIC= es un tipo de crecimiento logaritmico lineal, por ejemplo tienes varias listas o varios arreglos y debes ordenarlas por nivel, entonces iria, N^2 QUADRATIC,
N^3 CUBIC, 2^N EXPONENCIAL.
N^2 QUADRATIC= estos algoritmos tienen un ritmo de crecimiento mucho mayor, cuando la entrada va creciendo esto se vuelve una curva de crecimiento acelerado.
N^3 CUBIC= imagínate la multiplicación de dos matrices, entonces la matriz se va volviendo mas grande.
2^N EXPONENCIAL= crecimiento exponencial, donde sera con base 2 a la n, entonces la potencia se incrementa, entonces el n crecería en este tipo de algoritmo.
Para que me quedara un poco más claro, realicé la siguiente imagen en una calculadora graficadora en línea. Cada línea representa el Ritmo de Crecimiento
Donde x representa el valor n(datos ingresados en el algoritmo), y y representa el crecimiento.
Me percate de que esta clase forma parte del curso anterior y me puse a revisar. Hay cosas interesantes en el curso del 2017.
Les aconsejo que le den una mirada.
Curso de Algoritmos con C 2017
En varios videos de este curso veo que aportaste algunos apuntes o soluciones a problemas del curso hace seis meses, y veo de nuevo respuestas a este curso hace dos meses. Me llama la atencion tu comentario sobre este ultimo video cuando hace 6 meses dejaste otro con una descripción de lo que se vio.
@juan8A, gracias por tu comentario pero creo que se me corrió, este comentario recuerdo haberlo puesto en Curso de React avanzado y no se porque esta por aquí en curso básico de algoritmos. revisare nuevamente. Muchas gracias por tu observación. Saludos cordiales!!
No, la cantidad de lineas no influye, lo que influye son los pasos que uses. Por ejemplo un loop se tarda muhco, un loop dentro de otro loop mucho más. Para que entiendas muy bien esto te recomiendo el curso de algoritmos con python
tengo entendido que para analizar el ritmo de crecimiento debemos analizar en cuanto aumenta el numero de operaciones que se realizan a medida que aumentamos la entrada, pero que es lo que consideraríamos como "operaciones" dentro del algoritmo?
Una operación podría considerarse cada instrucciones que se ejecutan. Si están en ciclos ahí se debe considerar cuántas veces puede ejecutarse en total.
Te recomiendo ver esta clase y las tres que le siguen del Curso de POO y Algoritmos con Python para profundizar en estos conceptos. 🤓🌟
De hecho nos podemos ir a lo mas básico, las operaciones aritméticas y lógicas. Las operaciones aritméticas involucran matemática básica como sumas y restas, mientras que las operaciones lógicas comparan dos números, si tu tienes funciones, p.e. una función matemática, internamente esta consta también de operaciones básicas.
Sin embargo se pueden considerar también operaciones por pasos, es decir, en un algoritmo toma mas paso, iteraciones, llegar a un resultado que otro. Saludos :)