Skip to content
Análisis comparativo de algoritmos de ordenamiento
16 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
Los dos droplets de DigitalOcean usados como máquinas de benchmark — M1 (1 núcleo, 1 GB RAM) y M2 (2 núcleos, 2 GB RAM).

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 en Máquina 1 — el tiempo de ejecución crece rápidamente al superar el millón de elementos.
Bubble Sort M2
Bubble Sort en Máquina 2 — la frecuencia por núcleo más baja (1,8 GHz vs 2,4 GHz) resulta en peor rendimiento a pesar de tener más RAM.
Bubble Sort M1 vs M2
Bubble Sort M1 vs M2 — el crecimiento cuadrático O(n²) es claramente visible; M1 supera a M2 gracias a su mayor frecuencia de CPU.

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

Counting Sort M1
Counting Sort en Máquina 1 — curva casi plana O(n+k), terminando en milisegundos incluso con 1,6 M de elementos.
Counting Sort M2
Counting Sort en Máquina 2 — crecimiento igualmente plano; la ventaja O(n+k) se mantiene sin importar el nivel de hardware.
Counting Sort M1 vs M2
Counting Sort M1 vs M2 — ambas máquinas muestran tiempos casi idénticos por debajo del milisegundo, confirmando la cota O(n+k).

Montones (Heapsort): O(n log n)

Heap Sort M1
Heap Sort en Máquina 1 — crecimiento O(n log n), manteniéndose bien por debajo de 2 segundos con 1,6 M de elementos.
Heap Sort M2
Heap Sort en Máquina 2 — rendimiento O(n log n) consistente; ligeramente más lento que M1 por la menor frecuencia de reloj.
Heap Sort M1 vs M2
Heap Sort M1 vs M2 — las curvas superpuestas confirman que en tareas de un solo hilo la frecuencia de CPU es la variable decisiva.

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

Insertion Sort M1
Insertion Sort en Máquina 1 — curva O(n²) más pronunciada que Selection Sort, pero muy por debajo de Bubble Sort.
Insertion Sort M2
Insertion Sort en Máquina 2 — la menor velocidad por núcleo hace más evidente la penalización O(n²) con entradas grandes.
Insertion Sort M1 vs M2
Insertion Sort M1 vs M2 — M1 lidera en todo momento, reforzando que la frecuencia de reloj domina en algoritmos secuenciales.

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

Merge Sort M1
Merge Sort en Máquina 1 — curva O(n log n) estable, competitiva con Heap Sort y Quicksort.
Merge Sort M2
Merge Sort en Máquina 2 — rendimiento consistente; la RAM adicional puede ayudar con las asignaciones de memoria que requiere este algoritmo.
Merge Sort M1 vs M2
Merge Sort M1 vs M2 — los resultados son cercanos, mostrando que el uso de memoria del algoritmo puede compensar parcialmente la diferencia de frecuencia de CPU.

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

Quick Sort M1
Quicksort en Máquina 1 — segundo más rápido en general, manteniéndose consistentemente por debajo de 0,5 segundos hasta 1,6 M de elementos.
Quick Sort M2
Quicksort en Máquina 2 — ligeramente más lento que M1, pero sigue dentro del grupo rápido O(n log n).
Quick Sort M1 vs M2
Quicksort M1 vs M2 — ambas máquinas se comportan de forma similar, confirmando la eficiencia práctica de Quicksort con datos aleatorios.

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

Selection Sort M1
Selection Sort en Máquina 1 — crecimiento O(n²), más rápido que Bubble Sort pero muy por detrás del grupo O(n log n).
Selection Sort M2
Selection Sort en Máquina 2 — escalado cuadrático similar; menos intercambios que Bubble Sort pero la misma cota asintótica.
Selection Sort M1 vs M2
Selection Sort M1 vs M2 — M1 consistentemente más rápido; la ventaja de frecuencia de reloj es el factor decisivo en trabajo O(n²) de un solo hilo.

Gráfica comparativa de todos los algoritmos

All algorithms M1
Los siete algoritmos en Máquina 1 — el grupo O(n²) domina la escala; el grupo rápido se agrupa cerca del eje x.
All algorithms M2
Los siete algoritmos en Máquina 2 — el mismo patrón que M1; la curva de Bubble Sort eclipsa a todo lo demás.
All algorithms M1 vs M2
Todos los algoritmos en ambas máquinas — la mayor frecuencia de reloj de M1 la mantiene más rápida a pesar del mayor número de núcleos y RAM de 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
Algoritmos rápidos en Máquina 1 — Counting Sort lidera con tiempo casi nulo; Quicksort y Heap Sort le siguen de cerca.
Comparación de algoritmos de ordenamiento más rápidos — resultados de benchmark Máquina 2
Algoritmos rápidos en Máquina 2 — Counting Sort sigue dominando; se nota un ligero incremento en Heap Sort con tamaños grandes.
Comparación de algoritmos de ordenamiento más rápidos — Máquina 1 vs Máquina 2 lado a lado
Algoritmos rápidos lado a lado en ambas máquinas — la diferencia de rendimiento entre algoritmos supera la diferencia de hardware.

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
Especificaciones de Máquina 1 con lscpu: 1 núcleo a 2399.998 MHz — la mayor frecuencia de reloj compensó el menor número de recursos.
Arquitectura del sistema de la Máquina 2 de benchmark — especificaciones del droplet de DigitalOcean
Especificaciones de Máquina 2 con lscpu: 2 núcleos a 1799.998 MHz — más núcleos y RAM, pero menor frecuencia por núcleo.

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 llevados al límite de memoria de M1 — las curvas se separan a medida que el tamaño del dataset se acerca a la RAM disponible.
Algoritmos rápidos a máxima capacidad de memoria — resultados de benchmark Máquina 2
Algoritmos rápidos en el techo de memoria de M2 — la RAM adicional extiende el rango viable del dataset antes de que aparezca la degradación.

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 en M1 — sigue siendo casi lineal, pero las restricciones de memoria limitan el techo práctico.
Counting Sort a máxima escala de memoria — resultados de benchmark Máquina 2
Counting Sort a máxima escala en M2 — la RAM adicional le permite manejar rangos de enteros más grandes antes de alcanzar su cota de memoria.
Counting Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2
Counting Sort M1 vs M2 a máxima escala — la RAM adicional de M2 proporciona una ventaja clara con datasets de enteros grandes.

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 en M1 — la curva O(n log n) se mantiene bien comportada incluso con datasets muy grandes.
Heap Sort a máxima escala de memoria — resultados de benchmark Máquina 2
Heap Sort a máxima escala en M2 — ligeramente más lento que M1 pero estable; el ordenamiento in-place mantiene el uso de memoria bajo.
Heap Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2
Heap Sort M1 vs M2 a máxima escala — la brecha por frecuencia de CPU se amplía ligeramente con entradas más grandes.

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 en M1 — las asignaciones de memoria auxiliar se hacen perceptibles con entradas muy grandes.
Merge Sort a máxima escala de memoria — resultados de benchmark Máquina 2
Merge Sort a máxima escala en M2 — la RAM adicional reduce la presión de memoria, manteniendo la curva comparativamente suave.
Merge Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2
Merge Sort M1 vs M2 a máxima escala — la RAM extra de M2 ayuda a compensar la menor frecuencia de reloj en este algoritmo intensivo en memoria.

Quick Sort hasta máximo volumen

Quick Sort a máxima escala de memoria — resultados de benchmark Máquina 1
Quicksort a máxima escala en M1 — sigue entre los más rápidos; su diseño in-place mantiene la sobrecarga de memoria al mínimo.
Quick Sort a máxima escala de memoria — resultados de benchmark Máquina 2
Quicksort a máxima escala en M2 — comparable a M1; la entrada aleatoria evita la degradación al caso peor O(n²).
Quick Sort a máxima escala de memoria — comparación Máquina 1 vs Máquina 2
Quicksort M1 vs M2 a máxima escala — las curvas casi se superponen; ninguna máquina obtiene una ventaja decisiva.

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 en el techo de memoria de M1 — las curvas se separan claramente, revelando el orden práctico: Counting, Quick, Merge, Heap.
Todos los algoritmos rápidos a máxima escala de memoria — resultados de benchmark Máquina 2
Todos los algoritmos rápidos en el techo de memoria de M2 — la RAM adicional permite extender la comparación antes de que los resultados pierdan fiabilidad.
Todos los algoritmos rápidos a máxima escala de memoria — comparación Máquina 1 vs Máquina 2
Comparativa final a máxima escala en ambas máquinas — el comportamiento se alinea con la complejidad teórica para valores grandes de n.

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

Sergio Alexander Florez Galeano

Sergio Alexander Florez Galeano

CTO y Cofundador en DailyBot (YC S21). Escribo sobre desarrollo de productos, startups y el arte de la ingeniería de software.

Comparte este artículo:

Mantente al día

Recibe una notificación cuando publique algo nuevo. Sin spam, cancela cuando quieras.

Sin spam. Cancela cuando quieras.