Clasificación rápida vs clasificación heap


Respuestas:


60

Este papel tiene algunos análisis.

Además, de Wikipedia:

El competidor más directo de quicksort es heapsort. Heapsort suele ser algo más lento que quicksort, pero el peor tiempo de ejecución es siempre Θ (nlogn). La ordenación rápida suele ser más rápida, aunque existe la posibilidad de un rendimiento en el peor de los casos, excepto en la variante introsort, que cambia a la ordenación en pila cuando se detecta un caso incorrecto. Si se sabe de antemano que será necesario ordenar heapsort, usarlo directamente será más rápido que esperar a que introsort cambie a él.


12
Puede ser importante tener en cuenta que en las implementaciones típicas, ni la ordenación rápida ni la ordenación en memoria son clases estables.
MjrKusanagi

@DVK, de acuerdo con su enlace cs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html , la ordenación del montón toma 2.842 comparaciones para n = 100, pero toma 53.113 comparaciones para n = 500. Y eso implica que la relación entre n = 500 yn = 100 es 18 veces, y NO coincide con el algoritmo de ordenación del montón con la complejidad O (N logN). Supongo que es bastante probable que su implementación de ordenación del montón tenga algún tipo de error en su interior.
DU Jiaen

@DUJiaen: recuerde que O () trata sobre el comportamiento asintótico en N grande y tiene un posible multiplicador
DVK

Esto NO está relacionado con el multiplicador. Si un algoritmo tiene una complejidad de O (N log N), debe seguir una tendencia de Tiempo (N) = C1 * N * log (N). Y si toma Time (500) / Time (100), es obvio que C1 desaparecerá y el resultado debería cerrarse a (500 log500) / (100 log100) = 6.7 Pero desde su enlace, es 18, que es demasiado fuera de escala.
DU Jiaen

2
El enlace está muerto
PlsWork

125

Heapsort está garantizado por O (N log N), lo que es mucho mejor que el peor de los casos en Quicksort. Heapsort no necesita más memoria para que otra matriz coloque datos ordenados como lo necesita Mergesort. Entonces, ¿por qué las aplicaciones comerciales se quedan con Quicksort? ¿Qué Quicksort tiene que sea tan especial sobre otras implementaciones?

Yo mismo he probado los algoritmos y he visto que Quicksort tiene algo especial. Se ejecuta rápido, mucho más rápido que los algoritmos Heap y Merge.

El secreto de Quicksort es: casi no realiza intercambios de elementos innecesarios. El intercambio lleva mucho tiempo.

Con Heapsort, incluso si todos sus datos ya están ordenados, intercambiará el 100% de los elementos para ordenar la matriz.

Con Mergesort, es aún peor. Va a escribir el 100% de los elementos en otra matriz y volver a escribirla en la original, incluso si los datos ya están ordenados.

Con Quicksort no intercambia lo que ya está ordenado. Si sus datos están completamente ordenados, ¡no intercambia casi nada! Aunque hay mucho alboroto por el peor de los casos, una pequeña mejora en la elección del pivote, que no sea obtener el primer o el último elemento de la matriz, puede evitarlo. Si obtiene un pivote del elemento intermedio entre el primer, último y medio elemento, es suficiente para evitar el peor de los casos.

Lo que es superior en Quicksort no es el peor de los casos, ¡sino el mejor de los casos! En el mejor de los casos, haces la misma cantidad de comparaciones, está bien, pero no cambias casi nada. En el caso medio, intercambia parte de los elementos, pero no todos, como en Heapsort y Mergesort. Eso es lo que le da a Quicksort el mejor momento. Menos intercambio, más velocidad.

La siguiente implementación en C # en mi computadora, que se ejecuta en modo de liberación, supera a Array. Ordena 3 segundos con pivote medio y 2 segundos con pivote mejorado (sí, hay una sobrecarga para obtener un buen pivote).

static void Main(string[] args)
{
    int[] arrToSort = new int[100000000];
    var r = new Random();
    for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);

    Console.WriteLine("Press q to quick sort, s to Array.Sort");
    while (true)
    {
        var k = Console.ReadKey(true);
        if (k.KeyChar == 'q')
        {
            // quick sort
            Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            QuickSort(arrToSort, 0, arrToSort.Length - 1);
            Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
        else if (k.KeyChar == 's')
        {
            Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            Array.Sort(arrToSort);
            Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
    }
}

static public void QuickSort(int[] arr, int left, int right)
{
    int begin = left
        , end = right
        , pivot
        // get middle element pivot
        //= arr[(left + right) / 2]
        ;

    //improved pivot
    int middle = (left + right) / 2;
    int
        LM = arr[left].CompareTo(arr[middle])
        , MR = arr[middle].CompareTo(arr[right])
        , LR = arr[left].CompareTo(arr[right])
        ;
    if (-1 * LM == LR)
        pivot = arr[left];
    else
        if (MR == -1 * LR)
            pivot = arr[right];
        else
            pivot = arr[middle];
    do
    {
        while (arr[left] < pivot) left++;
        while (arr[right] > pivot) right--;

        if(left <= right)
        {
            int temp = arr[right];
            arr[right] = arr[left];
            arr[left] = temp;

            left++;
            right--;
        }
    } while (left <= right);

    if (left < end) QuickSort(arr, left, end);
    if (begin < right) QuickSort(arr, begin, right);
}

10
+1 para consideraciones sobre el no. de operaciones de intercambio, lectura / escritura requeridas para diferentes algoritmos de clasificación
ycy

2
Para cualquier estrategia de selección de pivote de tiempo constante determinista, puede encontrar una matriz que produzca el peor de los casos O (n ^ 2). No es suficiente eliminar solo lo mínimo. Debe elegir de manera confiable pivotes que estén dentro de una determinada banda pecrentil.
Antimony

1
Tengo curiosidad por saber si este es el código exacto que ejecutó para sus simulaciones entre su ordenación rápida codificada a mano y Array.sort integrado en C #. Probé este código y en todas mis pruebas, en el mejor de los casos, la clasificación rápida codificada a mano fue la misma que Array.sort. Una cosa que controlé en mi prueba de esto fue hacer dos copias idénticas de la matriz aleatoria. Después de todo, una asignación al azar determinada podría ser potencialmente más favorable (inclinarse hacia el mejor de los casos) que otra asignación al azar. Así que pasé los conjuntos idénticos a través de cada uno. Array.sort empatado o batido cada vez (suelte la compilación por cierto).
Chris

1
Merge sort no tiene que copiar el 100% de los elementos, a menos que sea una implementación muy ingenua de un libro de texto. Es sencillo implementarlo, de modo que solo necesita copiar el 50% de ellos (el lado izquierdo de las dos matrices fusionadas). También es trivial posponer la copia hasta que realmente tenga que "intercambiar" dos elementos, por lo que con los datos ya ordenados no tendrá sobrecarga de memoria. Entonces, incluso el 50% es en realidad el peor de los casos, y puede tener algo entre eso y el 0%.
ddekany

1
@MarquinhoPeli Quería decir que solo necesitas un 50% más de memoria disponible en comparación con el tamaño de la lista ordenada, no el 100%, lo que parece ser un error común. Así que estaba hablando del uso máximo de memoria. No puedo dar un enlace, pero es fácil ver si intenta fusionar las dos mitades ya ordenadas de una matriz en su lugar (solo la mitad izquierda tiene el problema de sobrescribir elementos que aún no ha consumido). La cantidad de copia de memoria que tiene que hacer durante todo el proceso de clasificación es otra cuestión, pero obviamente el peor de los casos no puede estar por debajo del 100% para ningún algoritmo de clasificación.
ddekany

15

Para la mayoría de las situaciones, tener rápido frente a un poco más rápido es irrelevante ... simplemente nunca querrás que ocasionalmente se vuelva demasiado lento. Aunque puede modificar QuickSort para evitar situaciones lentas, pierde la elegancia del QuickSort básico. Entonces, para la mayoría de las cosas, prefiero HeapSort ... puedes implementarlo con toda su elegancia simple y nunca obtener un tipo lento.

Para situaciones en las que SÍ desea la máxima velocidad en la mayoría de los casos, es posible que se prefiera QuickSort sobre HeapSort, pero ninguna de las dos puede ser la respuesta correcta. Para situaciones de velocidad crítica, vale la pena examinar de cerca los detalles de la situación. Por ejemplo, en algunos de mis códigos de velocidad crítica, es muy común que los datos ya estén ordenados o casi ordenados (está indexando múltiples campos relacionados que a menudo se mueven hacia arriba y hacia abajo juntos O se mueven hacia arriba y hacia abajo uno frente al otro, así que una vez que ordena por uno, los otros se ordenan o se ordenan al revés o se cierran ... cualquiera de los cuales puede matar a QuickSort). Para ese caso, no implementé ninguno ... en su lugar, implementé SmoothSort de Dijkstra ... una variante de HeapSort que es O (N) cuando ya está ordenada o casi ordenada ... no es tan elegante, no es demasiado fácil de entender, pero rápido ... leerhttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF si quieres algo un poco más difícil de codificar.


6

Los híbridos en lugar Quicksort-Heapsort también son realmente interesantes, ya que la mayoría de ellos solo necesita comparaciones n * log n en el peor de los casos (son óptimos con respecto al primer término de los asintóticos, por lo que evitan los peores escenarios de Quicksort), O (log n) extra-espacio y conservan al menos "la mitad" del buen comportamiento de Quicksort con respecto al conjunto de datos ya ordenados. Dikert y Weiss presentan un algoritmo extremadamente interesante en http://arxiv.org/pdf/1209.4214v1.pdf :

  • Seleccione un pivote p como la mediana de una muestra aleatoria de elementos sqrt (n) (esto se puede hacer en un máximo de 24 comparaciones sqrt (n) mediante el algoritmo de Tarjan & co, o 5 comparaciones sqrt (n) a través de la araña mucho más complicada -algoritmo de fábrica de Schonhage);
  • Divida su matriz en dos partes como en el primer paso de Quicksort;
  • Apila la parte más pequeña y usa O (log n) bits extra para codificar un montón en el que cada hijo izquierdo tiene un valor mayor que su hermano;
  • Extraiga recursivamente la raíz del montón, tamice la laguna dejada por la raíz hasta que llegue a una hoja del montón, luego llene la laguna con un elemento apropiado tomado de la otra parte de la matriz;
  • Se repite sobre la parte restante no ordenada de la matriz (si se elige p como la mediana exacta, no hay recursividad en absoluto).

2

Comp. entre quick sorty merge sortdado que ambos son un tipo de clasificación en el lugar, hay una diferencia entre el tiempo de ejecución del caso wrost del tiempo de ejecución del caso wrost para la clasificación rápida es O(n^2)y para la clasificación del montón sigue siendoO(n*log(n)) y para una cantidad promedio de datos la clasificación rápida será más útil. Dado que es un algoritmo aleatorio, la probabilidad de obtener respuestas correctas. en menos tiempo dependerá de la posición del elemento pivote que elija.

Entonces un

Buena llamada: los tamaños de L y G son cada uno menos de 3s / 4

Una mala llamada: uno de L y G tiene un tamaño superior a 3s / 4

para una pequeña cantidad, podemos optar por la ordenación por inserción y para una gran cantidad de datos, por la ordenación por montón.


Aunque la ordenación por combinación se puede implementar con la ordenación en el lugar, la implementación es compleja. AFAIK, la mayoría de las implementaciones de ordenación por fusión no están en el lugar, pero son estables.
MjrKusanagi

2

Heapsort tiene la ventaja de tener el peor caso de ejecución de O (n * log (n)), por lo que en los casos en los que es probable que quicksort tenga un rendimiento deficiente (en general, conjuntos de datos ordenados), se prefiere mucho el heapsort.


4
Quicksort solo funciona mal en un conjunto de datos mayormente ordenados si se elige un método de elección de pivote pobre. Es decir, el método de elección de pivote incorrecto sería elegir siempre el primer o último elemento como pivote. Si se elige un pivote aleatorio cada vez y se usa un buen método para manejar elementos repetidos, la posibilidad de una ordenación rápida en el peor de los casos es muy pequeña.
Justin Peel

1
@Justin - Eso es muy cierto, estaba hablando de una implementación ingenua.
Zellio

1
@Justin: Cierto, pero la posibilidad de una desaceleración importante siempre está ahí, por pequeña que sea. Para algunas aplicaciones, es posible que desee asegurar el comportamiento de O (n log n), incluso si es más lento.
David Thornley

2

Bueno, si va al nivel de arquitectura ... usamos la estructura de datos de la cola en la memoria caché. De modo que lo que esté disponible en la cola se ordenará. Como en la clasificación rápida, no tenemos problemas para dividir la matriz en cualquier longitud ... pero en el montón ordenar (mediante el uso de una matriz) puede suceder que el padre no esté presente en la submatriz disponible en la caché y luego tenga que traerla a la memoria caché ... lo cual lleva mucho tiempo. ¡¡Esa clasificación rápida es la mejor !! 😀


1

Heapsort crea un montón y luego extrae repetidamente el elemento máximo. Su peor caso es O (n log n).

Pero si ve el peor caso de clasificación rápida , que es O (n2), se daría cuenta de que la clasificación rápida sería una opción no tan buena para datos grandes.

Así que esto hace que la clasificación sea algo interesante; Creo que la razón por la que existen tantos algoritmos de clasificación en la actualidad es porque todos ellos son 'mejores' en sus mejores lugares. Por ejemplo, la clasificación de burbujas puede superar la clasificación rápida si los datos están clasificados. O si sabemos algo sobre los elementos que se van a clasificar, probablemente podamos hacerlo mejor.

Es posible que esto no responda a su pregunta directamente, pensé en agregar mis dos centavos.


1
Nunca use la clasificación de burbujas. Si cree razonablemente que sus datos se ordenarán, puede usar la ordenación por inserción o incluso probar los datos para ver si están ordenados. No utilices bubbleort.
vy32

Si tiene un conjunto de datos ALEATORIO muy grande, su mejor opción es la clasificación rápida. Si está parcialmente ordenado, entonces no, pero si comienza a trabajar con grandes conjuntos de datos, debe saber al menos esto sobre ellos.
Kobor42

1

Heap Sort es una apuesta segura cuando se trata de entradas muy grandes. El análisis asintótico revela el orden de crecimiento de Heapsort en el peor de los casos Big-O(n logn), que es mejor que el de Quicksort en el Big-O(n^2)peor de los casos. Sin embargo, Heapsort es algo más lento en la práctica en la mayoría de las máquinas que una ordenación rápida bien implementada. Heapsort tampoco es un algoritmo de clasificación estable.

La razón por la que heapsort es más lento en la práctica que quicksort se debe a la mejor localidad de referencia (" https://en.wikipedia.org/wiki/Locality_of_reference ") en quicksort, donde los elementos de datos están dentro de ubicaciones de almacenamiento relativamente cercanas. Los sistemas que exhiben una fuerte localidad de referencia son excelentes candidatos para la optimización del rendimiento. Sin embargo, la clasificación de pilas se ocupa de saltos más grandes. Esto hace que la clasificación rápida sea más favorable para entradas más pequeñas.


2
La clasificación rápida tampoco es estable.
Antimony

1

Para mí, hay una diferencia fundamental entre heapsort y quicksort: este último usa una recursividad. En los algoritmos recursivos, el montón crece con el número de recursiones. Esto no importa si n es pequeño, pero ahora mismo estoy ordenando dos matrices con n = 10 ^ 9 !!. El programa toma casi 10 GB de RAM y cualquier memoria adicional hará que mi computadora comience a cambiar a la memoria del disco virtual. Mi disco es un disco RAM, pero cambiarlo hace una gran diferencia en la velocidad . Entonces, en un paquete de estadísticas codificado en C ++ que incluye matrices de dimensiones ajustables, con un tamaño desconocido de antemano para el programador, y un tipo de ordenación estadística no paramétrica, prefiero el heapsort para evitar retrasos en los usos con matrices de datos muy grandes.


1
Solo necesita memoria O (logn) en promedio. La sobrecarga de recursividad es trivial, asumiendo que no tienes mala suerte con los pivotes, en cuyo caso tienes problemas mayores de los que preocuparte.
Antimony

-1

Para responder a la pregunta original y abordar algunos de los otros comentarios aquí:

Acabo de comparar implementaciones de selección, rápida, fusión y ordenación por montones para ver cómo se comparan entre sí. La respuesta es que todos tienen sus desventajas.

TL; DR: Rápido es el mejor tipo de uso general (razonablemente rápido, estable y en su mayoría en el lugar). Personalmente, prefiero el tipo de pila a menos que necesite un tipo estable.

Selección - N ^ 2 - Realmente solo es bueno para menos de 20 elementos aproximadamente, entonces tiene un rendimiento superior. A menos que sus datos ya estén ordenados, o muy, muy cerca. N ^ 2 se vuelve muy lento muy rápido.

Rápido, en mi experiencia, no es realmente tan rápida todo el tiempo. Sin embargo, las ventajas de utilizar la clasificación rápida como clasificación general son que es razonablemente rápida y estable. También es un algoritmo in situ, pero como generalmente se implementa de forma recursiva, ocupará espacio adicional en la pila. También cae en algún lugar entre O (n log n) y O (n ^ 2). El tiempo en algunos tipos parece confirmar esto, especialmente cuando los valores caen dentro de un rango estrecho. Es mucho más rápido que el ordenamiento por selección en 10,000,000 elementos, pero más lento que fusionar o agrupar.

La ordenación por fusión está garantizada O (n log n) ya que su ordenación no depende de los datos. Simplemente hace lo que hace, independientemente de los valores que le haya dado. También es estable, pero los tipos muy grandes pueden arruinar tu pila si no tienes cuidado con la implementación. Hay algunas implementaciones complejas de ordenación por combinación en el lugar, pero generalmente necesita otra matriz en cada nivel para combinar sus valores. Si esas matrices viven en la pila, puede tener problemas.

La clasificación de montón es máxima O (n log n), pero en muchos casos es más rápida, dependiendo de qué tan lejos tenga que mover sus valores hacia arriba en el montón de log n deep. El montón se puede implementar fácilmente en el lugar en la matriz original, por lo que no necesita memoria adicional y es iterativo, por lo que no se preocupe por el desbordamiento de la pila mientras se recupera. La gran desventaja de la clasificación en montón es que no es una clasificación estable, lo que significa que está bien si la necesita.


La clasificación rápida no es una clasificación estable. Más allá de eso, preguntas de esta naturaleza fomentan respuestas basadas en opiniones y podrían llevar a editar guerras y argumentos. Las preguntas que requieren respuestas basadas en opiniones están explícitamente desalentadas por las directrices de la SO. Quienes respondan deben evitar la tentación de responderlas incluso si tienen una experiencia y sabiduría significativas en el área. Marcarlos para cerrarlos o esperar a que alguien con suficiente reputación los marque y los cierre. Este comentario no es un reflejo de su conocimiento o la validez de su respuesta.
MikeC
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.