2009-01-31 25 views
20

Estaba trabajando en la implementación de un quicksort ayer, y luego lo ejecuté, esperando un tiempo de ejecución más rápido que el Mergesort (que también había implementado). Ejecuté los dos, y aunque el quicksort fue más rápido para conjuntos de datos más pequeños < 100 elementos (y I hizo verifican que funciona), el mergesort se convirtió rápidamente en el algoritmo más rápido. Me habían enseñado que el quicksort es casi siempre "más rápido" que mergesort, y entiendo que haya algún debate sobre este tema, pero al menos esperaba que fuera más cercano. Para conjuntos de datos> 10000 elementos, el mergesort fue más de 4 veces más rápido. ¿Es esto esperado, o hay un error en mi código quicksort?Quicksort más lento que Mergesort?

mergesort:

public static void mergeSort(int[ ] e) 
{ 
    if (e.length <= 1) return; 
    int[] first = new int[e.length/2]; 
    int[] second = new int[e.length - first.length]; 
    System.arraycopy(e, 0, first, 0, first.length); 
    System.arraycopy(e, first.length, second, 0, second.length); 
    mergeSort(first); 
    mergeSort(second); 
    System.arraycopy(merge(first, second), 0, e, 0, e.length); 
} 

private static int[] merge(int[] first, int[] second) { 
    int iFirst = 0; 
    int iSecond = 0; 
    int iCombined = 0; 

    int[] combined = new int[first.length + second.length]; 
    while(iFirst < first.length && iSecond < second.length) { 
     if (first[iFirst] > second[iSecond]) { 
      combined[iCombined++] = second[iSecond++]; 
     } 
     else combined[iCombined++] = first[iFirst++]; 
    } 
    for(; iFirst < first.length; iFirst++) { 
     combined[iCombined++] = first[iFirst]; 
    } 
    for(; iSecond < second.length; iSecond++) { 
     combined[iCombined++] = second[iSecond]; 
    } 
    return combined; 
} 

clasificación rápida:

public static void quicksort(int[] a, int first, int last) { 
    if (first >= last) return; 

    int partitionIndex = partition(a, first, last); 
    quicksort(a, first, partitionIndex - 1); 
    quicksort(a, partitionIndex + 1, last); 
} 

public static int partition(int[] x, int first, int last) { 
    int left = first; 
    int right = last; 
    int pivot = x[first]; 
    int pivotIdx = first; 

    while(left <= right) { 
     while(left < x.length && x[left] <= pivot) left++; 
     while(right >= 0 && x[right] > pivot) right--; 
     if (left <= right) { 
      int temp = x[left]; 
      x[left] = x[right]; 
      x[right] = temp; 
     } 
    } 
    pivotIdx = right; 
    x[first] = x[right]; 
    x[pivotIdx] = pivot; 
    return pivotIdx; 
} 

Respuesta

2

Con base en esta wikipedia article Se espera que los resultados.

+1

No, no lo son. Un quicksort sin errores es mucho más rápido. –

+0

@Stephan Eggermont: ¿Puedes señalar los errores en la implementación de John? – Giorgio

+0

ver mi respuesta arriba –

1

Me imagino que accediendo directamente a la memoria, usando C por ejemplo, uno puede mejorar el rendimiento de Quicksort más de lo que es posible con Mergesort.

Otra razón es que Mergesort necesita más memoria porque es difícil implementarla como una ordenación in situ.

Y específicamente para su implementación puede mejorar la elección del pivote, hay muchos algoritmos diferentes para encontrar un buen pivote.

Como se puede ver on wikipedia, uno puede implementar Quicksort de diferentes maneras.

0

¿Los conjuntos de datos eran lo suficientemente aleatorios? ¿Estaban parcialmente clasificados?

que podrían afectar a la velocidad de la especie ...

Al igual que para la partición de la QuickSort(), que no iría a lo largo de si los números están en el orden establecido, hasta que encuentre uno que no lo es.

0

Puede depender del tipo de datos que esté ordenando para la prueba (listas ya ordenadas, aleatorizadas, ordenadas inversamente). Además, es probable que el quicksort sea más rápido en general si elige un pivote aleatorio en lugar de usar el primer elemento.

2

El peor caso de Merge sort es el caso promedio de quicksort, por lo que si no tiene una buena implementación, el tipo de combinación será más rápido en general. Hacer que quicksort trabaje rápido es evitar casos por debajo del promedio. Elige un pivote mejor (la mediana de 3 ayuda) y verás una diferencia.

+0

No entiendo la argumentación. Si el quicksort es O (n log (n)) _on average_ es porque existen casos sub promedios y no puede evitarlos, independientemente de cómo elija su pivote. ¿O estoy pasando por alto algo? – Giorgio

3

Una de las ventajas de quicksort para tamaños de matriz relativamente pequeños es solo un artefacto de la implementación de hardware.

En matrices, la ordenación rápida se puede realizar in situ, lo que significa que está leyendo y escribiendo en la misma área de memoria. Mergesort, por otro lado, generalmente requiere asignar nuevos buffers, lo que significa que su acceso a la memoria está más extendido. Puede ver estos dos comportamientos en sus implementaciones de ejemplo.

Como resultado, para conjuntos de datos relativamente pequeños, es más probable que quicksort obtenga visitas de caché y, por lo tanto, tiende a ejecutarse más rápido en la mayoría del hardware.

Mergesort sigue siendo una solución bastante buena para grandes conjuntos de datos u otras estructuras de datos, como listas vinculadas, como lo confirman sus experimentos.

10

De hecho, escribí un "programa demo de comparación comparativo de listas enlazadas" en C y llegué a una conclusión similar (que mergesort superará el quicksort para la mayoría de los usos), aunque me han dicho que el enlace no se usa generalmente para enlaces listas de todos modos. Me gustaría señalar que la elección de los valores de pivote es un factor monstruoso: mi versión inicial usó un nodo aleatorio como pivote, y cuando lo refiné un poco para tomar una media de dos nodos (aleatorios), el tiempo de ejecución para 1000000 registros pasaron de más de 4 minutos a menos de 10 segundos, poniéndolo a la par con mergesort.

Mergesort y quicksort tienen el mismo gran O mejor caso (n * log (n)) ya pesar de lo que las personas pueden intentar afirmar, el gran O realmente se trata de recuento de iteraciones y no de comparación. La diferencia más grande que se puede producir entre los dos siempre será en detrimento del quicksort, e involucra listas que ya están ordenadas o contienen una gran cantidad de vínculos (cuando quicksort lo hace mejor que mergesort, la diferencia no será casi tan bueno). Esto se debe a que los vínculos o los segmentos ya ordenados optimizan directamente a través de mergesort; cuando vuelvan a fusionarse dos listas divididas, si una lista ya contiene todos los valores más pequeños, todos los valores de la izquierda se compararán uno a la vez con el primer elemento de la derecha y luego (dado que las listas devueltas tienen una orden interna) no hay más comparaciones necesidad de hacerse y el derecho es simplemente iterado en el extremo. Es decir, el número de iteraciones se mantendrá constante, pero el número de comparaciones se reducirá a la mitad. Si está hablando de tiempo real y está ordenando cadenas, son las comparaciones las que son caras.

Los vínculos y los segmentos ya clasificados en el quicksort pueden generar listas desequilibradas si el valor de pivote no se determina cuidadosamente y las listas desequilibradas (por ejemplo, una a la derecha, diez a la izquierda) son las que causan la desaceleración. Por lo tanto, si puede hacer que su quicksort funcione tan bien en una lista ya ordenada como en una lista ramificada, tiene un buen método para encontrar el pivote.

Si está interesado, el programa de demostración produce una salida como ésta:

[root~/C] ./a.out -1 3 
Using "", 0 records 
Primary Criteria offset=128 

Command (h for help, Q to quit): N 
How many records? 4000000 
New list is 562500.00 kb 

Command (h for help, Q to quit): m 

Mergesorting..............3999999 function calls 
123539969 Iterations  Comparison calls: 82696100 
Elapsed time: 0 min 9 sec 


Command (h for help, Q to quit): S 
Shuffled. 

Command (h for help, Q to quit): q 

Quicksorting..............4000000 function calls 
190179315 Iterations  Comparison calls: 100817020 
Elapsed time: 0 min 23 sec 

Altho sin los Kolors Krazy. Hay algo más al respecto por mi alrededor de la mitad del camino this page.

ps. ninguna clase requiere memoria adicional con la lista vinculada.

+1

Esta es una respuesta irrelevante, ya que utiliza una tienda de respaldo de lista vinculada –

+0

Usted dijo que "Mergesort y quicksort tienen la misma gran O mejor caso (n * log (n))", pero quiero mencionar que Big O es estrictamente para el límite superior del tiempo de ejecución (es el caso más desfavorable) Big Omega describe el límite inferior (el mejor de los casos) – talloaktrees

1

(1) Hay un qsort algo, utilizado por C qsort(), que no requiere memoria extra. Este fue probablemente inventado por Hoare. Esto hace que qsort() rápido en C.

(2) La aleatorización de los datos antes de ejecutar qsort casi siempre acelerarlo.

(3) seleccionar los datos mediana para pivote puede hacer que sea más rápido,

+0

Incluso si se llama qsort() probablemente no sea una ordenación rápida pura. – Giorgio

1

Esto es consistente con el análisis de los algoritmos. Merge-sort se garantiza O (nlogn) para cualquier entrada y para cada tiempo de ejecución. Quicksort es el mejor de los casos O (nlogn) y el caso medio O (nlogn), pero el peor de los casos O (n^2), por lo que la ejecución promedio será entre O (nlogn) y O (n^2).

Quicksort es el mejor algoritmo de caso general porque tiene una sobrecarga baja, por lo que tiene una buena velocidad para valores de n hasta aproximadamente 10000 y sigue siendo un buen tiempo de ejecución para valores arbitrariamente astronómicos de n. Merge-sort tiene la desafortunada sobrecarga de escribir un marco de pila, requerido por cada llamada recursiva. Por lo tanto, para valores bajos de n tiene una c atrozmente alta en RT = cnlogn y no es el método de clasificación general preferido.

Editar: Software Monkey señaló una contradicción: Quicksort promedia O (nlogn) para entrada aleatoria, pero O (n^2) peor caso. Por lo tanto, en realidad está un poco vinculado por la entropía de sus datos, o puede elegir el pivote de forma aleatoria. Sin embargo, aún podría estar fuera un poco.

+0

Quicksort no puede ser tanto el "caso medio O (nlogn)" como el "promedio ... entre O (nlogn) y O (n^2)". –

+0

lo siento promedio O (nlogn) para la entrada aleatoria, pero O (n^2) peor caso Por lo tanto, de hecho está unida por la entropía – Overflown

0

Para un buen desempeño de la clasificación rápida, es importante no recursiva hasta el fondo de las listas de longitud 1

Se debe tener en cuenta la clasificación de las listas 2, 3 y hasta 4 como ifs anidados intercambiando si es necesario. Háganos saber cómo cambia el rendimiento.

1

Si implementa el ordenamiento de montones como el algoritmo de clasificación base en el peor de los casos, obtiene un algoritmo theta (n log n).

Si no necesita una clasificación estable, y no ordena una lista vinculada, creo que sería lo más rápido que podría ir.

Merge sort

4

por fusión es mucho más lenta para datos basados ​​matriz al azar, siempre que se ajuste en la RAM. Esta es la primera vez que lo veo debatido.

  • qsort el subcampo más corto primero.
  • interruptor de la inserción ordenar por debajo de 5-25 elementos
  • hacer una selección de giro normal de

Su qsort es muy lento, ya que trata de crear particiones y matrices qsort de longitud 2 y 3.

+1

+1 Para el cambio a la ordenación por inserción, debería dar una buena mejora – helpermethod

+1

Cualquier razón por la que sugerir la optimización de la implementación de ordenación rápida y no la implementación de clasificación de fusión? La clasificación por fusión también puede beneficiarse al cambiar a ordenación por inserción (ver timsort como ejemplo). Por cierto, muchas implementaciones de lenguaje de programación usan una versión optimizada de tipo de combinación internamente: Java, Python, C con GNU libc ... Las últimas llamadas pares ordenan rápidamente "el algoritmo más lento". –

1

I ejecutaron pruebas similares y el ordenamiento rápido puro (con selección aleatoria de pivote) resultó ser mucho más lento que el tipo de fusión para arreglos grandes.

Elegir el pivote como la mediana del primer, medio y último elemento mejoró el rendimiento de la ordenación rápida, pero el ordenamiento rápido fue definitivamente peor que la ordenación por fusión en arreglos grandes (> 100000 elementos).

Vi una gran mejora cuando implementé intro-sort, es decir, una ordenación rápida que vuelve a la ordenación de pila si la profundidad de recursión excede un cierto umbral. Mi implementación de intro-sort fue casi tan rápida como mi implementación de tipo de fusión. Por supuesto, intro-sort ya no es clasificación rápida pura ya que utiliza la ordenación de montón para devolver la complejidad a n log (n) cuando la ordenación rápida pura llega a algunos datos erróneos. Puedo publicar los resultados si estás interesado.

1

Creo que siempre que los datos quepan en la memoria, una buena implementación de la ordenación por fusión funciona mejor que una buena implementación de clasificación rápida.

Una de las implementaciones más utilizadas de qsort(), glibc qsort(), utiliza internamente la ordenación por fusión para la mayoría de los casos cuando los datos encajan en la memoria.Este tipo de combinación asigna un espacio de memoria temporal utilizado para la fusión, lo que agrega cierta sobrecarga de memoria, pero la mayoría de las veces supera a su propia implementación interna de quicksort con una buena selección de pivote y optimización. glibc solo usa quicksort cuando los datos y la memoria temporal para ordenar por fusión no caben en la memoria.

He medido el rendimiento de esas dos implementaciones en mi máquina con una CPU de 2.1 GHz con varios GB de RAM. Las entradas se generan con un generador pseudoaleatorio, y cada clave es un entero sin signo de 32 bits, lo que significa un poco más de ciclos de comparación que la comparación entera debido a la interfaz de la función de comparación.

de Merge para ordenar:

2 MB, time_diff 165.156000 ms, 78.752518 ns per byte 
4 MB, time_diff 344.298000 ms, 82.087040 ns per byte 
8 MB, time_diff 730.926000 ms, 87.133169 ns per byte 
16 MB, time_diff 1541.215000 ms, 91.863573 ns per byte 
32 MB, time_diff 3088.924000 ms, 92.057109 ns per byte 
64 MB, time_diff 6262.868000 ms, 93.324006 ns per byte 
128 MB, time_diff 12887.018000 ms, 96.015766 ns per byte 
256 MB, time_diff 26731.597000 ms, 99.582959 ns per byte 

Por ordenación rápida:

2 MB, time_diff 243.519000 ms, 116.118908 ns per byte 
4 MB, time_diff 504.975000 ms, 120.395422 ns per byte 
8 MB, time_diff 1075.276000 ms, 128.182888 ns per byte 
16 MB, time_diff 2183.865000 ms, 130.168498 ns per byte 
32 MB, time_diff 4343.993000 ms, 129.461080 ns per byte 
64 MB, time_diff 8714.166000 ms, 129.851192 ns per byte 
128 MB, time_diff 17881.344000 ms, 133.226395 ns per byte 
256 MB, time_diff 36751.029000 ms, 136.908252 ns per byte 

Se puede ver que existen claras diferencias en el rendimiento entre los dos aplicación y por qué mergesort es preferible a la clasificación rápida en tan ampliamente utiliza la implementación qsort. La razón principal detrás de esta diferencia parece ser que el género rápido tiene un 10-20% más de comparaciones que el tipo de combinación, debido a la división desigual en cada paso.

Cuestiones relacionadas