Eine 2-Wege-Zusammenführung wird als Teil des Mergesort-Algorithmus umfassend untersucht. Aber ich bin daran interessiert herauszufinden, wie man eine N-Wege-Zusammenführung am besten durchführen kann.
Nehmen wir an, ich habe N
Dateien, die jeweils 1 Million Ganzzahlen sortiert haben. Ich muss sie zu einer einzigen Datei zusammenführen, die diese 100 Millionen sortierten ganzen Zahlen enthält.
Bitte beachten Sie, dass der Anwendungsfall für dieses Problem die externe Sortierung ist, die auf der Festplatte basiert. Daher würde es in realen Szenarien auch eine Speicherbeschränkung geben. Ein naiver Ansatz, bei dem 2 Dateien gleichzeitig (99 Mal) zusammengeführt werden, funktioniert also nicht. Nehmen wir an, wir haben nur ein kleines verschiebbares Speicherfenster für jedes Array.
Ich bin mir nicht sicher, ob es bereits eine standardisierte Lösung für diese N-Wege-Zusammenführung gibt. (Googeln hat mir nicht viel erzählt) .
Wenn Sie jedoch wissen, ob es sich um einen guten N-Way-Merge-Algorithmus handelt, posten Sie bitte algo / link.
Zeitkomplexität: Wenn wir die Anzahl der zusammenzuführenden Dateien ( N
) stark erhöhen , wie würde sich dies auf die Zeitkomplexität Ihres Algorithmus auswirken?
Danke für deine Antworten.
Ich wurde nirgendwo danach gefragt, aber ich hatte das Gefühl, dass dies eine interessante Interviewfrage sein könnte. Daher markiert.
Antworten:
Wie wäre es mit der folgenden Idee:
Da das Hinzufügen von Elementen zu einer Prioritätswarteschlange in logarithmischer Zeit erfolgen kann, ist Element 2 O (N × log N) . Da (fast alle) Iterationen der while-Schleife ein Element hinzufügen, ist die gesamte while-Schleife O (M × log N), wobei M die Gesamtzahl der zu sortierenden Zahlen ist.
Angenommen, alle Dateien haben eine nicht leere Folge von Zahlen, dann haben wir M> N und daher sollte der gesamte Algorithmus O sein (M × log N) .
quelle
Suchen Sie nach "Polyphase merge" und sehen Sie sich die Klassiker an - Donald Knuth & EHFriend.
Vielleicht möchten Sie auch einen Blick auf das vorgeschlagene Smart Block Merging von Seyedafsari & Hasanzadeh werfen, das ähnlich wie frühere Vorschläge Prioritätswarteschlangen verwendet.
Eine weitere interessante Begründung ist der In Place Merging Algorithm von Kim & Kutzner.
Ich empfehle auch dieses Papier von Vitter: Externe Speicheralgorithmen und Datenstrukturen: Umgang mit massiven Daten .
quelle
Eine einfache Idee besteht darin, eine Prioritätswarteschlange der zusammenzuführenden Bereiche so zu speichern, dass der Bereich mit dem kleinsten ersten Element zuerst aus der Warteschlange entfernt wird. Sie können dann eine N-Wege-Zusammenführung wie folgt durchführen:
Die Richtigkeit dieses Algorithmus ist im Wesentlichen eine Verallgemeinerung des Beweises, dass eine 2-Wege-Zusammenführung korrekt funktioniert. Wenn Sie immer das kleinste Element aus einem Bereich hinzufügen und alle Bereiche sortiert sind, wird die Sequenz als Ganzes sortiert.
Die Laufzeitkomplexität dieses Algorithmus kann wie folgt ermittelt werden. Sei M die Gesamtzahl der Elemente in allen Sequenzen. Wenn wir einen binären Heap verwenden, führen wir höchstens O (M) -Einfügungen und O (M) -Löschungen aus der Prioritätswarteschlange durch, da für jedes in die Ausgabesequenz geschriebene Element eine Warteschlange zum Herausziehen der kleinsten Sequenz vorhanden ist, gefolgt von einer Enqueue, um den Rest der Sequenz wieder in die Warteschlange zu stellen. Jeder dieser Schritte erfordert O (lg N) -Operationen, da das Einfügen oder Löschen aus einem binären Heap mit N Elementen darin O (lg N) -Zeit benötigt. Dies ergibt eine Nettolaufzeit von O (M lg N), die mit der Anzahl der Eingabesequenzen weniger als linear wächst.
Es mag einen Weg geben, dies noch schneller zu erreichen, aber dies scheint eine ziemlich gute Lösung zu sein. Die Speichernutzung ist O (N), da wir O (N) Overhead für den binären Heap benötigen. Wenn wir den binären Heap implementieren, indem wir Zeiger auf die Sequenzen anstatt auf die Sequenzen selbst speichern, sollte dies kein allzu großes Problem sein, es sei denn, Sie haben eine wirklich lächerliche Anzahl von Sequenzen zum Zusammenführen. In diesem Fall führen Sie sie einfach in Gruppen zusammen, die in den Speicher passen, und führen Sie dann alle Ergebnisse zusammen.
Hoffe das hilft!
quelle
Ein einfacher Ansatz zum Zusammenführen von k sortierten Arrays (jeweils mit der Länge n) erfordert die Zeit O (nk ^ 2) und nicht die Zeit O (nk). Wenn Sie die ersten 2 Arrays zusammenführen, dauert es 2n Mal, und wenn Sie die dritte mit der Ausgabe zusammenführen, dauert es 3n Mal, da wir jetzt zwei Arrays der Länge 2n und n zusammenführen. Wenn wir nun diese Ausgabe mit der vierten zusammenführen, benötigt diese Zusammenführung 4n Zeit. Daher erfordert die letzte Zusammenführung (wenn wir das k-te Array zu unserem bereits sortierten Array hinzufügen) k * n Zeit. Diese Gesamtzeit beträgt 2n + 3n + 4n + ... k * n ist O (nk ^ 2).
Es sieht so aus, als könnten wir dies in O (kn) -Zeit tun, aber es ist nicht so, weil jedes Mal, wenn unser Array, das wir zusammenführen, an Größe zunimmt.
Obwohl wir durch Teilen und Erobern eine bessere Bindung erreichen können. Ich arbeite noch daran und poste eine Lösung, wenn ich eine finde.
quelle
Siehe http://en.wikipedia.org/wiki/External_sorting . Hier ist meine Sicht auf die Heap-basierte K-Way-Zusammenführung unter Verwendung eines gepufferten Lesevorgangs aus den Quellen, um die E / A-Reduzierung zu emulieren:
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); } }
Hier ist eine mögliche Implementierung von
MinHeap<T>
. Einige Tests:[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)); }
quelle
Ich habe diesen Code im STL-Stil geschrieben, der N-Way-Merge ausführt, und dachte, ich würde ihn hier veröffentlichen, um zu verhindern, dass andere das Rad neu erfinden. :) :)
Achtung: Es ist nur mild getestet. Vor Gebrauch testen. :) :)
Sie können es so verwenden:
Es ermöglicht auch die Verwendung von Iteratorpaaren anstelle der Container selbst.
Wenn Sie Boost.Range verwenden, können Sie einen Teil des Boilerplate-Codes entfernen.
Der Code:
quelle
}}
quelle
Java-Implementierung des Min-Heap-Algorithmus zum Zusammenführen von k sortierten Arrays:
quelle