Quicksort: Elegir el pivote

votos
94

Al implementar Quicksort, una de las cosas que debe hacer es elegir un pivote. Pero cuando miro el pseudocódigo como el siguiente, no está claro cómo debería 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))

¿Alguien puede ayudarme a comprender el concepto de elegir un pivote y si diferentes escenarios requieren diferentes estrategias?

Publicado el 02/10/2008 a las 20:37
fuente por usuario
En otros idiomas...                            


13 respuestas

votos
72

Elegir un pivote aleatorio minimiza la posibilidad de que se encuentre con el peor rendimiento de O (n 2 ) (siempre eligiendo primero o el último causaría el peor de los casos para los datos casi ordenados o casi revertidos). Elegir el elemento medio también sería aceptable en la mayoría de los casos.

Además, si está implementando esto usted mismo, existen versiones del algoritmo que funcionan en el lugar (es decir, sin crear dos listas nuevas y luego concatenarlas).

Respondida el 02/10/2008 a las 20:41
fuente por usuario

votos
47

Depende de tus requisitos. Elegir un pivote al azar hace que sea más difícil crear un conjunto de datos que genere O (N ^ 2) rendimiento. 'Mediana de tres' (primero, último, medio) es también 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 único valor de pivote) al azar. Los registros de la base de datos pueden ser costosos de comparar.


Actualización: Tirar comentarios en respuesta.

mdkess afirmó:

'Mediana de 3' NO es la primera en el medio. Elija tres índices aleatorios y tome el valor medio de esto. El objetivo es asegurarse de que su elección de pivotes no sea determinista; si lo es, los datos del caso más desfavorable pueden generarse fácilmente.

A lo que respondí:

  • Análisis del algoritmo de búsqueda de Hoare con partición de mediana de tres (1997) por P Kirschenhofer, H Prodinger, C Martínez respalda su afirmación (que la 'mediana de tres' es tres ítems aleatorios).

  • Hay un artículo descrito en portal.acm.org que trata sobre "La peor permutación de casos para Mediana-de-tres Quicksort" por Hannu Erkiö, publicado en The Computer Journal, Vol 27, No 3, 1984. [Actualización 2012-02- 26: Tengo el texto para el artículo . La sección 2 'El algoritmo' comienza: ' Al usar la mediana de los elementos primero, medio y último 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 primer enfoque de Mo3, el medio y el último.]

  • Otro breve artículo 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, octubre de 1984 "Teoría y práctica en la construcción de una rutina de clasificación de trabajo" afirma "Hoare sugirió la partición en torno a la mediana de varias líneas seleccionadas al azar. [...] Sedgewick recomendó elegir la mediana del primero [. ..] último [...] y medio ". Esto indica que ambas técnicas para 'mediana de tres' son conocidas en la literatura. (Actualización del 2014-11-23: el artículo parece estar disponible en IEEE Xplore o en Wiley , si tiene membresía 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 extensa discusión de los problemas, y ellos eligieron un algoritmo de partición adaptativo basado en parte en el tamaño del conjunto de datos. Existe una gran cantidad de discusiones sobre compensaciones para 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 antes con la 'mediana de tres' determinista.

Respondida el 02/10/2008 a las 20:42
fuente por usuario

votos
1

Si está ordenando una colección accesible al azar (como una matriz), lo mejor es elegir el elemento medio físico. Con esto, si la matriz está todo ordenado (o casi ordenado), las dos particiones estarán casi iguales, y obtendrá la mejor velocidad.

Si está ordenando algo solo con acceso lineal (como una lista enlazada), entonces es mejor elegir el primer elemento, porque es el elemento más rápido para acceder. Aquí, sin embargo, si la lista ya está ordenada, estás equivocado: una partición siempre será nula y la otra tendrá todo, produciendo el peor momento.

Sin embargo, para una lista enlazada, elegir cualquier cosa además de la primera, solo empeorará las cosas. Selecciona el elemento del medio en una lista listada, debe recorrerlo en cada paso de la partición - agregando una operación O (N / 2) que se realiza logN veces haciendo un tiempo total O (1.5 N * log N) y eso es si sabemos cuánto tiempo es la lista antes de comenzar, generalmente no, así que tendríamos que pasar todo el camino para contarlas, luego dar un paso hacia la mitad para encontrar el centro y luego tercera vez para hacer la partición real: O (2.5N * log N)

Respondida el 02/10/2008 a las 20:42
fuente por usuario

votos
1

Depende enteramente de cómo se ordenan sus datos para comenzar. Si crees que será pseudoaleatorio, la mejor opción es elegir una selección al azar o elegir la del medio.

Respondida el 02/10/2008 a las 20:46
fuente por usuario

votos
16

Je, acabo de enseñar esta clase.

Hay varias opciones.
Simple: elija el primer o el último elemento del rango. (Mal en la entrada parcialmente clasificada) Mejor: Elija el elemento en el medio del rango. (mejor en entrada parcialmente clasificada)

Sin embargo, elegir cualquier elemento arbitrario corre el riesgo de particionar mal la matriz de tamaño n en dos matrices de tamaño 1 y n-1. Si lo hace con la suficiente frecuencia, su solución rápida corre el riesgo de convertirse en O (n ^ 2).

Una mejora que he visto es escoger 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, elegir el primero o el último es suficiente. 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 los datos parcialmente ordenados).

Si aún te encuentras con problemas, entonces sigue la ruta de la mediana.

Respondida el 02/10/2008 a las 20:46
fuente por usuario

votos
8

Nunca elija un pivote fijo; puede atacarlo para aprovechar el tiempo de ejecución O (n ^ 2) del caso más desfavorable de su algoritmo, lo cual no hace más que plantear problemas. El peor tiempo de ejecución de Quicksort ocurre al particionar los resultados en una matriz de 1 elemento y una matriz de n-1 elementos. Supongamos que elige el primer elemento como su 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 de la misma. Luego, cuando repites, el primer elemento volverá a ser el más grande, así que una vez más, colocas todo a la izquierda, y así sucesivamente.

Una mejor técnica es el método de la mediana de 3, donde eliges tres elementos al azar y eliges el medio. Sabes que el elemento que elijas no será el primero ni el último, pero también, por el teorema del límite central, la distribución del elemento medio será normal, lo que significa que tenderás hacia el centro (y por lo tanto , n lg n tiempo).

Si desea garantizar el tiempo de ejecución de O (nlgn) para el algoritmo, el método de columnas de 5 para encontrar la mediana de una matriz se ejecuta en tiempo O (n), lo que significa que la ecuación de recurrencia para la conexión rápida en el peor de los casos ser T (n) = O (n) (encontrar la mediana) + O (n) (partición) + 2T (n / 2) (recursivo a la izquierda y a la derecha.) Según el teorema maestro, esto es O (n lg n) . Sin embargo, el factor constante será enorme, y si el peor de los casos es su principal preocupación, use una combinación de fusión, que es solo un poco más lenta que la oferta promedio y garantiza el tiempo O (nlgn) (y será mucho más rápido) que este medio rápido cojo).

Explicación de la mediana del algoritmo de las medianas

Respondida el 25/10/2008 a las 22:50
fuente por usuario

votos
5

No intentes ponerte demasiado listo y combinar estrategias pivotantes. Si combina la mediana de 3 con pivote aleatorio eligiendo la mediana del primer, último y un índice aleatorio en el medio, entonces seguirá siendo vulnerable a muchas de las distribuciones que envían una mediana de 3 cuadráticos (por lo que es peor que pivote aleatorio simple)

Por ejemplo, una distribución de órgano de tubos (1,2,3 ... N / 2..3,2,1) primero y el último serán ambos 1 y el índice aleatorio será un número mayor que 1, tomando la mediana da 1 ( ya sea primero o último) y obtienes una partición extremadamente desequilibrada.

Respondida el 26/10/2008 a las 04:54
fuente por usuario

votos
1

Es más fácil de romper la ordenación rápida en tres secciones haciendo esto

  1. función de Exchange o elemento de datos de intercambio
  2. La función de partición
  3. El procesamiento de las particiones

Es sólo un poco más de una función inefficent largo, pero es mucho más fácil de entender.

Código sigue:

/* This selects what the data type in the array to be sorted is */

#define DATATYPE long

/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */

void swap (DATATYPE *x, DATATYPE *y){  
  DATATYPE Temp;

  Temp = *x;        // Hold current x value
  *x = *y;          // Transfer y to x
  *y = Temp;        // Set y to the held old x value
};


/* This is the partition code */

int partition (DATATYPE list[], int l, int h){

  int i;
  int p;          // pivot element index
  int firsthigh;  // divider position for pivot element

  // Random pivot example shown for median   p = (l+h)/2 would be used
  p = l + (short)(rand() % (int)(h - l + 1)); // Random partition point

  swap(&list[p], &list[h]);                   // Swap the values
  firsthigh = l;                                  // Hold first high value
  for (i = l; i < h; i++)
    if(list[i] < list[h]) {                 // Value at i is less than h
      swap(&list[i], &list[firsthigh]);   // So swap the value
      firsthigh++;                        // Incement first high
    }
  swap(&list[h], &list[firsthigh]);           // Swap h and first high values
  return(firsthigh);                          // Return first high
};



/* Finally the body sort */

void quicksort(DATATYPE list[], int l, int h){

  int p;                                      // index of partition 
  if ((h - l) > 0) {
    p = partition(list, l, h);              // Partition list 
    quicksort(list, l, p - 1);        // Sort lower partion
    quicksort(list, p + 1, h);              // Sort upper partition
  };
};
Respondida el 10/03/2011 a las 03:19
fuente por usuario

votos
0

Idealmente, el pivote debe ser el valor medio en toda la matriz. Esto reducirá las posibilidades de conseguir un rendimiento peor caso.

Respondida el 17/04/2013 a las 15:57
fuente por usuario

votos
-1

En una aplicación realmente optimizada, el método para la elección de pivote debe depender del tamaño de la matriz - para una gran variedad, vale la pena dedicar más tiempo a la elección de un buen pivote. Sin hacer un análisis completo, yo supongo "medio de O (log (n)) Elementos" es un buen comienzo, y esto tiene la ventaja añadida de no requerir ninguna memoria adicional: El uso de llamada final en la partición más grande e in- lugar de partición, se utiliza la misma O (log (n)) de memoria extra en casi todas las etapas del algoritmo.

Respondida el 08/10/2013 a las 20:50
fuente por usuario

votos
0

La complejidad de ordenación rápida varía mucho con la selección del valor de pivote. por ejemplo, si siempre se elige primer elemento como un pivote, la complejidad del algoritmo se vuelve tan peor como O (n ^ 2). aquí es un método inteligente para elegir pivote element- 1. elegir el primero, medio, último elemento de la matriz. 2. comparar estos tres números y encontrar el número que es mayor que uno y menor que la mediana es decir, otra. 3. hacer este elemento como elemento de pivote.

la elección del pivote por este método se divide la matriz en casi dos medio y por lo tanto la complejidad se reduce a O (n log (n)).

Respondida el 05/12/2013 a las 06:05
fuente por usuario

votos
0

En promedio, la mediana de 3 es bueno para la pequeña n. La mediana de 5 es un poco mejor para mayor n. El ninther, que es el "promedio de tres medianas de tres" es aún mejor para n muy grande.

Cuanto más se asciende con el muestreo de la mejor que podemos encontrar a medida que n aumenta, pero la mejora ralentiza drásticamente a medida que aumenta las muestras. Y de incurrir en los gastos generales de muestreo y la clasificación de muestras.

Respondida el 19/10/2016 a las 07:04
fuente por usuario

votos
0

Te recomiendo usar el índice medio, ya que se puede calcular fácilmente.

Se puede calcular mediante el redondeo (Array.length / 2).

Respondida el 08/08/2017 a las 22:29
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more