Uno de los algoritmos de ordenación más populares es QuickSort. Es un método de divide-and-conquer (divide y vencerás) que de manera iterativa elige un elemento pivot del array, divide el array en 3 partes — los elementos más pequeños, los iguales y los más grandes que el pivot — y repite este proceso hasta llegar a un punto en el que todo el array está ordenado.
Entonces, en cada iteración:
Elegimos un pivot en el array
Movemos los elementos más pequeños a la parte izquierda del pivot
Movemos los elementos más grandes a la parte derecha del pivot
Agrupamos en el centro todos los elementos iguales al pivot
Repetimos este proceso con las partes izquierda y derecha
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
Esta implementación de QuickSort usa arreglos adicionales para las partes left, middle y right. Podemos hacerlo in-place modificando el código para usar índices y reacomodar los elementos directamente en el array.
def quicksort(a, start, end):
if start >= end: # No hay elementos que ordenar
return
pivot = a[start] # Elegir un pivot
l, r = start + 1, end # Ordenar a[start+1 ... end]
while l <= r: # Mientras queden elementos dentro del rango
if a[l] < pivot: # si a[l] < pivot => dejar a[i] sin cambios
l += 1 # Incrementar el puntero izquierdo
else: # de lo contrario, mover a[l] al final
a[l], a[r] = a[r], a[l] # Intercambiar a[l] y a[r] -> mover al final
r -= 1 # Reducir el puntero derecho
a[start], a[r] = a[r], a[start] # Intercambiar pivot con a[r]
quicksort(a, start, r - 1) # Ordenar a[start ... r-1]
quicksort(a, r + 1, end) # Ordenar a[r+1 ... end]
Tal vez hayas notado que la manera de elegir el pivot fue diferente en estas dos implementaciones. El algoritmo QuickSort no impone restricciones específicas para escoger un pivot. Existen varias formas de hacerlo. A continuación, presentamos algunas ideas para seleccionar el pivot:
Primer elemento: a[start] como se ve en la segunda implementación
Último elemento: a[end]
Elemento medio: a[(start + end + 1) // 2]
Elemento aleatorio: elegir cualquier elemento en a[start ... end]
Elemento mediano: elegir la mediana del segmento actual. Esto garantiza que cada partición produzca partes left y right balanceadas, pero requiere una implementación más compleja.
El tiempo de ejecución promedio del algoritmo QuickSort es . Sin embargo, dependiendo de cómo se elija el pivot, el rendimiento puede caer a cuadrático .
🤔 ¿Puedes pensar en un ejemplo donde el algoritmo haría operaciones si siempre elegimos start como el pivot?
Challenge
Dada una lista de n enteros y q pivots, se pide mover todos los elementos más pequeños que el pivot a su izquierda, y todos los elementos más grandes a su derecha, dejando todos los iguales juntos (contiguos, a la derecha de los más pequeños y a la izquierda de los más grandes).
Entrada
La primera línea de la entrada contiene un único entero n (1 ≤ n ≤ 100 000).
La siguiente línea contiene una lista de n enteros separados por espacios ( ≤ ≤ ).
La tercera línea contiene un único entero q (1 ≤ q ≤ 10).
La siguiente línea contiene q pivots, cada uno representado por un índice (1 ≤ ≤ n), que indica la posición del elemento pivot.
Salida
Para cada uno de los q pivots, el programa debe imprimir el array resultante tras hacer el reacomodo. Si hay varias respuestas posibles, se puede imprimir cualquiera de ellas.