Algoritmos de división y conquista: ¿por qué no dividir en más partes que dos?


33

En algoritmos de divide y vencerás, como quicksort y mergesort, la entrada generalmente se divide (al menos en textos introductorios) en dos , y los dos conjuntos de datos más pequeños se tratan de forma recursiva. Para mí tiene sentido que esto acelere la resolución de un problema si las dos mitades requieren menos de la mitad del trabajo de tratar con todo el conjunto de datos. Pero, ¿por qué no dividir el conjunto de datos en tres partes? Cuatro? n ?

Supongo que el trabajo de dividir los datos en muchos, muchos subconjuntos hace que no valga la pena, pero me falta la intuición para ver que uno debe detenerse en dos subconjuntos.

También he visto muchas referencias al quicksort de 3 vías. ¿Cuándo es esto más rápido? ¿Qué se usa en la práctica?


Intente crear un algoritmo similar a quicksort que divida una matriz en tres partes.
gnasher729

Respuestas:


49

Para mí tiene sentido que esto acelere la resolución de un problema si las dos mitades requieren menos de la mitad del trabajo de tratar con todo el conjunto de datos.

Esa no es la esencia de los algoritmos de divide y vencerás. Por lo general, el punto es que los algoritmos no pueden "tratar con todo el conjunto de datos" en absoluto. En cambio, se divide en piezas que son triviales de resolver (como ordenar dos números), luego se resuelven trivialmente y los resultados se recombinan de una manera que proporciona una solución para el conjunto de datos completo.

Pero, ¿por qué no dividir el conjunto de datos en tres partes? Cuatro? ¿norte?

Principalmente porque dividirlo en más de dos partes y recombinar más de dos resultados resulta en una implementación más compleja pero no cambia la característica fundamental (Big O) del algoritmo: la diferencia es un factor constante y puede resultar en una desaceleración si la división y recombinación de más de 2 subconjuntos crea una sobrecarga adicional.

Por ejemplo, si realiza una combinación de 3 vías, entonces en la fase de recombinación ahora debe encontrar el mayor de 3 elementos para cada elemento, lo que requiere 2 comparaciones en lugar de 1, por lo que hará el doble de comparaciones en general . A cambio, reduce la profundidad de recursión en un factor de ln (2) / ln (3) == 0.63, por lo que tiene 37% menos intercambios, pero 2 * 0.63 == 26% más comparaciones (y accesos de memoria). Si eso es bueno o malo depende de cuál sea más costoso en su hardware.

También he visto muchas referencias al quicksort de 3 vías. ¿Cuándo es esto más rápido?

Aparentemente, se puede demostrar que una variante de doble pivote de clasificación rápida requiere la misma cantidad de comparaciones, pero en promedio un 20% menos de intercambios, por lo que es una ganancia neta.

¿Qué se usa en la práctica?

Hoy en día, casi nadie programa sus propios algoritmos de clasificación; usan uno provisto por una biblioteca. Por ejemplo, la API de Java 7 en realidad usa el quicksort de doble pivote.

Las personas que realmente programan su propio algoritmo de clasificación por alguna razón tenderán a apegarse a la variante simple de 2 vías porque la menor posibilidad de errores supera el 20% de rendimiento la mayor parte del tiempo. Recuerde: con mucho, la mejora de rendimiento más importante es cuando el código pasa de "no funciona" a "funciona".


1
Pequeña nota: Java 7 usa la función de clasificación rápida de doble pivote solo al ordenar primitivas. Para ordenar objetos usa Timsort.
Bakuriu

1
+1 para "En estos días, casi nadie programa sus propios algoritmos de clasificación" y (lo que es más importante) "Recuerde: la mejora más importante del rendimiento es cuando el código pasa de" no funciona "a" funciona ". Sin embargo, me gustaría saber si esa sobrecarga sigue siendo trivial si, por ejemplo, se divide el conjunto de datos en muchas, muchas partes. Tal como sucede, también lo han hecho otras personas: bealto.com/gpu-sorting_intro.html stackoverflow.com/questions/1415679/… devgurus.amd.com/thread/157159
AndrewJacksonZA

Estoy un poco lento. ¿Alguien podría explicar por qué se necesitan 2 * 0.69 más comparaciones? No estoy seguro de dónde vino el 0.69.
jeebface

@jeebface oops, eso fue un error tipográfico (ahora corregido). Es 0.63 (la reducción en la profundidad de recursión), entonces el resultado de 26% más también funciona.
Michael Borgwardt

30

Asintóticamente hablando, no importa. Por ejemplo, la búsqueda binaria hace aproximadamente log 2  n comparaciones, y la búsqueda ternaria hace aproximadamente log 3  n comparaciones. Si conoce sus logaritmos, sabe que log a  x = log b  x / log b  a, por lo que la búsqueda binaria solo genera alrededor de 1 / log 3 2 ≈ 1.5 veces más comparaciones que la búsqueda ternaria. Esta es también la razón por la que nadie especifica la base del logaritmo en notación Oh grande: siempre es un factor constante alejado del logaritmo en una base dada, sin importar cuál sea la base real. Por lo tanto, dividir el problema en más subconjuntos no mejora la complejidad del tiempo y prácticamente no es suficiente para superar la lógica más compleja. De hecho, esa complejidad puede afectar negativamente el rendimiento práctico, aumentando la presión de caché o haciendo que las micro optimizaciones sean menos inviables.

Por otro lado, alguna estructura de datos en árbol utiliza un alto factor de ramificación (mucho mayor que 3, a menudo 32 o más), aunque generalmente por otras razones. Mejora la utilización de la jerarquía de memoria: las estructuras de datos almacenadas en RAM hacen un mejor uso de la memoria caché, las estructuras de datos almacenadas en el disco requieren menos lecturas HDD-> RAM.


Sí, busque el octree para una aplicación específica de una estructura de árbol más que binaria.
daaxix

@daaxix btree es probablemente más común.
Jules

4

Hay algoritmos de búsqueda / clasificación que se subdividen no por dos, sino por N.

Un ejemplo simple es la búsqueda por codificación hash, que toma O (1) tiempo.

Si la función hash preserva el orden, se puede usar para hacer un algoritmo de clasificación O (N). (Puede pensar en cualquier algoritmo de ordenación como simplemente haciendo N búsquedas de dónde debe ir un número en el resultado).

La cuestión fundamental es que, cuando un programa examina algunos datos y luego ingresa en algunos de los siguientes estados, ¿cuántos estados siguientes hay y cuán cercanas son sus probabilidades?

Cuando una computadora hace una comparación de dos números, digamos, y luego salta o no, si ambas rutas son igualmente probables, el contador del programa "conoce" un bit más de información en cada ruta, por lo que en promedio ha "aprendido" uno poco. Si un problema requiere que se aprendan M bits, entonces, usando decisiones binarias, no puede obtener la respuesta en menos de M decisiones. Entonces, por ejemplo, buscar un número en una tabla ordenada de tamaño 1024 no se puede hacer en menos de 10 decisiones binarias, aunque solo sea porque menos no tendrían suficientes resultados, pero ciertamente se puede hacer en más de eso.

Cuando una computadora toma un número y lo transforma en un índice en una matriz, "aprende" hasta registrar la base 2 del número de elementos en la matriz, y lo hace en tiempo constante. Por ejemplo, si hay una tabla de salto de 1024 entradas, todas más o menos igualmente probables, entonces saltar a través de esa tabla "aprende" 10 bits. Ese es el truco fundamental detrás de la codificación hash. Un ejemplo de esto es cómo puedes ordenar un mazo de cartas. Tiene 52 contenedores, uno para cada tarjeta. Lanza cada carta en su contenedor y luego recoge todas. No se requiere subdividir.


1

Como se trata de una cuestión de división general y conquista, no solo de clasificación, me sorprende que nadie haya mencionado el Teorema Maestro

En resumen, el tiempo de ejecución de los algoritmos de divide y vencerás está determinado por dos fuerzas contrarias: el beneficio que obtienes al convertir problemas más grandes en problemas pequeños y el precio que pagas por tener que resolver más problemas. Dependiendo de los detalles del algoritmo, puede o no pagar dividir un problema en más de dos partes. Si se divide en el mismo número de subproblemas en cada paso, y conoce la complejidad temporal de combinar los resultados en cada paso, el Teorema maestro le indicará la complejidad temporal del algoritmo general.

El algoritmo de Karatsuba para la multiplicación utiliza una división y conquista de 3 vías para lograr un tiempo de ejecución de O (3 n ^ log_2 3) que supera el O (n ^ 2) para el algoritmo de multiplicación ordinario (n es el número de dígitos en el números).


En el teorema maestro, el número de subproblemas que crea no es el único factor. En Karatsuba y su primo Strassen, la mejora en realidad proviene de la fusión inteligente de soluciones de algunos de los subproblemas, por lo que reduce el número de llamadas recursivas en los subproblemas. En resumen, el bteorema del maestro principal requiere que el aavance sea más lento para que usted tenga una mejora en la división adicional.
Informado A

-4

Debido a su naturaleza binaria, una computadora es muy eficiente para dividir cosas en 2 y no tanto en 3. Obtiene una división en 3 dividiendo primero en 2 y luego divide una de las partes nuevamente en 2. Entonces, si necesita dividir por 2 para obtener tu división 3, también podrías dividir en 2.

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.