Una combinación bidireccional se estudia ampliamente como parte del algoritmo Mergesort. Pero estoy interesado en averiguar cuál es la mejor manera de realizar una fusión de N vías.
Digamos que tengo N
archivos que han ordenado 1 millón de enteros cada uno. Tengo que fusionarlos en un solo archivo que tendrá esos 100 millones de enteros ordenados.
Tenga en cuenta que el caso de uso de este problema es en realidad la clasificación externa que se basa en disco. Por lo tanto, en escenarios reales también habría limitación de memoria. Por lo tanto, un enfoque ingenuo de fusionar 2 archivos a la vez (99 veces) no funcionará. Digamos que solo tenemos una pequeña ventana deslizante de memoria disponible para cada arreglo.
No estoy seguro de si ya existe una solución estandarizada para esta fusión de N vías. (Buscar en Google no me dijo mucho) .
Pero si sabe si es un buen algoritmo de fusión de n vías, publique algo / enlace.
Complejidad del tiempo: si aumentamos en gran medida la cantidad de archivos ( N
) que se fusionarán, ¿cómo afectaría eso a la complejidad del tiempo de su algoritmo?
Gracias por tus respuestas.
No me han preguntado esto en ninguna parte, pero sentí que podría ser una pregunta interesante para la entrevista. Por lo tanto etiquetado.
Respuestas:
¿Qué tal la siguiente idea?
Dado que la adición de elementos a una cola de prioridad se puede hacer en tiempo logarítmico, el elemento 2 es O (N × log N) . Dado que (casi todas) las iteraciones del ciclo while agregan un elemento, todo el ciclo while es O (M × log N) donde M es el número total de números para ordenar.
Suponiendo que todos los archivos tienen una secuencia de números no vacía, tenemos M> N y, por lo tanto, todo el algoritmo debería ser O (M × log N) .
fuente
Busque "Polyphase merge", vea los clásicos: Donald Knuth y EHFriend.
Además, es posible que desee echar un vistazo a la fusión de bloques inteligentes propuesta por Seyedafsari & Hasanzadeh, que, de manera similar a las sugerencias anteriores, usa colas de prioridad.
Otro razonamiento interesante es el algoritmo de fusión in situ de Kim & Kutzner.
También recomiendo este artículo de Vitter: Algoritmos de memoria externa y estructuras de datos: manejo de datos masivos .
fuente
Una idea simple es mantener una cola de prioridad de los rangos para fusionar, almacenada de tal manera que el rango con el primer elemento más pequeño se elimine primero de la cola. Luego puede hacer una combinación de N vías de la siguiente manera:
La exactitud de este algoritmo es esencialmente una generalización de la prueba de que una combinación bidireccional funciona correctamente: si siempre agrega el elemento más pequeño de cualquier rango y todos los rangos están ordenados, terminará con la secuencia como un todo ordenado.
La complejidad del tiempo de ejecución de este algoritmo se puede encontrar de la siguiente manera. Sea M el número total de elementos en todas las secuencias. Si usamos un montón binario, entonces hacemos como máximo inserciones O (M) y eliminaciones O (M) de la cola de prioridad, ya que para cada elemento escrito en la secuencia de salida hay una salida de cola para extraer la secuencia más pequeña, seguida de poner en cola para poner el resto de la secuencia de nuevo en la cola. Cada uno de estos pasos requiere O (lg N) operaciones, porque la inserción o eliminación de un montón binario con N elementos lleva O (lg N) tiempo. Esto da un tiempo de ejecución neto de O (M lg N), que crece menos que linealmente con el número de secuencias de entrada.
Puede haber una forma de conseguirlo aún más rápido, pero parece una solución bastante buena. El uso de memoria es O (N) porque necesitamos O (N) sobrecarga para el montón binario. Si implementamos el montón binario almacenando punteros a las secuencias en lugar de a las secuencias en sí, esto no debería ser un gran problema a menos que tenga una cantidad realmente ridícula de secuencias para fusionar. En ese caso, simplemente combínelos en grupos que encajen en la memoria, luego combine todos los resultados.
¡Espero que esto ayude!
fuente
Un enfoque simple para fusionar k matrices ordenadas (cada una de longitud n) requiere O (nk ^ 2) tiempo y no O (nk) tiempo. Como cuando fusiona las primeras 2 matrices, lleva 2n tiempo, luego, cuando fusiona la tercera con la salida, toma 3n tiempo, ya que ahora estamos fusionando dos matrices de longitud 2n y n. Ahora, cuando fusionamos esta salida con la cuarta, esta fusión requiere 4n tiempo. Por lo tanto, la última fusión (cuando estamos agregando la matriz kth a nuestra matriz ya ordenada) requiere k * n tiempo. Por lo tanto, el tiempo total requerido es 2n + 3n + 4n + ... k * n que es O (nk ^ 2).
Parece que podemos hacerlo en tiempo O (kn) pero no es así porque cada vez que nuestro arreglo que estamos fusionando aumenta de tamaño.
Aunque podemos lograr un mejor límite usando divide y conquistar. Todavía estoy trabajando en eso y publico una solución si encuentro una.
fuente
Consulte http://en.wikipedia.org/wiki/External_sorting . Aquí está mi opinión sobre la combinación de k-way basada en el montón, usando una lectura almacenada en búfer de las fuentes para emular la reducción de E / S:
public class KWayMerger<T> { private readonly IList<T[]> _sources; private readonly int _bufferSize; private readonly MinHeap<MergeValue<T>> _mergeHeap; private readonly int[] _indices; public KWayMerger(IList<T[]> sources, int bufferSize, Comparer<T> comparer = null) { if (sources == null) throw new ArgumentNullException("sources"); _sources = sources; _bufferSize = bufferSize; _mergeHeap = new MinHeap<MergeValue<T>>( new MergeComparer<T>(comparer ?? Comparer<T>.Default)); _indices = new int[sources.Count]; } public T[] Merge() { for (int i = 0; i <= _sources.Count - 1; i++) AddToMergeHeap(i); var merged = new T[_sources.Sum(s => s.Length)]; int mergeIndex = 0; while (_mergeHeap.Count > 0) { var min = _mergeHeap.ExtractDominating(); merged[mergeIndex++] = min.Value; if (min.Source != -1) //the last item of the source was extracted AddToMergeHeap(min.Source); } return merged; } private void AddToMergeHeap(int sourceIndex) { var source = _sources[sourceIndex]; var start = _indices[sourceIndex]; var end = Math.Min(start + _bufferSize - 1, source.Length - 1); if (start > source.Length - 1) return; //we're done with this source for (int i = start; i <= end - 1; i++) _mergeHeap.Add(new MergeValue<T>(-1, source[i])); //only the last item should trigger the next buffered read _mergeHeap.Add(new MergeValue<T>(sourceIndex, source[end])); _indices[sourceIndex] += _bufferSize; //we may have added less items, //but if we did we've reached the end of the source so it doesn't matter } } internal class MergeValue<T> { public int Source { get; private set; } public T Value { get; private set; } public MergeValue(int source, T value) { Value = value; Source = source; } } internal class MergeComparer<T> : IComparer<MergeValue<T>> { public Comparer<T> Comparer { get; private set; } public MergeComparer(Comparer<T> comparer) { if (comparer == null) throw new ArgumentNullException("comparer"); Comparer = comparer; } public int Compare(MergeValue<T> x, MergeValue<T> y) { Debug.Assert(x != null && y != null); return Comparer.Compare(x.Value, y.Value); } }
Aquí hay una posible implementación de
MinHeap<T>
. Algunas pruebas:[TestMethod] public void TestKWaySort() { var rand = new Random(); for (int i = 0; i < 10; i++) AssertKwayMerge(rand); } private static void AssertKwayMerge(Random rand) { var sources = new[] { GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(), GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(), GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(), GenerateRandomCollection(rand, 10, 30, 0, 30).OrderBy(i => i).ToArray(), }; Assert.IsTrue(new KWayMerger<int>(sources, 20).Merge().SequenceEqual(sources.SelectMany(s => s).OrderBy(i => i))); } public static IEnumerable<int> GenerateRandomCollection(Random rand, int minLength, int maxLength, int min = 0, int max = int.MaxValue) { return Enumerable.Repeat(0, rand.Next(minLength, maxLength)).Select(i => rand.Next(min, max)); }
fuente
Escribí este código al estilo STL que se fusiona en N y pensé en publicarlo aquí para ayudar a evitar que otros reinventen la rueda. :)
Advertencia: solo se ha probado levemente. Prueba antes de usar. :)
Puedes usarlo así:
También permite usar pares de iteradores en lugar de los propios contenedores.
Si usa Boost.Range, puede eliminar parte del código repetitivo.
El código:
fuente
}
fuente
Implementación Java del algoritmo min heap para fusionar k matrices ordenadas:
fuente