Al implementar Quicksort, una de las cosas que debe hacer es elegir un pivote. Pero cuando miro un pseudocódigo como el siguiente, no está claro cómo debo elegir el pivote. ¿Primer elemento de la lista? ¿Algo más?
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array
if x ≤ pivot then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))
¿Puede alguien ayudarme a comprender el concepto de elegir un pivote y si diferentes escenarios requieren diferentes estrategias?
algorithm
sorting
pseudocode
quicksort
Jacob T. Nielsen
fuente
fuente
Respuestas:
La elección de un pivote aleatorio minimiza la posibilidad de que encuentre un rendimiento O (n 2 ) en el peor de los casos (si elige siempre el primero o el último, se produciría el peor rendimiento para los datos casi ordenados o casi al revés). La elección del elemento intermedio también sería aceptable en la mayoría de los casos.
Además, si está implementando esto usted mismo, hay versiones del algoritmo que funcionan en el lugar (es decir, sin crear dos listas nuevas y luego concatenarlas).
fuente
Depende de sus requisitos. La elección de un pivote al azar dificulta la creación de un conjunto de datos que genere un rendimiento O (N ^ 2). La 'mediana de tres' (primero, último, medio) también es una forma de evitar problemas. Sin embargo, tenga cuidado con el rendimiento relativo de las comparaciones; si sus comparaciones son costosas, entonces Mo3 hace más comparaciones que elegir (un solo valor pivote) al azar. Los registros de bases de datos pueden ser costosos de comparar.
Actualización: Conversión de comentarios en respuesta.
mdkess afirmó:
A lo que respondí:
El análisis del algoritmo de búsqueda de Hoare con una partición mediana de tres (1997) de P Kirschenhofer, H Prodinger, C Martínez respalda su afirmación (que la 'mediana de tres' son tres elementos aleatorios).
Hay un artículo descrito en portal.acm.org que trata sobre 'La permutación del peor caso para la clasificación rápida de mediana de tres' por Hannu Erkiö, publicado en The Computer Journal, Vol 27, No 3, 1984. [Actualización 2012-02- 26: Tengo el texto del artículo . La sección 2 'El algoritmo' comienza: ' Al usar la mediana del primer, medio y último elemento de A [L: R], se pueden lograr particiones eficientes en partes de tamaños bastante iguales en la mayoría de las situaciones prácticas. 'Por lo tanto, está discutiendo el enfoque de Mo3 primero-medio-último.]
Otro artículo breve que es interesante es el de MD McIlroy, "A Killer Adversary for Quicksort" , publicado en Software-Practice and Experience, vol. 29 (0), 1–4 (0 1999). Explica cómo hacer que casi cualquier Quicksort se comporte de forma cuadrática.
AT&T Bell Labs Tech Journal, Oct 1984 "Teoría y práctica en la construcción de una rutina de clasificación de trabajo" afirma "Hoare sugirió dividir alrededor de la mediana de varias líneas seleccionadas al azar. Sedgewick recomendó elegir la mediana de la primera [. ..] último [...] y medio ". Esto indica que ambas técnicas para 'mediana de tres' son conocidas en la literatura. (Actualización 2014-11-23: el artículo parece estar disponible en IEEE Xplore o en Wiley , si es miembro o está dispuesto a pagar una tarifa).
'Engineering a Sort Function' de JL Bentley y MD McIlroy, publicado en Software Practice and Experience, Vol 23 (11), noviembre de 1993, entra en una discusión extensa de los problemas, y eligieron un algoritmo de partición adaptativo basado en parte en el tamaño del conjunto de datos. Hay mucha discusión sobre las compensaciones de varios enfoques.
Una búsqueda en Google de 'mediana de tres' funciona bastante bien para un mayor seguimiento.
Gracias por la información; Solo me había encontrado con la 'mediana de tres' determinista antes.
fuente
Je, acabo de dar esta clase.
Hay varias opciones.
Simple: elija el primer o último elemento del rango. (malo en la entrada parcialmente ordenada) Mejor: Elija el elemento en el medio del rango. (mejor en entradas parcialmente ordenadas)
Sin embargo, elegir cualquier elemento arbitrario corre el riesgo de dividir mal la matriz de tamaño n en dos matrices de tamaño 1 y n-1. Si lo hace con suficiente frecuencia, su clasificación rápida corre el riesgo de convertirse en O (n ^ 2).
Una mejora que he visto es elegir la mediana (primero, último, medio); En el peor de los casos, todavía puede ir a O (n ^ 2), pero probabilísticamente, este es un caso raro.
Para la mayoría de los datos, basta con elegir el primero o el último. Pero, si encuentra que se encuentra con los peores escenarios a menudo (entrada parcialmente ordenada), la primera opción sería elegir el valor central (que es un pivote estadísticamente bueno para datos parcialmente ordenados).
Si todavía tiene problemas, siga la ruta mediana.
fuente
Nunca elija un pivote fijo; esto puede ser atacado para explotar el peor tiempo de ejecución O (n ^ 2) de su algoritmo, que solo está buscando problemas. El peor tiempo de ejecución de Quicksort ocurre cuando la partición da como resultado una matriz de 1 elemento y una matriz de n-1 elementos. Suponga que elige el primer elemento como partición. Si alguien alimenta una matriz a su algoritmo que está en orden decreciente, su primer pivote será el más grande, por lo que todo lo demás en la matriz se moverá a la izquierda. Luego, cuando recurras, el primer elemento volverá a ser el más grande, así que una vez más pones todo a la izquierda, y así sucesivamente.
Una mejor técnica es el método de la mediana de 3, en el que se seleccionan tres elementos al azar y se elige el medio. Sabes que el elemento que elijas no será el primero ni el último, pero también, según el teorema del límite central, la distribución del elemento medio será normal, lo que significa que tenderás hacia el medio (y por tanto , n lg n tiempo).
Si absolutamente desea garantizar el tiempo de ejecución O (nlgn) para el algoritmo, el método de columnas de 5 para encontrar la mediana de una matriz se ejecuta en el tiempo O (n), lo que significa que la ecuación de recurrencia para la ordenación rápida en el peor de los casos será sea T (n) = O (n) (encuentre la mediana) + O (n) (partición) + 2T (n / 2) (recursiva izquierda y derecha). Según el Teorema principal, esto es O (n lg n) . Sin embargo, el factor constante será enorme, y si el rendimiento en el peor de los casos es su principal preocupación, utilice una ordenación combinada en su lugar, que es solo un poco más lenta que la ordenación rápida en promedio y garantiza el tiempo O (nlgn) (y será mucho más rápido que este rápido ordenamiento medio cojo).
Explicación del algoritmo de la mediana de medianas
fuente
No intente ser demasiado inteligente y combine estrategias de pivote. Si combinó la mediana de 3 con un pivote aleatorio eligiendo la mediana del primero, el último y un índice aleatorio en el medio, seguirá siendo vulnerable a muchas de las distribuciones que envían una mediana de 3 cuadráticas (por lo que en realidad es peor que pivote aleatorio simple)
Por ejemplo, una distribución de órganos de tubos (1,2,3 ... N / 2..3,2,1) primero y último será 1 y el índice aleatorio será un número mayor que 1, tomando la mediana da 1 ( ya sea primero o último) y se obtiene una partición desequilibrada externamente.
fuente
Es más fácil dividir la clasificación rápida en tres secciones haciendo esto
Es solo un poco más ineficaz que una función larga, pero es mucho más fácil de entender.
El código sigue:
fuente
Depende completamente de cómo se ordenen sus datos para empezar. Si cree que será pseudoaleatorio, su mejor opción es elegir una selección aleatoria o elegir el medio.
fuente
Si está ordenando una colección de acceso aleatorio (como una matriz), en general es mejor elegir el elemento físico del medio. Con esto, si la matriz está lista y ordenada (o casi ordenada), las dos particiones estarán casi uniformes y obtendrá la mejor velocidad.
Si está ordenando algo con acceso únicamente lineal (como una lista vinculada), entonces es mejor elegir el primer elemento, porque es el elemento de acceso más rápido. Aquí, sin embargo, si la lista ya está ordenada, está jodido: una partición siempre será nula y la otra lo tendrá todo, produciendo el peor momento.
Sin embargo, para una lista vinculada, elegir cualquier cosa que no sea la primera solo empeorará las cosas. Elija el elemento del medio en una lista, tendrá que recorrerlo en cada paso de la partición, agregando una operación O (N / 2) que se realiza logN veces, lo que hace que el tiempo total sea O (1.5 N * log N) y eso es si sabemos cuánto tiempo es la lista antes de comenzar; por lo general, no lo sabemos, por lo que tendríamos que recorrer todo el camino para contarlos, luego pasar a la mitad para encontrar el medio, luego pasar por un tercera vez para hacer la partición real: O (2.5N * log N)
fuente
Idealmente, el pivote debería ser el valor medio de toda la matriz. Esto reducirá las posibilidades de obtener el peor rendimiento posible.
fuente
La complejidad de la clasificación rápida varía mucho con la selección del valor de pivote. por ejemplo, si siempre elige el primer elemento como pivote, la complejidad del algoritmo se vuelve tan peor como O (n ^ 2). Aquí hay un método inteligente para elegir el elemento pivote: 1. Elija el primer, medio y último elemento de la matriz. 2. compare estos tres números y encuentre el número que sea mayor que uno y menor que otro, es decir, la mediana. 3. Haga de este elemento un elemento pivote.
La elección del pivote mediante este método divide la matriz en casi dos mitades y, por lo tanto, la complejidad se reduce a O (nlog (n)).
fuente
En promedio, la mediana de 3 es buena para n pequeña. La mediana de 5 es un poco mejor para n más grandes. El ninther, que es la "mediana de tres medianas de tres" es incluso mejor para n muy grande.
Cuanto más alto vaya con el muestreo, mejor obtendrá a medida que aumenta n, pero la mejora se ralentiza drásticamente a medida que aumenta las muestras. Y se incurre en los gastos generales de muestreo y clasificación de muestras.
fuente
Recomiendo usar el índice medio, ya que se puede calcular fácilmente.
Puede calcularlo redondeando (array.length / 2).
fuente
En una implementación verdaderamente optimizada, el método para elegir el pivote debería depender del tamaño de la matriz; para una matriz grande, vale la pena dedicar más tiempo a elegir un buen pivote. Sin hacer un análisis completo, supongo que "la mitad de los elementos O (log (n))" es un buen comienzo, y esto tiene la ventaja adicional de no requerir memoria adicional: usar tail-call en la partición más grande y en colocando particiones, usamos la misma memoria adicional O (log (n)) en casi todas las etapas del algoritmo.
fuente