Articulo de referencia

Ordenación por fusión

O(n\\log n) "},"best-time":{"wt":" \\Omega(n\\log n) typical,\n \\Omega(n) natural variant"},"average-time":{"wt":" \\Theta(n\\log n) "},"space":{"wt":" O(n) total with O(n) aux...

El algoritmo de ordenación por fusión (también escrito comúnmente como mergesort o merge-sort [ 2 ] ) es un algoritmo de ordenación eficiente, de propósito general y basado en comparaciones . La mayoría de las implementaciones de este algoritmo son estables , lo que significa que el orden relativo de los elementos iguales se mantiene entre la entrada y la salida. La ordenación por fusión es un algoritmo de divide y vencerás inventado por John von Neumann en 1945. [ 3 ] Una descripción y análisis detallados de la ordenación por fusión ascendente aparecieron en un informe de Goldstine y von Neumann ya en 1948. [ 4 ]

Algoritmo

Conceptualmente, una ordenación por fusión funciona de la siguiente manera:

  1. Divide la lista sin ordenar en n sublistas, cada una de las cuales contiene un elemento (una lista de un solo elemento se considera ordenada).
  2. Combina repetidamente las sublistas para generar nuevas sublistas ordenadas hasta que solo quede una. Esta será la lista ordenada.

El algoritmo de ordenación por fusión es eficiente porque la fusión y la ordenación de dos sublistas se pueden realizar en tiempo lineal, siempre que las sublistas ya estén ordenadas.

Implementación de arriba hacia abajo

Ejemplo de código tipo C que utiliza índices para un algoritmo de ordenación por fusión descendente. Este algoritmo divide recursivamente la lista en sublistas (llamadas secuencias en este ejemplo) hasta que el tamaño de cada sublista sea 1, y luego fusiona dichas sublistas para producir una lista ordenada. El paso de copia hacia atrás se evita alternando la dirección de la fusión en cada nivel de recursión (excepto por una copia inicial única, que también se puede evitar).

Como ejemplo sencillo, consideremos un array con dos elementos. Estos elementos se copian a b, y luego se vuelven a combinar en a. Si hay cuatro elementos, al llegar al final del nivel de recursión, las secuencias de un solo elemento desde ase combinan en b, y luego, en el siguiente nivel de recursión, esas secuencias de dos elementos se combinan en a. Este patrón se repite con cada nivel de recursión.

// Copia una sección del array a en el array b (desde begin hasta end - 1) void copyArray ( int [] a , int begin , int end , int [] b ) { for ( int k = begin ; k < end ; ++ k ) { b [ k ] = a [ k ] ; } }// Fusiona dos mitades ordenadas (de a) en una única secuencia ordenada (en b) void topDownMerge ( int [] a , int begin , int middle , int end , int [] b ) { int i = begin ; int j = middle ;// Fusionar las dos secuencias ordenadas en b para ( int k = begin ; k < end ; ++ k ) { if ( i < middle && ( j >= end || a [ i ] <= a [ j ] )) { b [ k ] = a [ i ] ; // Tomar el elemento de la secuencia izquierda i ++ ; } else { b [ k ] = a [ j ] ; // Tomar el elemento de la secuencia derecha j ++ ; } } }// Divide el array a en dos mitades, ordena ambas mitades en b, // y fusiona las mitades ordenadas de nuevo en a void topDownSplitMerge ( int [] a , int begin , int end , int [] b ) { if ( end - begin <= 1 ) { return ; // Caso base: El tamaño de ejecución es 1, por lo que ya está ordenado }int medio = ( inicio + fin ) / 2 ; // Encuentra el punto medio para dividir el array// Ordena recursivamente las mitades izquierda y derecha en b topDownSplitMerge ( b , begin , middle , a ); topDownSplitMerge ( b , middle , end , a );// Fusiona las mitades ordenadas de nuevo en un topDownMerge ( b , begin , middle , end , a ); }void topDownMergeSort ( int [] a , int [] b , int n ) { // Copiar inicialmente todo el array a en b copyArray ( a , 0 , n , b ); // Dividir y fusionar recursivamente el array b en a topDownSplitMerge ( a , 0 , n , b ); }

La ordenación de todo el array se realiza mediante topDownMergeSort(a, b, a.length) .

Implementación de abajo hacia arriba

Ejemplo de código similar a C que utiliza índices para un algoritmo de ordenación por fusión ascendente que trata la lista como una matriz de n sublistas (llamadas secuencias en este ejemplo) de tamaño 1, y fusiona iterativamente las sublistas de un lado a otro entre dos búferes:

// Copia el array b en el array a void copyArray ( int [] b , int [] a , int n ) { for ( int i = 0 ; i < n ; i ++ ) { a [ i ] = b [ i ] ; } }// La carrera izquierda es a[izquierda : derecha-1]. // La carrera derecha es a[derecha : fin-1]. void bottomUpMerge ( int [] a , int izquierda , int derecha , int fin , int [] b ) { int i = izquierda ; int j = derecha ;// Mientras haya elementos en las secuencias izquierda o derecha... for ( int k = left ; k < end ; ++ k ) { // Si existe un encabezado de secuencia izquierda y es <= un encabezado de secuencia derecha existente. if ( i < right && ( j >= end || a [ i ] <= a [ j ] )) { b [ k ] = a [ i ] ; i = i + 1 ; } else { b [ k ] = a [ j ] ; j = j + 1 ; } } }void bottomUpMergeSort ( int [] a , int [] b , int n ) { // Cada secuencia de 1 elemento en a ya está "ordenada". // Crea secuencias ordenadas sucesivamente más largas de longitud 2, 4, 8, 16... hasta que todo el array esté ordenado. for ( int width = 1 ; width < n ; width *= 2 ) { // El array a está lleno de secuencias de longitud width. for ( int i = 0 ; i < n ; i = i + 2 * width ) { // Fusionar dos secuencias: a[i:i+width-1] y a[i+width:i+2*width-1] en b[] // o copiar a[i:n-1] en b[] (si (i+width >= n)) bottomUpMerge ( a , i , Math . min ( i + width , n ), Math . min ( i + 2 * width , n ), b ); } // Ahora el array de trabajo b está lleno de secuencias de longitud 2 * width. // Copiar el array b al array a para la siguiente iteración. // Una implementación más eficiente intercambiaría los roles de a y b. copyArray ( b , a , n ); } }

Implementación descendente mediante listas

Pseudocódigo para un algoritmo de ordenación por fusión descendente que divide recursivamente la lista de entrada en sublistas más pequeñas hasta que las sublistas estén ordenadas trivialmente, y luego fusiona las sublistas mientras regresa a través de la cadena de llamadas.

La función merge_sort( lista m) es // Caso base. Una lista de cero o un elemento está ordenada, por definición. Si la longitud de m ≤ 1, entonces devuelve m. // Caso recursivo. Primero, divide la lista en sublistas de igual tamaño // que consisten en la primera mitad y la segunda mitad de la lista. // Esto supone que las listas comienzan en el índice 0. var left := lista vacía var right := lista vacía para cada x con índice i en m hacer si i < (longitud de m)/2 entonces agregar x a la izquierda demás agregar x a la derecha // Ordena recursivamente ambas sublistas. izquierda := merge_sort(izquierda) derecha := merge_sort(derecha) // A continuación, fusiona las sublistas ahora ordenadas. devolver fusión(izquierda, derecha)

En este ejemplo, la función merge combina las sublistas izquierda y derecha.

La función merge(left, right) es var result := lista vacía mientras left no esté vacío y right no esté vacío , si first(left) ≤ first(right) entonces agregar primero(izquierdo) al resultado izquierda := resto(izquierda) demás agregar primero(derecha) al resultado derecha := resto(derecha) // Tanto el lado izquierdo como el derecho pueden tener elementos restantes; consúmalos. // (Solo se entrará en uno de los siguientes bucles). Mientras el lado izquierdo no esté vacío, haga lo siguiente: agregar primero(izquierdo) al resultado izquierda := resto(izquierda) mientras la derecha no esté vacía, haga agregar primero(derecha) al resultado derecha := resto(derecha) devolver resultado

Implementación ascendente mediante listas

Pseudocódigo para el algoritmo de ordenación por fusión ascendente que utiliza un pequeño array fijo de referencias a nodos, donde array[i]es una referencia a una lista de tamaño 2i o nil . node es una referencia o puntero a un nodo. La merge()función sería similar a la mostrada en el ejemplo de listas de fusión descendentes, fusiona dos listas ya ordenadas y maneja listas vacías. En este caso, merge()usaría node como parámetro de entrada y valor de retorno.

función merge_sort( cabezal de nodo ) es // Devuelve si la lista está vacía si head = nil entonces devuelve nil var node array[32]; inicialmente todo nil var node result var node next var int i resultado := cabeza // fusionar nodos en un array mientras result ≠ nil hacer siguiente := resultado.siguiente; resultado.siguiente := nil para (i = 0; (i < 32) && (array[i] ≠ nil); i += 1) hacer resultado := fusionar(array[i], resultado) array[i] := nil // no sobrepasar el final del array si i = 32 entonces i -= 1 array[i] := resultado resultado := siguiente // fusionar el array en una sola lista resultado := nil para (i = 0; i < 32; i += 1) hacer resultado := fusionar(array[i], resultado) devolver resultado

Implementación de arriba hacia abajo en un estilo declarativo.

Pseudocódigo similar a Haskell , que muestra cómo se puede implementar la ordenación por fusión en dicho lenguaje utilizando construcciones e ideas de la programación funcional .

mergeSort :: Ord a => [ a ] ​​-> [ a ] ​​mergeSort [] = [] mergeSort [ x ] = [ x ] mergeSort xs = merge ( mergeSort l , mergeSort r ) donde ( l , r ) = splitAt ( longitud xs ` div ` 2 ) xsfusionar :: Ord a => ([ a ], [ a ]) -> [ a ] ​​fusionar ( [] , xs ) = xs fusionar ( xs , [] ) = xs fusionar ( x : xs , y : ys ) | x <= y = x : fusionar ( xs , y : ys ) | de lo contrario = y : fusionar ( x : xs , ys )

Análisis

Un algoritmo de ordenación por fusión recursivo utilizado para ordenar un arreglo de 7 valores enteros. Estos son los pasos que seguiría un humano para emular la ordenación por fusión (de arriba hacia abajo).

En la ordenación de n objetos, la ordenación por fusión tiene un rendimiento promedio y en el peor de los casos de O ( n  log n ) comparaciones. Si el tiempo de ejecución (número de comparaciones) de la ordenación por fusión para una lista de longitud n es T ( n ), entonces la relación de recurrencia T ( n ) = 2 T ( n /2) + n se deduce de la definición del algoritmo (se aplica el algoritmo a dos listas de la mitad del tamaño de la lista original y se suman los n pasos necesarios para fusionar las dos listas resultantes). [ 5 ] La forma cerrada se deduce del teorema maestro para recurrencias de divide y vencerás . 

El número de comparaciones que realiza el algoritmo de ordenación por fusión en el peor de los casos viene dado por los números de ordenación . Estos números son iguales o ligeramente menores que ( n lg n ⌉ − 2 ⌈lg n + 1), que se encuentra entre ( n lg nn + 1) y ( n lg n + n + O(lg n )). [ 6 ] El mejor caso del algoritmo de ordenación por fusión requiere aproximadamente la mitad de iteraciones que el peor caso. [ 7 ]       

Para n grande y una lista de entrada ordenada aleatoriamente, el número esperado (promedio) de comparaciones del ordenamiento por fusión se aproxima a α · n menos que el peor caso, dondeα=1+k=012k+10,2645.{\displaystyle \alpha =-1+\sum _{k=0}^{\infty }{\frac {1}{2^{k}+1}}\approx 0.2645.}

En el peor de los casos, el algoritmo de ordenación por fusión utiliza aproximadamente un 39 % menos de comparaciones que el algoritmo de ordenación rápida en su caso promedio , y en términos de movimientos, la complejidad del peor caso del algoritmo de ordenación por fusión es O ( n  log n ), la misma complejidad que el mejor caso del algoritmo de ordenación rápida. [ 7 ] 

El algoritmo de ordenación por fusión es más eficiente que el algoritmo de ordenación rápida para ciertos tipos de listas si los datos a ordenar solo se pueden acceder de forma secuencial y eficiente, por lo que es popular en lenguajes como Lisp , donde las estructuras de datos de acceso secuencial son muy comunes. A diferencia de algunas implementaciones (eficientes) de ordenación rápida, el algoritmo de ordenación por fusión es un algoritmo de ordenación estable.

La implementación más común del algoritmo de ordenación por fusión no ordena en el mismo lugar; [ 8 ] por lo tanto, se debe asignar el tamaño de memoria de la entrada para que se almacene la salida ordenada (vea a continuación las variaciones que solo necesitan n /2 espacios adicionales).

ordenación por fusión natural

Un ordenamiento por fusión natural es similar a un ordenamiento por fusión ascendente, excepto que se explotan todas las secuencias ordenadas que aparecen de forma natural en la entrada. Se pueden explotar tanto secuencias monótonas como bitónicas (alternando hacia arriba/abajo), siendo las listas (o equivalentemente cintas o archivos) estructuras de datos convenientes (utilizadas como colas FIFO o pilas LIFO ). [ 9 ] En el ordenamiento por fusión ascendente, el punto de partida supone que cada secuencia tiene un elemento de longitud. En la práctica, los datos de entrada aleatorios tendrán muchas secuencias cortas que simplemente están ordenadas. En el caso típico, el ordenamiento por fusión natural puede no necesitar tantas pasadas porque hay menos secuencias que fusionar. En el mejor de los casos, la entrada ya está ordenada (es decir, es una sola secuencia), por lo que el ordenamiento por fusión natural solo necesita hacer una pasada a través de los datos. En muchos casos prácticos, hay secuencias naturales largas, y por esa razón el ordenamiento por fusión natural se explota como el componente clave de Timsort . Ejemplo:

Inicio: 3 4 2 1 7 5 8 9 0 6 Seleccione las carreras: (3 4)(2)(1 7)(5 8 9)(0 6) Combinar: (2 3 4)(1 5 7 8 9)(0 6) Combinar: (1 2 3 4 5 7 8 9)(0 6) Combinar: (0 1 2 3 4 5 6 7 8 9)

Formalmente, se dice que el algoritmo de ordenación por fusión natural es Runs -óptimo, dondeRnortes(L){\displaystyle {\mathtt {Corre}}(L)}es el número de carreras enL{\displaystyle L}, menos uno.

Los algoritmos de ordenación por selección con reemplazo de torneo se utilizan para recopilar las ejecuciones iniciales para los algoritmos de ordenación externos.

ordenación por fusión de ping-pong

En lugar de fusionar dos bloques a la vez, una fusión ping-pong fusiona cuatro bloques a la vez. Los cuatro bloques ordenados se fusionan simultáneamente en el espacio auxiliar en dos bloques ordenados, luego los dos bloques ordenados se fusionan de nuevo en la memoria principal. Al hacerlo, se omite la operación de copia y se reduce a la mitad el número total de movimientos. Una implementación temprana de dominio público de una fusión de cuatro a la vez fue realizada por WikiSort en 2014; el método fue descrito más tarde ese mismo año como una optimización para la ordenación por paciencia y se denominó fusión ping-pong. [ 10 ] [ 11 ] Quadsort implementó el método en 2020 y lo denominó fusión cuádruple. [ 12 ]

ordenación de fusión in situ

Una desventaja del algoritmo de ordenación por fusión, cuando se implementa en arreglos, es su requerimiento de memoria de trabajo de O ( n ) . Se han sugerido varios métodos para reducir la memoria o hacer que la ordenación por fusión se realice completamente in situ :

  • Kronrod (1969) sugirió una versión alternativa del algoritmo de ordenación por fusión que utiliza espacio adicional constante.
  • Katajainen et al. presentan un algoritmo que requiere una cantidad constante de memoria de trabajo: suficiente espacio de almacenamiento para guardar un elemento del arreglo de entrada y espacio adicional para guardar O (1) punteros al arreglo de entrada. Logran un límite de tiempo de O ( n log n ) con constantes pequeñas, pero su algoritmo no es estable. [ 13 ]
  • Se han realizado varios intentos para producir un algoritmo de fusión in situ que pueda combinarse con una ordenación por fusión estándar (de arriba hacia abajo o de abajo hacia arriba) para producir una ordenación por fusión in situ. En este caso, la noción de "in situ" puede relajarse para significar "utilizar espacio de pila logarítmico", porque la ordenación por fusión estándar requiere esa cantidad de espacio para su propio uso de pila. Geffert et al. demostraron que la fusión estable in situ es posible en tiempo O ( n log n ) utilizando una cantidad constante de espacio temporal , pero su algoritmo es complicado y tiene altos factores constantes: fusionar arreglos de longitud n y m puede tomar 5n + 12m + o ( m ) movimientos. [ 14 ] Este alto factor constante y el complicado algoritmo in situ se simplificaron y se hicieron más fáciles de entender. Bing-Chao Huang y Michael A. Langston [ 15 ] presentaron un algoritmo de tiempo lineal directo de fusión in situ práctica para fusionar una lista ordenada utilizando una cantidad fija de espacio adicional. Ambos han utilizado el trabajo de Kronrod y otros. Se fusiona en tiempo lineal y espacio extra constante. El algoritmo requiere un tiempo promedio ligeramente mayor que los algoritmos de ordenación por fusión estándar, libres para explotar O ( n ) celdas de memoria extra temporales, por menos de un factor de dos. Aunque el algoritmo es mucho más rápido en la práctica, es inestable para algunas listas. Pero utilizando conceptos similares, han podido resolver este problema. Otros algoritmos in situ incluyen SymMerge, que requiere un tiempo total de O (( n + m ) log ( n + m )) y es estable. [ 16 ] Incorporar dicho algoritmo a la ordenación por fusión aumenta su complejidad a la no linealítmica , pero aún cuasilineal , O ( n (log n ) 2 ) .
  • Muchas aplicaciones de ordenación externa utilizan una forma de ordenación por fusión en la que la entrada se divide en un mayor número de sublistas, idealmente en un número tal que su fusión permita que el conjunto de páginas que se está procesando actualmente quepa en la memoria principal.
  • Una variante moderna, estable, lineal y de fusión in situ es la ordenación por fusión de bloques , que crea una sección de valores únicos para usar como espacio de intercambio.
  • El espacio adicional requerido puede reducirse a O ( √n ) mediante búsquedas binarias y rotaciones. [ 17 ] Este método es empleado por la biblioteca STL de C++ y quadsort. [ 12 ]
  • Una alternativa para reducir la copia en múltiples listas consiste en asociar un nuevo campo de información a cada clave (los elementos de m se denominan claves). Este campo se utilizará para vincular las claves y la información asociada en una lista ordenada (una clave y su información relacionada se denominan registro). A continuación, la fusión de las listas ordenadas se realiza modificando los valores de los enlaces; no es necesario mover ningún registro. Un campo que contiene solo un enlace suele ser más pequeño que un registro completo, por lo que también ocupa menos espacio. Esta es una técnica de ordenación estándar, no exclusiva de la ordenación por fusión.
  • Una forma sencilla de reducir el espacio de almacenamiento a n /2 es mantener las partes izquierda y derecha como una estructura combinada, copiar solo la parte izquierda de m en un espacio temporal y dirigir la rutina de fusión para que coloque el resultado fusionado en m . Con esta versión, es mejor asignar el espacio temporal fuera de la rutina de fusión , de modo que solo se necesite una asignación. El exceso de copias mencionado anteriormente también se reduce, ya que el último par de líneas antes de la instrucción de retorno del resultado (la función de fusión en el pseudocódigo anterior) se vuelve superfluo.

Utilizar con unidades de cinta

Los algoritmos de ordenación por fusión permitieron ordenar grandes conjuntos de datos en las primeras computadoras, que contaban con memorias de acceso aleatorio pequeñas según los estándares actuales. Los registros se almacenaban en cinta magnética y se procesaban en bancos de unidades de cinta magnética, como estas IBM 729 .

El algoritmo de ordenación por fusión externa es práctico para usar con unidades de disco o cinta cuando los datos a ordenar son demasiado grandes para caber en la memoria . La ordenación externa explica cómo se implementa la ordenación por fusión con unidades de disco. Una ordenación típica con unidades de cinta utiliza cuatro unidades de cinta. Todas las operaciones de entrada/salida son secuenciales (excepto los rebobinados al final de cada pasada). Una implementación mínima puede funcionar con solo dos búferes de registro y unas pocas variables de programa.

Al denominar las cuatro unidades de cinta como A, B, C y D, con los datos originales en A, y utilizando solo dos búferes de registro, el algoritmo es similar a la implementación ascendente , utilizando pares de unidades de cinta en lugar de matrices en memoria. El algoritmo básico se puede describir de la siguiente manera:

  1. Combinar pares de registros de A; escribir sublistas de dos registros alternativamente en C y D.
  2. Fusionar las sublistas de dos registros de C y D en sublistas de cuatro registros; escribirlas alternativamente en A y B.
  3. Fusionar las sublistas de cuatro registros de A y B en sublistas de ocho registros; escribirlas alternativamente en C y D.
  4. Repita hasta que tenga una lista que contenga todos los datos, ordenados, en log 2 ( n ) pasadas.

En lugar de comenzar con secuencias muy cortas, generalmente se utiliza un algoritmo híbrido , donde la pasada inicial lee muchos registros en memoria, realiza una ordenación interna para crear una secuencia larga y luego distribuye esas secuencias largas en el conjunto de salida. Este paso evita muchas pasadas iniciales. Por ejemplo, una ordenación interna de 1024 registros ahorra nueve pasadas. La ordenación interna suele ser grande debido a esta ventaja. De hecho, existen técnicas que pueden hacer que las secuencias iniciales sean más largas que la memoria interna disponible. Una de ellas, el "quitanieves" de Knuth (basado en un min-heap binario ), genera secuencias dos veces más largas (en promedio) que el tamaño de la memoria utilizada. [ 18 ]

Con cierta sobrecarga, el algoritmo anterior puede modificarse para usar tres cintas. También se puede lograr un tiempo de ejecución de O ( n log n ) usando dos colas , o una pila y una cola, o tres pilas. En la otra dirección, usando k > dos cintas (y O ( k ) elementos en memoria), podemos reducir el número de operaciones de cinta en O (log k ) veces usando una fusión de k/2 vías .

Un algoritmo de ordenación por fusión más sofisticado que optimiza el uso de las unidades de cinta (y disco) es la ordenación por fusión polifásica .

Optimización de la ordenación por fusión

Ordenación por fusión en mosaico aplicada a una matriz de enteros aleatorios. El eje horizontal representa el índice de la matriz y el eje vertical, el entero.

En los ordenadores modernos, la localidad de referencia puede ser de suma importancia en la optimización del software , debido al uso de jerarquías de memoria multinivel. Se han propuesto versiones del algoritmo de ordenación por fusión que tienen en cuenta la caché , cuyas operaciones se han elegido específicamente para minimizar el movimiento de páginas dentro y fuera de la caché de memoria de la máquina. Por ejemplo, elEl algoritmo de ordenación por fusión en mosaico deja de particionar submatrices cuando se alcanzan submatrices de tamaño S, donde S es el número de elementos de datos que caben en la caché de la CPU. Cada una de estas submatrices se ordena con un algoritmo de ordenación in situ, comola ordenación por inserción, para evitar intercambios de memoria, y luego se completa la ordenación por fusión normal de forma recursiva estándar. Este algoritmo ha demostrado un mejor rendimientoen máquinas que se benefician de la optimización de la caché.(LaMarca y Ladner, 1997)

ordenación por fusión paralela

El algoritmo de ordenación por fusión se paraleliza bien gracias al método de divide y vencerás . A lo largo de los años, se han desarrollado diversas variantes paralelas de este algoritmo. Algunos algoritmos de ordenación por fusión paralelos están estrechamente relacionados con el algoritmo de fusión secuencial descendente, mientras que otros presentan una estructura general diferente y utilizan el método de fusión de K vías .

Ordenación por fusión con recursión paralela

El procedimiento de ordenación por fusión secuencial se puede describir en dos fases: la fase de división y la fase de fusión. La primera consiste en numerosas llamadas recursivas que realizan repetidamente el mismo proceso de división hasta que las subsecuencias quedan trivialmente ordenadas (contienen uno o ningún elemento). Un enfoque intuitivo es la paralelización de dichas llamadas recursivas. [ 19 ] El siguiente pseudocódigo describe la ordenación por fusión con recursión paralela utilizando las palabras clave fork y join :

// Ordena los elementos lo a hi (exclusivos) del array A. El algoritmo mergesort(A, lo, hi) es si lo+1 < hi entonces // Dos o más elementos. medio := ⌊(lo + hi) / 2⌋ bifurcar mergesort(A, lo, mid) mergesort(A, medio, alto) unir fusionar(A, lo, mid, hi)

Este algoritmo es una modificación trivial de la versión secuencial y no se paraleliza bien. Por lo tanto, su aceleración no es muy impresionante. Tiene un intervalo deΘ(norte){\displaystyle \Theta (n)}, que es solo una mejora deΘ(registronorte){\displaystyle \Theta (\log n)}en comparación con la versión secuencial (véase Introducción a los algoritmos ). Esto se debe principalmente al método de fusión secuencial, ya que constituye el cuello de botella de las ejecuciones paralelas.

Ordenación por fusión con fusión paralela

Se puede lograr un mejor paralelismo utilizando un algoritmo de fusión paralela . Cormen et al. presentan una variante binaria que fusiona dos subsecuencias ordenadas en una secuencia de salida ordenada. [ 19 ]

En una de las secuencias (la más larga si son de longitud desigual), se selecciona el elemento del índice central. Su posición en la otra secuencia se determina de tal manera que esta secuencia permanezca ordenada si dicho elemento se inserta en esa posición. De este modo, se sabe cuántos otros elementos de ambas secuencias son menores y se puede calcular la posición del elemento seleccionado en la secuencia de salida. Para las secuencias parciales de elementos menores y mayores creadas de esta forma, el algoritmo de fusión se ejecuta de nuevo en paralelo hasta alcanzar el caso base de la recursión.

El siguiente pseudocódigo muestra el método de ordenación por fusión paralela modificado utilizando el algoritmo de fusión paralela (adoptado de Cormen et al.).

/** * A: Matriz de entrada * B: Matriz de salida * lo: límite inferior * hola: límite superior * apagado: desplazamiento */ El algoritmo parallelMergesort(A, lo, hi, B, off) es longitud := alto - bajo + 1 Si len == 1, entonces  B[off] := A[lo]; de lo contrario , sea T[1..len] un nuevo array. medio := ⌊(lo + hi) / 2⌋ medio' := medio - bajo + 1 bifurcación paralelaMergesort(A, lo, mid, T, 1) parallelMergesort(A, mid + 1, hi, T, mid' + 1) unir parallelMerge(T, 1, mid', mid' + 1, len, B, off)

Para analizar una relación de recurrencia para el intervalo del peor caso, las llamadas recursivas de parallelMergesort deben incorporarse solo una vez debido a su ejecución paralela, obteniendo

Tclasificar(norte)=Tclasificar(norte2)+Tunir(norte)=Tclasificar(norte2)+Θ(registro(norte)2).{\displaystyle T_{\infty }^{\text{sort}}(n)=T_{\infty }^{\text{sort}}\left({\frac {n}{2}}\right)+T_{\infty }^{\text{merge}}(n)=T_{\infty }^{\text{sort}}\left({\frac {n}{2}}\right)+\Theta \left(\log(n)^{2}\right).}

Para obtener información detallada sobre la complejidad del procedimiento de fusión en paralelo, consulte el algoritmo de fusión .

La solución de esta recurrencia viene dada por

Tclasificar=Θ(registro(norte)3).{\displaystyle T_{\infty }^{\text{sort}}=\Theta \left(\log(n)^{3}\right).}

Este algoritmo de fusión paralela alcanza un paralelismo deΘ(norte(registronorte)2){\textstyle \Theta \left({\frac {n}{(\log n)^{2}}}\right)}, que es mucho mayor que el paralelismo del algoritmo anterior. Este tipo de ordenación puede funcionar bien en la práctica cuando se combina con una ordenación secuencial estable y rápida, como la ordenación por inserción , y una fusión secuencial rápida como caso base para fusionar matrices pequeñas. [ 20 ]

Ordenación por fusión multidireccional paralela

Parece arbitrario restringir los algoritmos de ordenación por fusión a un método de fusión binaria, ya que normalmente hay p > 2 procesadores disponibles. Un mejor enfoque podría ser utilizar un método de fusión de K vías , una generalización de la fusión binaria, en el quek{\displaystyle k}Las secuencias ordenadas se fusionan. Esta variante de fusión es muy adecuada para describir un algoritmo de ordenación en un PRAM . [ 21 ] [ 22 ]

Idea básica

El proceso de ordenación por fusión multidireccional paralelo en cuatro procesadorest0{\displaystyle t_{0}}at3{\displaystyle t_{3}}.

Dada una secuencia no ordenada denorte{\displaystyle n}elementos, el objetivo es ordenar la secuencia conpag{\displaystyle p}procesadores disponibles . Estos elementos se distribuyen equitativamente entre todos los procesadores y se ordenan localmente mediante un algoritmo de ordenación secuencial . Por lo tanto, la secuencia consta de secuencias ordenadas.S1,...,Spag{\displaystyle S_{1},...,S_{p}}de longitudnortepag{\textstyle \lceil {\frac {n}{p}}\rceil }. Para simplificar, seanorte{\displaystyle n}ser un múltiplo depag{\displaystyle p}, de modo que|Si|=nortepag{\textstyle \left\vert S_{i}\right\vert ={\frac {n}{p}}}parai=1,...,pag{\displaystyle i=1,...,p}.

Estas secuencias se utilizarán para realizar una selección de secuencias múltiples/selección de divisores. Paraj=1,...,pag{\displaystyle j=1,...,p}El algoritmo determina los elementos divisores.vj{\displaystyle v_{j}}con clasificación mundialk=jnortepag{\textstyle k=j{\frac {n}{p}}}. Luego las posiciones correspondientes dev1,...,vpag{\displaystyle v_{1},...,v_{p}}en cada secuenciaSi{\displaystyle S_{i}}se determinan con búsqueda binaria y por lo tanto elSi{\displaystyle S_{i}}se dividen además enpag{\displaystyle p}subsecuenciasSi,1,...,Si,pag{\displaystyle S_{i,1},...,S_{i,p}}conSi,j:={incógnitaSi|ranortek(vj1)<ranortek(incógnita)ranortek(vj)}{\textstyle S_{i,j}:=\{x\in S_{i}|rank(v_{j-1})<rank(x)\leq rank(v_{j})\}}.

Además, los elementos deS1,i,...,Spag,i{\displaystyle S_{1,i},...,S_{p,i}}se asignan al procesadori{\displaystyle i}, significa todos los elementos entre rango(i1)nortepag{\textstyle (i-1){\frac {n}{p}}}y rangoinortepag{\textstyle i{\frac {n}{p}}}, que se distribuyen por todoSi{\displaystyle S_{i}}Por lo tanto, cada procesador recibe una secuencia de secuencias ordenadas. El hecho de que el rangok{\displaystyle k}de los elementos divisoresvi{\displaystyle v_{i}}fue elegido a nivel mundial, proporciona dos propiedades importantes: Por un lado,k{\displaystyle k}fue elegido para que cada procesador pueda seguir funcionando ennorte/pag{\textstyle n/p}elementos después de la asignación. El algoritmo está perfectamente equilibrado en carga . Por otro lado, todos los elementos en el procesadori{\displaystyle i}son menores o iguales que todos los elementos del procesadori+1{\displaystyle i+1}Por lo tanto, cada procesador realiza la fusión de p vías localmente y, de este modo, obtiene una secuencia ordenada a partir de sus subsecuencias. Debido a la segunda propiedad, no es necesario realizar ninguna otra fusión de p vías; los resultados solo deben ordenarse según el número de procesador.

Selección de secuencias múltiples

En su forma más simple, dadopag{\displaystyle p}secuencias ordenadasS1,...,Spag{\displaystyle S_{1},...,S_{p}}distribuidos uniformemente enpag{\displaystyle p}procesadores y un rangok{\displaystyle k}, la tarea es encontrar un elementoincógnita{\displaystyle x}con una clasificación mundialk{\displaystyle k}en la unión de las secuencias. Por lo tanto, esto se puede utilizar para dividir cadaSi{\displaystyle S_{i}}en dos partes en un índice divisorli{\displaystyle l_{i}}, donde la parte inferior contiene solo elementos que son más pequeños queincógnita{\displaystyle x}, mientras que los elementos más grandes queincógnita{\displaystyle x}se encuentran en la parte superior.

El algoritmo secuencial presentado devuelve los índices de las divisiones en cada secuencia, por ejemplo, los índicesli{\displaystyle l_{i}}en secuenciasSi{\displaystyle S_{i}}de tal manera queSi[li]{\displaystyle S_{i}[l_{i}]}tiene un puesto global inferior ak{\displaystyle k}yranortek(Si[li+1])k{\displaystyle \mathrm {rank} \left(S_{i}[l_{i}+1]\right)\geq k}. [ 23 ]

El algoritmo msSelect(S : Array of sorted Sequences [S_1,..,S_p], k : int) es para i = 1 a p hacer (l_i, r_i) = (0, |S_i|-1) mientras exista i: l_i < r_i hacer // seleccionar el elemento pivote en S_j[l_j], .., S_j[r_j], elegir j aleatoriamente de forma uniforme v := pickPivot(S, l, r) para i = 1 a p hacer m_i = binarySearch(v, S_i[l_i, r_i]) // secuencialmente Si m_1 + ... + m_p >= k entonces // m_1 + ... + m_p es el rango global de v r := m // asignación de vector demás l := m regresar l

Para el análisis de complejidad se elige el modelo PRAM . Si los datos están distribuidos uniformemente en todos lospag{\displaystyle p}, la ejecución p-fold del método binarySearch tiene un tiempo de ejecución deO(pagregistro(norte/pag)){\displaystyle {\mathcal {O}}\left(p\log \left(n/p\right)\right)}. La profundidad de recursión esperada esO(registro(i|Si|))=O(registro(norte)){\displaystyle {\mathcal {O}}\left(\log \left(\textstyle \sum _{i}|S_{i}|\right)\right)={\mathcal {O}}(\log(n))}como en la selección rápida ordinaria . Por lo tanto, el tiempo de ejecución total esperado esO(pagregistro(norte/pag)registro(norte)){\displaystyle {\mathcal {O}}\left(p\log(n/p)\log(n)\right)}.

Aplicado al algoritmo de ordenación por fusión multiway paralela, este algoritmo debe invocarse en paralelo de manera que todos los elementos divisores de rangoinortepag{\textstyle i{\frac {n}{p}}}parai=1,..,pag{\displaystyle i=1,..,p}se encuentran simultáneamente. Estos elementos divisores se pueden utilizar para particionar cada secuencia enpag{\displaystyle p}partes, con el mismo tiempo total de funcionamiento deO(pagregistro(norte/pag)registro(norte)){\displaystyle {\mathcal {O}}\left(p\,\log(n/p)\log(n)\right)}.

Pseudocódigo

A continuación, se presenta el pseudocódigo completo del algoritmo de ordenación por fusión multivariante paralelo. Suponemos que existe una sincronización de barrera antes y después de la selección multisecuencia, de modo que cada procesador pueda determinar correctamente los elementos de división y la partición de la secuencia.

/** * d: Matriz de elementos sin ordenar * n: Número de elementos * p: Número de procesadores * devolver matriz ordenada */ algoritmo parallelMultiwayMergesort(d : Array, n : int, p : int) es o := new Array[0, n] // el array de salida para i = 1 a p hacer en paralelo // cada procesador en paralelo  S_i := d[(i-1) * n/p, i * n/p] // Secuencia de longitud n/p ordenar(S_i) // ordenar localmente sincronización v_i := msSelect([S_1,...,S_p], i * n/p) // elemento con rango global i * n/p sincronización (S_i,1, ..., S_i,p) := sequence_partitioning(si, v_1, ..., v_p) // divide s_i en subsecuencias o[(i-1) * n/p, i * n/p] := kWayMerge(s_1,i, ..., s_p,i) // fusionar y asignar a la matriz de salida regresar o

Análisis

En primer lugar, cada procesador ordena los elementos asignados.norte/pag{\displaystyle n/p}elementos localmente usando un algoritmo de ordenación con complejidadO(norte/pagregistro(norte/pag)){\displaystyle {\mathcal {O}}\left(n/p\;\log(n/p)\right)}Después de eso, los elementos divisores deben calcularse en tiempoO(pagregistro(norte/pag)registro(norte)){\displaystyle {\mathcal {O}}\left(p\,\log(n/p)\log(n)\right)}. Finalmente, cada grupo depag{\displaystyle p}Las divisiones deben fusionarse en paralelo por cada procesador con un tiempo de ejecución deO(registro(pag)norte/pag){\displaystyle {\mathcal {O}}(\log(p)\;n/p)}utilizando un algoritmo de fusión secuencial de p vías . Por lo tanto, el tiempo de ejecución total viene dado por

O(nortepagregistro(nortepag)+pagregistro(nortepag)registro(norte)+nortepagregistro(pag)){\displaystyle {\mathcal {O}}\left({\frac {n}{p}}\log \left({\frac {n}{p}}\right)+p\log \left({\frac {n}{p}}\right)\log(n)+{\frac {n}{p}}\log(p)\right)}.

Adaptación y aplicación práctica

El algoritmo de ordenación por fusión multi-vía es muy escalable gracias a su alta capacidad de paralelización, que permite el uso de muchos procesadores. Esto lo convierte en una opción viable para ordenar grandes cantidades de datos, como los que se procesan en clústeres de computadoras . Además, dado que en estos sistemas la memoria no suele ser un recurso limitante, la desventaja de la complejidad espacial de la ordenación por fusión es insignificante. Sin embargo, en estos sistemas entran en juego otros factores que no se tienen en cuenta al modelar en una PRAM . Aquí, es necesario considerar los siguientes aspectos: la jerarquía de memoria , cuando los datos no caben en la caché de los procesadores, o la sobrecarga de comunicación del intercambio de datos entre procesadores, que podría convertirse en un cuello de botella cuando ya no se puede acceder a los datos a través de la memoria compartida.

Sanders et al. han presentado en su artículo un algoritmo paralelo síncrono masivo para la ordenación por fusión multinivel y multidireccional, que dividepag{\displaystyle p}procesadores enr{\displaystyle r}grupos de tamañopag{\displaystyle p'}. Todos los procesadores ordenan primero localmente. A diferencia del ordenamiento por fusión multidireccional de un solo nivel, estas secuencias luego se particionan enr{\displaystyle r}partes y asignadas a los grupos de procesadores apropiados. Estos pasos se repiten recursivamente en esos grupos. Esto reduce la comunicación y, en particular, evita problemas con muchos mensajes pequeños. La estructura jerárquica de la red real subyacente puede utilizarse para definir los grupos de procesadores (por ejemplo, racks , clústeres , etc.). [ 22 ]

Otras variantes

El algoritmo Merge Sort fue uno de los primeros algoritmos de ordenación en los que se logró una aceleración óptima, con Richard Cole utilizando un ingenioso algoritmo de submuestreo para asegurar una fusión O (1). [ 24 ] Otros algoritmos de ordenación paralela sofisticados pueden lograr límites de tiempo iguales o mejores con una constante menor. Por ejemplo, en 1991 David Powers describió un Quicksort paralelizado (y un Radix Sort relacionado ) que puede operar en tiempo O (log n ) en una máquina de acceso aleatorio paralela (PRAM) CRCW con n procesadores mediante la partición implícita. [ 25 ] Powers muestra además que una versión segmentada del Bitonic Mergesort de Batcher en tiempo O ((log n ) ² ) en una red de ordenación mariposa es en la práctica más rápida que sus ordenaciones O (log n ) en una PRAM, y proporciona una discusión detallada de las sobrecargas ocultas en comparación, ordenación radix y ordenación paralela. [ 26 ]

Comparación con otros algoritmos de ordenación

Aunque heapsort tiene los mismos límites de tiempo que merge sort, requiere solo Θ(1) espacio auxiliar en lugar de Θ( n ) de merge sort. En arquitecturas modernas típicas, las implementaciones eficientes de quicksort generalmente superan a merge sort para ordenar arreglos basados ​​en RAM. [ 27 ] Se prefieren los quicksorts cuando el tamaño de los datos a ordenar es menor, ya que la complejidad espacial para quicksort es O(log n ), lo que ayuda a utilizar la localidad de caché mejor que merge sort (con complejidad espacial O(n)). [ 27 ] Por otro lado, merge sort es un ordenamiento estable y es más eficiente para manejar medios secuenciales de acceso lento. Merge sort suele ser la mejor opción para ordenar una lista enlazada : en esta situación es relativamente fácil implementar un merge sort de tal manera que requiera solo Θ(1) espacio adicional, y el lento rendimiento de acceso aleatorio de una lista enlazada hace que algunos otros algoritmos (como quicksort) tengan un rendimiento deficiente, y otros (como heapsort) sean completamente imposibles.

A partir de Perl 5.8, el algoritmo de ordenación por fusión es el predeterminado (en versiones anteriores de Perl era quicksort). [ 28 ] En Java , los métodos Arrays.sort() utilizan la ordenación por fusión o una versión optimizada de quicksort según los tipos de datos y, para mayor eficiencia en la implementación, cambian a la ordenación por inserción cuando se ordenan menos de siete elementos de la matriz. [ 29 ] El kernel de Linux utiliza la ordenación por fusión para sus listas enlazadas. [ 30 ]

Timsort , un híbrido optimizado de ordenación por fusión y ordenación por inserción, se utiliza en diversas plataformas y lenguajes de software, incluidas las plataformas Java y Android [ 31 ] , y Python lo utiliza desde la versión 2.3; desde la versión 3.11, la política de fusión de Timsort se actualizó a Powersort . [ 32 ]

Referencias

  1. Skiena (2008 , p. 122)
  2. Goodrich, Michael T.; Tamassia, Roberto; Goldwasser, Michael H. (2013). «Capítulo 12 - Ordenación y selección». Estructuras de datos y algoritmos en Python (1.ª  ed.). Hoboken [NJ]: Wiley. págs. 538–549 . ISBN  978-1-118-29027-9.
  3. Knuth (1998 , p. 158) 
  4. Katajainen, Jyrki; Träff, Jesper Larsson (marzo de 1997). «Algoritmos y complejidad». Actas de la 3.ª Conferencia Italiana sobre Algoritmos y Complejidad . Conferencia Italiana sobre Algoritmos y Complejidad. Lecture Notes in Computer Science. Vol. 1203. Roma. pp. 217–228 . CiteSeerX 10.1.1.86.3154 . doi : 10.1007/3-540-62592-5_74 . ISBN    978-3-540-62592-6.
  5. ^ Cormen et al. (2009 , pág. 36) 
  6. El valor del peor caso que se muestra aquí no coincide con el que aparece en el volumen 3 de *El arte de la programación informática* de Knuth . La discrepancia se debe a que Knuth analiza una implementación variante del algoritmo de ordenación por fusión que es ligeramente subóptima.
  7. 1 2 Jayalakshmi, N. (2007). Estructura de datos usando C++ . Firewall Media. ISBN 978-81-318-0020-1OCLC 849900742 
  8. ^ Cormen et al. (2009 , pág. 151) 
  9. Powers, David MW; McMahon, Graham B. (1983). "Un compendio de programas interesantes en Prolog". Informe técnico DCS 8313 (Informe). Departamento de Ciencias de la Computación, Universidad de Nueva Gales del Sur.
  10. "WikiSort. Algoritmo de ordenación rápido y estable que utiliza memoria O(1). Dominio público" . GitHub . 14 de abril de 2014.
  11. Chandramouli, Badrish; Goldstein, Jonathan (2014). La paciencia es una virtud: una revisión de la fusión y la ordenación en los procesadores modernos (PDF) . SIGMOD/PODS.
  12. 1 2 "Quadsort es un algoritmo de ordenación por fusión adaptativo estable sin ramificaciones" . GitHub . 8 de junio de 2022.
  13. ^ Katajainen, Pasanen y Teuhola (1996)
  14. ^ Geffert, Viliam; Katajainen, Jyrki; Pasanen, Tomi (2000). "Fusión in situ asintóticamente eficiente" . Informática Teórica . 237 ( 1– 2): 159– 181. doi : 10.1016/S0304-3975(98)00162-5 .
  15. Huang, Bing-Chao; Langston, Michael A. (marzo de 1988). "Fusión práctica in situ" . Communications of the ACM . 31 (3): 348– 352. doi : 10.1145/42392.42403 . S2CID 4841909 . 
  16. Kim, Pok-Son; Kutzner, Arne (2004). "Fusión estable de almacenamiento mínimo mediante comparaciones simétricas". Algorithms – ESA 2004. Simposio Europeo de Algoritmos. Lecture Notes in Computer Science. Vol. 3221. pp. 714–723 . CiteSeerX 10.1.1.102.4612 . doi : 10.1007/978-3-540-30140-0_63 . ISBN    978-3-540-23025-0.
  17. Kim, Pok-Son; Kutzner, Arne (1 de septiembre de 2003). "Un nuevo método para la fusión eficiente in situ" . Actas de la Conferencia del Instituto Coreano de Sistemas Inteligentes : 392–394 .
  18. Ferragina, Paolo (2009–2019), "5. Ordenación de elementos atómicos" (PDF) , ¡La magia de los algoritmos!, págs. 5-4, archivado (PDF) del original el 12 de mayo de 2021 
  19. ^ Cormen et al. (2009 , págs. 797–805) 
  20. ^ Victor J. Duvanenko "Clasificación por fusión paralela" Blog y diario del Dr. Dobby repositorio de GitHub con implementación en C++
  21. ^ Peter Sanders; Johannes Singler (2008). "Conferencia Algoritmos paralelos " (PDF) . Consultado el 2 de mayo de 2020 .
  22. 1 2 Axtmann, Michael; Bingmann, Timo; Sanders, Peter; Schulz, Christian (2015). "Clasificación masivamente paralela práctica" . Actas del 27.º simposio de la ACM sobre paralelismo en algoritmos y arquitecturas . págs. 13–23 . arXiv : 1410.6754 . doi : 10.1145/2755573.2755595 . ISBN  9781450335881. S2CID 18249978 . 
  23. Peter Sanders (2019). "Conferencia sobre algoritmos paralelos " (PDF) . Consultado el 2 de mayo de 2020 .
  24. Cole, Richard (agosto de 1988). "Ordenación por fusión paralela". SIAM J. Comput . 17 (4): 770– 785. CiteSeerX 10.1.1.464.7118 . doi : 10.1137/0217049 . S2CID 2416667 .  
  25. Powers, David MW (1991). "Quicksort y Radixsort paralelizados con aceleración óptima" . Actas de la Conferencia Internacional sobre Tecnologías de Computación Paralela, Novosibirsk . Archivado del original el 25 de mayo de 2007.
  26. Powers, David MW (enero de 1995). Unificación paralela: complejidad práctica (PDF) . Taller de arquitectura informática de Australasia, Universidad de Flinders.
  27. 1 2 Oladipupo, Esau Taiwo; Abikoye, Oluwakemi Christianah (2020). "Comparación de quicksort y mergesort" . Tercera Conferencia Internacional sobre Computación y Comunicaciones de Red (CoCoNet 2019) . 2020 (2020): 9. Recuperado el 20 de enero de 2024 vía Elsevier Science Direct.
  28. "Sort – Documentación de Perl 5 versión 8.8" . Consultado el 23 de agosto de 2020 .
  29. coleenp (22 feb. 2019). "src/java.base/share/classes/java/util/Arrays.java @ 53904:9c3fe09f69bc" . OpenJDK .
  30. kernel de Linux /lib/list_sort.c
  31. Universidad de Liverpool (12 de diciembre de 2022). "Científicos informáticos mejoran la función de ordenación de Python" . Tech Xplore . Consultado el 8 de mayo de 2024 .
  32. James, Mike (21/12/2022). "Python ahora usa Powersort" . i-programmer.info . Consultado el 08/05/2024 .

Bibliografía

  • Cormen, Thomas H .; Leiserson, Charles E .; Rivest, Ronald L .; Stein, Clifford (2009) [1990]. Introducción a los algoritmos (3ª  ed.). MIT Press y McGraw-Hill. ISBN 0-262-03384-4.
  • Katajainen, Jyrki; Pasanen, Tomi; Teuhola, Jukka (1996). "Practical in-place mergesort" . Nordic Journal of Computing . 3 (1): 27– 40. CiteSeerX 10.1.1.22.8523 . ISSN 1236-6064 . Archivado del original el 7 de agosto de 2011. Recuperado el 4 de abril de 2009 .  . También ordenación por fusión práctica in situ . También
  • Knuth, Donald (1998). «Sección 5.2.4: Ordenación por fusión». Ordenación y búsqueda . El arte de la programación informática . Vol.  3 (2.ª  ed.). Addison-Wesley. págs. 158–168 . ISBN  0-201-89685-0.
  • Kronrod, MA (1969). "Algoritmo de ordenación óptima sin campo operacional". Matemáticas Soviéticas - Doklady . 10 : 744.
  • LaMarca, A.; Ladner, RE (1997). "La influencia de las cachés en el rendimiento de la ordenación". Actas del 8.º Simposio Anual ACM-SIAM sobre Algoritmos Discretos (SODA97) : 370–379 . CiteSeerX 10.1.1.31.1153 . 
  • Skiena, Steven S. (2008). "4.5: Mergesort: Ordenación por divide y vencerás". The Algorithm Design Manual (2.ª  ed.). Springer. pp. 120–125 . ISBN  978-1-84800-069-8.
  • Sun Microsystems. "API de matrices (Java SE 6)" . Consultado el 19 de noviembre de 2007 .
  • Oracle Corp. "Arreglos (Java SE 10 y JDK 10)" . Consultado el 23 de julio de 2018 .
  • Algoritmos de ordenación animados: Ordenación por fusión en Wayback Machine (archivado el 6 de marzo de 2015) – demostración gráfica
  • Estructuras de datos abiertas - Sección 11.1.1 - Ordenación por fusión , Pat Morin
  • Programa en C para implementar el algoritmo de ordenación por fusión.
  • Combinar código de ordenación con Java
  • Programa en C++ para el algoritmo de ordenación por fusión