Skip to content
Análisis comparativo de algoritmos de ordenamiento
6 min de lectura

Análisis comparativo de algoritmos de ordenamiento

Como parte de un ejercicio típico de algoritmia en la universidad, hice un pequeño análisis comparativo de los algoritmos de ordenamiento más populares, buscando estudiar la complejidad de cada uno y cómo las diferentes formas de resolver un mismo problema pueden afectar los tiempos de ejecución.

Quiero aclarar que este es solo un análisis académico muy simple que quise documentar, y que tal vez sirva a futuro para otros estudiantes de ciencias de la computación.

Comencé desarrollando un pequeño script en Java que genera números aleatorios de cinco dígitos y los almacena en un archivo de texto, para poder analizar el mismo conjunto de datos entre diferentes algoritmos. El script lo puedes encontrar en este repositorio y ejecutar de la siguiente forma:

# Ruta del archivo
> algorithms/java/RandomNumbers.java

# Ejecutar script en Java
$ javac RandomNumbers.java && java RandomNumbers

Lo anterior genera el archivo numbers/numbers.txt con n números aleatorios definidos dentro del script. En mis experimentos llegué a generar un archivo de 1.000.000.000 de datos (cerca de 5 GB), por eso no lo adjunté en el repositorio.


Algoritmos evaluados

En un paso siguiente procedí a implementar algoritmos de ordenamiento populares:

Para esta tarea seleccioné C y los scripts se encuentran en algorithms/c/sortAlgorithms.


Automatización de pruebas

Dado que para hacer un buen análisis se deben correr muchas pruebas, creé un par de scripts para automatizarlas:

# Script base para ejecutar cualquier algoritmo y generar logs de tiempos
> algorithms/c/benchmark.c

# Correr prueba
# arg1, arg2 => nombre del algoritmo y cantidad de elementos
$ gcc benchmark.c -o benchmark.out && ./benchmark.out arg1 arg2

# Script para correr múltiples pruebas
> algorithms/c/runTest.c

# Correr pruebas
$ gcc runTest.c -o runTest.out && ./runTest.out

Con esto ya estaba todo listo. Solo faltaba dejar corriendo runTest.c en una máquina. Aunque era un ejercicio académico sin gran rigor científico, procuré usar un pequeño ambiente controlado para evitar ruido por otros procesos.

Para eso creé dos droplets en Digital Ocean:

Digital Ocean droplets

El segundo servidor tenía el doble de recursos, así que en teoría debía rendir mejor.

También configuré Java y C en ambos servidores con este script de aprovisionamiento: ServerConfig/provision.sh

# Base installation
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y build-essential gcc python-dev python-pip python-setuptools

# Git
sudo apt-get install -y git

# Install Java
sudo apt-get install default-jre -y
sudo apt-get install default-jdk -y
sudo apt-get install openjdk-7-jre -y
sudo apt-get install openjdk-7-jdk -y

Resultados

En cada máquina se corrieron pruebas con el mismo archivo de números aleatorios, aumentando el tamaño en diferentes intervalos (10, 100, 1.000, 10.000, etc.). Los resultados detallados están en results/analysis.ods.

Como pausa útil, este fue el truco para dejar el proceso en background sin depender de la sesión:

$ gcc runTest.c -o runTest.out && ./runTest.out
# Ctrl + z
disown -h %1
bg 1

Después de varios días, los experimentos apenas iban por 1.600.000 de datos en los algoritmos O(n^2), así que detuve la ejecución en ambos servidores y empecé el análisis.

M1 = Máquina 1 (1 núcleo, 1GB RAM)
M2 = Máquina 2 (2 núcleos, 2GB RAM)

Burbuja (Bubble Sort): O(n^2)

Bubble Sort M1 Bubble Sort M2 Bubble Sort M1 vs M2

Conteo (Counting Sort): O(n+k)

Counting Sort M1 Counting Sort M2 Counting Sort M1 vs M2

Montones (Heapsort): O(n log n)

Heap Sort M1 Heap Sort M2 Heap Sort M1 vs M2

Inserción (Insertion Sort): O(n^2)

Insertion Sort M1 Insertion Sort M2 Insertion Sort M1 vs M2

Mezcla (Merge Sort): O(n log n)

Merge Sort M1 Merge Sort M2 Merge Sort M1 vs M2

Rápido (Quicksort): O(n log n)

Quick Sort M1 Quick Sort M2 Quick Sort M1 vs M2

Selección (Selection Sort): O(n^2)

Selection Sort M1 Selection Sort M2 Selection Sort M1 vs M2

Gráfica comparativa de todos los algoritmos

All algorithms M1 All algorithms M2 All algorithms M1 vs M2

En esa comparativa, los cuatro algoritmos rápidos (quickSort, mergeSort, heapSort, countingSort) se solapan por escala. El perdedor claro fue bubbleSort, seguido por insertionSort y selectionSort.

Esto refleja un reto clásico en computación: para un mismo problema hay muchas soluciones, pero cada una funciona mejor bajo condiciones concretas.


Tiempos de respuesta (últimos 7 puntos)

Máquina 1 (M1)

SizeBubbleSortCountingSortHeapSortInsertionSortMergeSortQuickSortSelectionSort
1,000,0005584.2544990.0166090.7473952592.4989770.7042810.2914991935.487457
1,100,0006637.2222520.0191870.9257643171.4457150.6534550.4710392269.966268
1,200,0008045.9536820.0236520.9135373722.6388850.5130990.2394542783.279525
1,300,00010169.3833780.0452080.7133084824.2502850.5751490.2612893514.914589
1,400,00012053.6587980.0346131.4890845658.7399510.6761120.2794784066.729922
1,500,00013798.8541230.0275251.0942576555.3654990.7436510.3156024839.340426
1,600,00015205.6805440.0284780.9966486794.5121190.7253470.3259905056.213092

Máquina 2 (M2)

SizeBubbleSortCountingSortHeapSortInsertionSortMergeSortQuickSortSelectionSort
1,000,0007069.3170380.0324150.7521683178.6942370.5582000.3156892454.531144
1,100,0008458.1503870.0241570.8420383666.3597870.5574810.2845792804.449695
1,200,0009495.8987080.0265300.8828194084.5819240.6166360.3585023081.748250
1,300,00010626.0237710.0273090.9138144933.2018830.7538900.4014563912.714921
1,400,00013439.2500820.0300091.0612215790.7978040.6331800.4424494066.729922
1,500,00015102.7365920.0318261.0647446565.6303580.7885510.4002385114.565289
1,600,00016483.6948080.0392981.3651297311.3470040.7606180.4492845676.768371

Algoritmos rápidos

Al graficar solo los algoritmos eficientes (quickSort, mergeSort, heapSort, countingSort), casi ninguno superó 1 segundo con 1.600.000 datos.

Comparación de algoritmos de ordenamiento más rápidos — resultados de benchmark Máquina 1 Comparación de algoritmos de ordenamiento más rápidos — resultados de benchmark Máquina 2 Comparación de algoritmos de ordenamiento más rápidos — Máquina 1 vs Máquina 2 lado a lado

El ganador fue countingSort (O(n+k)), pero no es una bala de plata:

  • Solo funciona con enteros.
  • Puede consumir mucha memoria si máximo - mínimo es grande.

En segundo lugar quedó quickSort, muy usado en práctica, aunque puede degradar a O(n^2) con mala selección de pivote y distribuciones adversas.


Máquina 1 vs Máquina 2

Surgió una pregunta importante: ¿cómo puede M1 vencer a M2 en tiempos si M2 tiene el doble de recursos?

La clave es que los algoritmos no estaban paralelizados. Aunque M2 tiene dos núcleos, el código no usaba hilos para explotarlos realmente.

Eso deja la comparación más ligada a frecuencia de CPU por núcleo:

  • M1: 2399.998 MHz
  • M2: 1799.998 MHz

Información del sistema

Arquitectura del sistema de la Máquina 1 de benchmark — especificaciones del droplet de DigitalOcean Arquitectura del sistema de la Máquina 2 de benchmark — especificaciones del droplet de DigitalOcean

Las unidades de frecuencia:

  • 1 Hertz (Hz) = un ciclo/segundo
  • 1 Kilohertz (KHz) = 1024 Hz
  • 1 Megahertz (MHz) = 1024 KHz
  • 1 Gigahertz (GHz) = 1024 MHz
  • 1 Terahertz (THz) = 1024 GHz

Comando usado para inspeccionar CPU:

$ lscpu

Prueba extendida (máxima capacidad por memoria)

Luego extendí el experimento enfocándome en algoritmos eficientes para ver cuánto soportaban las máquinas por memoria disponible:

Algoritmos rápidos a máxima capacidad de memoria — resultados de benchmark Máquina 1 Algoritmos rápidos a máxima capacidad de memoria — resultados de benchmark Máquina 2

Aquí M2 (2GB RAM) sí logró procesar más volumen en varios casos.

Counting Sort hasta máximo volumen

Counting Sort a máxima escala de memoria — resultados de benchmark Máquina 1 Counting Sort a máxima escala de memoria — resultados de benchmark Máquina 2 Counting Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2

Heap Sort hasta máximo volumen

Heap Sort a máxima escala de memoria — resultados de benchmark Máquina 1 Heap Sort a máxima escala de memoria — resultados de benchmark Máquina 2 Heap Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2

Merge Sort hasta máximo volumen

Merge Sort a máxima escala de memoria — resultados de benchmark Máquina 1 Merge Sort a máxima escala de memoria — resultados de benchmark Máquina 2 Merge Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2

Quick Sort hasta máximo volumen

Quick Sort a máxima escala de memoria — resultados de benchmark Máquina 1 Quick Sort a máxima escala de memoria — resultados de benchmark Máquina 2 Quick Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2

Comparativa final de algoritmos eficientes

Todos los algoritmos rápidos a máxima escala de memoria — resultados de benchmark Máquina 1 Todos los algoritmos rápidos a máxima escala de memoria — resultados de benchmark Máquina 2 Todos los algoritmos rápidos a máxima escala de memoria — comparación Máquina 1 vs Máquina 2

Al extender el experimento con mayor volumen de datos, las curvas se volvieron más uniformes y comparables con sus funciones de complejidad teórica.


Conclusiones

Este ejercicio me ayudó a reforzar varias ideas:

  • Complejidad algorítmica importa, pero no cuenta toda la historia.
  • El hardware influye según el tipo de algoritmo y su uso real de recursos.
  • Más RAM no siempre acelera, pero puede ampliar el límite de datos procesables.
  • Paralelismo real requiere diseño explícito (hilos, partición de trabajo, etc.).

Espero que este análisis le sirva a quien está empezando en ciencias de la computación o quiere repasar fundamentos.

Recursos: Repositorio con código en GitHub


“La inteligencia consiste no solo en el conocimiento, sino también en la destreza de aplicar los conocimientos en la práctica.”
Aristóteles