Clasificación rápida con Python
En la vida real, siempre deberíamos usar el tipo integrado proporcionado por Python. Sin embargo, comprender el algoritmo de clasificación rápida es instructivo.
Mi objetivo aquí es desglosar el tema de tal manera que el lector pueda entenderlo y reproducirlo fácilmente sin tener que volver a los materiales de referencia.
El algoritmo de clasificación rápida es esencialmente el siguiente:
- Seleccione un punto de datos de pivote.
- Mueva todos los puntos de datos menores (debajo) del pivote a una posición debajo del pivote; mueva los puntos mayores o iguales (arriba) del pivote a una posición por encima de este.
- Aplicar el algoritmo a las áreas por encima y por debajo del pivote
Si los datos se distribuyen aleatoriamente, seleccionar el primer punto de datos como pivote equivale a una selección aleatoria.
Ejemplo legible:
Primero, veamos un ejemplo legible que usa comentarios y nombres de variables para señalar valores intermedios:
def quicksort(xs):
"""Given indexable and slicable iterable, return a sorted list"""
if xs:
pivot = xs[0]
below = [i for i in xs[1:] if i < pivot]
above = [i for i in xs[1:] if i >= pivot]
return quicksort(below) + [pivot] + quicksort(above)
else:
return xs
Para reformular el algoritmo y el código que se muestran aquí, movemos los valores por encima del pivote a la derecha y los valores por debajo del pivote a la izquierda, y luego pasamos esas particiones a la misma función para que se clasifiquen más.
Jugado al golf:
Esto se puede jugar al golf hasta 88 caracteres:
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Para ver cómo llegamos allí, primero tome nuestro ejemplo legible, elimine los comentarios y las cadenas de documentos, y busque el pivote en su lugar:
def quicksort(xs):
if xs:
below = [i for i in xs[1:] if i < xs[0]]
above = [i for i in xs[1:] if i >= xs[0]]
return quicksort(below) + [xs[0]] + quicksort(above)
else:
return xs
Ahora busque abajo y arriba, en el lugar:
def quicksort(xs):
if xs:
return (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
else:
return xs
Ahora, sabiendo que and
devuelve el elemento anterior si es falso, de lo contrario si es verdadero, evalúa y devuelve el siguiente elemento, tenemos:
def quicksort(xs):
return xs and (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
Dado que las lambdas devuelven una sola expresión, y lo hemos simplificado a una sola expresión (aunque se está volviendo más ilegible) ahora podemos usar una lambda:
quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
Y para reducir a nuestro ejemplo, acorte los nombres de las funciones y variables a una letra y elimine los espacios en blanco que no son necesarios.
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Tenga en cuenta que esta lambda, como la mayoría de los códigos de golf, tiene un estilo bastante malo.
Ordenación rápida en el lugar, utilizando el esquema de particionamiento Hoare
La implementación anterior crea muchas listas adicionales innecesarias. Si podemos hacer esto en el lugar, evitaremos desperdiciar espacio.
La siguiente implementación usa el esquema de partición de Hoare, sobre el cual puede leer más en wikipedia (pero aparentemente hemos eliminado hasta 4 cálculos redundantes por partition()
llamada usando la semántica de bucle while en lugar de do-while y moviendo los pasos de reducción al final de el bucle externo while.).
def quicksort(a_list):
"""Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort"""
def _quicksort(a_list, low, high):
if low < high:
p = partition(a_list, low, high)
_quicksort(a_list, low, p)
_quicksort(a_list, p+1, high)
def partition(a_list, low, high):
pivot = a_list[low]
while True:
while a_list[low] < pivot:
low += 1
while a_list[high] > pivot:
high -= 1
if low >= high:
return high
a_list[low], a_list[high] = a_list[high], a_list[low]
low += 1
high -= 1
_quicksort(a_list, 0, len(a_list)-1)
return a_list
No estoy seguro si lo probé lo suficientemente a fondo:
def main():
assert quicksort([1]) == [1]
assert quicksort([1,2]) == [1,2]
assert quicksort([1,2,3]) == [1,2,3]
assert quicksort([1,2,3,4]) == [1,2,3,4]
assert quicksort([2,1,3,4]) == [1,2,3,4]
assert quicksort([1,3,2,4]) == [1,2,3,4]
assert quicksort([1,2,4,3]) == [1,2,3,4]
assert quicksort([2,1,1,1]) == [1,1,1,2]
assert quicksort([1,2,1,1]) == [1,1,1,2]
assert quicksort([1,1,2,1]) == [1,1,1,2]
assert quicksort([1,1,1,2]) == [1,1,1,2]
Conclusión
Este algoritmo se enseña con frecuencia en cursos de informática y se solicita en entrevistas de trabajo. Nos ayuda a pensar en la recursividad y el divide y vencerás.
Quicksort no es muy práctico en Python ya que nuestro algoritmo de ordenación de tiempos incorporado es bastante eficiente y tenemos límites de recursividad. Esperaríamos ordenar listas en el lugar con list.sort
o crear nuevas listas ordenadas con sorted
, las cuales toman un argumento key
y reverse
.
my_list = list1 + list2 + ...
. O descomprima listas en una nueva listamy_list = [*list1, *list2]