Ordenar una colección de datos es probablemente la operación más común en programación después de recorrerla y filtrarla. Casi ningún lenguaje moderno te obliga a implementar tu propio algoritmo de ordenación —Python tiene sorted(), JavaScript tiene Array.prototype.sort()— pero entender cómo funcionan por dentro sigue siendo una de las mejores formas de aprender a razonar sobre eficiencia y complejidad.
Vamos a ver tres algoritmos clásicos, de menor a mayor sofisticación: bubble sort, insertion sort y quicksort. Los tres resuelven el mismo problema, pero con estrategias y costes muy distintos.
Cómo pensar en cualquier algoritmo de ordenación
Antes de entrar en cada uno, una idea general: todo algoritmo de ordenación por comparación reduce el problema a la misma pregunta repetida miles de veces: "¿este elemento va antes o después que este otro?". Lo que cambia entre algoritmos es en qué orden haces esas comparaciones y qué haces con el resultado. Esa diferencia de estrategia es la que separa un algoritmo que tarda segundos de uno que tarda horas con la misma cantidad de datos.
Bubble sort recorre la lista comparando cada par de elementos adyacentes y los intercambia si están en el orden equivocado. Repite este recorrido completo tantas veces como sea necesario hasta que en una pasada no hace falta ningún intercambio. Su nombre viene de cómo los valores más grandes van "burbujeando" hacia el final en cada pasada.
Es el algoritmo más fácil de entender y de programar, pero también el menos eficiente: en el peor caso hace del orden de n² comparaciones, lo que lo vuelve impráctico para listas grandes. Su único mérito real hoy en día es didáctico.
def bubble_sort(numeros):
n = len(numeros)
for pasada in range(n - 1):
intercambio = False
for i in range(n - 1 - pasada):
if numeros[i] > numeros[i + 1]:
numeros[i], numeros[i + 1] = numeros[i + 1], numeros[i]
intercambio = True
if not intercambio: # ya está ordenado, no seguir
break
return numeros
print(bubble_sort([5, 2, 8, 1, 9, 3])) # [1, 2, 3, 5, 8, 9]
Insertion sort construye la lista ordenada de uno en uno, igual que ordenarías cartas de una baraja en la mano: coges la siguiente carta y la insertas en la posición correcta respecto a las que ya tienes ordenadas, desplazando las que hagan falta.
Sigue siendo O(n²) en el peor caso, pero en la práctica es más rápido que bubble sort y funciona especialmente bien con listas pequeñas o casi ordenadas, hasta el punto de que muchos algoritmos híbridos lo usan como paso final para fragmentos pequeños.
def insertion_sort(numeros):
for i in range(1, len(numeros)):
actual = numeros[i]
j = i - 1
# desplaza los elementos mayores que 'actual' una posición
while j >= 0 and numeros[j] > actual:
numeros[j + 1] = numeros[j]
j -= 1
numeros[j + 1] = actual
return numeros
print(insertion_sort([5, 2, 8, 1, 9, 3])) # [1, 2, 3, 5, 8, 9]
Quicksort cambia de estrategia por completo: elige un elemento como "pivote", reorganiza la lista para que todo lo menor que el pivote quede a su izquierda y todo lo mayor a su derecha, y luego aplica el mismo proceso, de forma recursiva, a cada una de esas dos mitades.
En promedio consigue O(n log n), sensiblemente mejor que los dos algoritmos anteriores, y es el que suelen usar internamente los lenguajes de programación (con variaciones) para ordenar colecciones grandes. Su punto débil: si eliges mal el pivote de forma sistemática, puede degradarse a O(n²) en el peor caso.
def quicksort(numeros):
if len(numeros) <= 1:
return numeros # caso base: 0 o 1 elementos ya están ordenados
pivote = numeros[len(numeros) // 2]
menores = [x for x in numeros if x < pivote]
iguales = [x for x in numeros if x == pivote]
mayores = [x for x in numeros if x > pivote]
return quicksort(menores) + iguales + quicksort(mayores)
print(quicksort([5, 2, 8, 1, 9, 3])) # [1, 2, 3, 5, 8, 9]
¿Qué usan realmente Python y JavaScript por debajo?
Ni Python ni JavaScript usan quicksort "puro" en su implementación estándar. Python usa Timsort, un algoritmo híbrido entre insertion sort y merge sort optimizado para datos parcialmente ordenados (muy comunes en el mundo real). Los motores de JavaScript modernos, como V8, usan variantes también híbridas, combinando insertion sort para fragmentos pequeños con algoritmos más eficientes para el resto. La lección práctica es la misma en ambos casos: en producción, usa siempre la función de ordenación integrada del lenguaje; implementar la tuya solo tiene sentido para aprender o para casos muy específicos.
Cuándo te van a preguntar esto de verdad
Más allá del código en sí, entender estos algoritmos te da algo más valioso: intuición sobre complejidad algorítmica, que es justo lo que se evalúa en la parte técnica de una entrevista de programador. Saber explicar por qué quicksort es más rápido que bubble sort "en la mayoría de casos, pero no siempre" demuestra que entiendes el compromiso entre casos promedio y casos peores, algo que se traslada directamente a decisiones de diseño reales, como elegir bien las estructuras de datos sobre las que vas a operar.
Cuál usar en la práctica
La respuesta corta: ninguno, usa la función de ordenación de tu lenguaje. La respuesta larga: si alguna vez necesitas justificar por qué, insertion sort gana con listas muy pequeñas o casi ordenadas por su bajo coste por operación; quicksort (o sus variantes) gana con listas grandes y desordenadas por su complejidad media más baja; y bubble sort no gana en ningún escenario real, solo en valor didáctico para entender qué significa "ineficiente".