MergeSort ist wie QuickSort ein rekursiver Sortieralgorithmus.
Die grundlegende Idee des MergeSort-Algorithmus ist die, dass das zu sortierende Array in zwei Teil-Arrays zerlegt wird und diese durch rekursive Anwendung des Algorithmus sortiert und anschließend gemischt werden. Die Rekursion endet, wenn ein Teil-Array nur noch aus einem Element besteht. In diesem Fall ist es ja sortiert.
Mischen zweier sortierter Teil-Arrays bedeutet, dass diese zu einem sortierten Array verschmolzen werden. Dazu werden die Teil-Arrays zunächst in ein Hilfs-Array kopiert. Anschließend werden die beiden Teil-Arrays elementweise durchlaufen. Das jeweils kleinere Element wird zurückkopiert. Zum Schluss muss dann noch der Rest eines der beiden Teil-Arrays zurückkopiert werden.
Die folgende Klasse MergeSort (MergeSort.ham) implementiert das Interface SortierAlgorithmus entsprechend des MergeSort-Algorithmus:
public class MergeSort implements SortierAlgorithmus { private int[] hilfsArray; // sortiert das uebergebene Array in aufsteigender Reihenfolge // gemaess dem MergeSort-Algorithmus public void sortiere(int[] zahlen) { hilfsArray = new int[zahlen.length]; mergeSort(zahlen, 0, zahlen.length-1); } // der MergeSort-Algorithmus wird auf dem Array zwischen den // angegebenen Indizes ausgefuehrt private void mergeSort(int[] zahlen, int linkerIndex, int rechterIndex) { if (linkerIndex < rechterIndex) { int mittlererIndex = (linkerIndex + rechterIndex) / 2; mergeSort(zahlen, linkerIndex, mittlererIndex); mergeSort(zahlen, mittlererIndex+1, rechterIndex); mischen(zahlen, linkerIndex, mittlererIndex, rechterIndex); } } // mischt die zwei (sortierten) Teil-Arrays von linkerIndex bis // mittlererIndex und mittlererIndex+1 bis rechterIndex private void mischen(int[] zahlen, int linkerIndex, int mittlererIndex, int rechterIndex) { // beide Teil-Arrays in das Hilfsarray kopieren for (int i=linkerIndex; i<=rechterIndex; i++) { hilfsArray[i] = zahlen[i]; } // jeweils das kleinere Element der beiden Teil-Arrays zurückkopieren int linkerZeiger = linkerIndex; int rechterZeiger = mittlererIndex + 1; int aktuellerZeiger = linkerIndex; while (linkerZeiger <= mittlererIndex && rechterZeiger <= rechterIndex) { if (hilfsArray[linkerZeiger] <= hilfsArray[rechterZeiger]) { zahlen[aktuellerZeiger++] = hilfsArray[linkerZeiger++]; } else { zahlen[aktuellerZeiger++] = hilfsArray[rechterZeiger++]; } } // falls vorhanden Reste des ersten Teil-Arrays zurückkopieren while (linkerZeiger <= mittlererIndex) { zahlen[aktuellerZeiger++] = hilfsArray[linkerZeiger++]; } // falls vorhanden Reste des zweiten Teil-Arrays zurückkopieren while (rechterZeiger <= rechterIndex) { zahlen[aktuellerZeiger++] = hilfsArray[rechterZeiger++]; } } }
Die folgende Hamster-Klasse MergeSortHamster (MergeSortHamster.ham) implementiert das Interface SortierHamster und visualisiert dabei den MergeSort-Algorithmus. Voraussetzung ist, dass es im Territorium drei nicht durch Mauern blockierte Reihen unterhalb des Standard-Hamsters gibt. In der ersten Reihe unterhalb der Körnerhaufenreihe markieren zwei Markierungshamster jeweils den linken und rechten Rand des aktuell betrachteten Teil-Arrays. Darunter die Reihe wird als Ablagereihe benutzt. Hierhin werden vor dem Mischen die Körnerhaufen der beiden beteiligten Teil-Arrays transportiert. Unterhalb dieser Reihe markieren zwei weitere Markierungshamster beim Mischen das jeweils betrachtete Element der beiden Teil-Arrays. Ein fünfter Markierungshamster (weiß) markiert in der ersten Reihe unterhalb der Körnerhaufenreihe die Kachel, in die jeweils beim Mischen der kleinere der beiden betrachteten Körnerhaufen der Teil-Arrays transportiert werden muss.
// Demonstration des MergeSort-Algorithmus public class MergeSortHamster extends KoernerHaufenSortierHamster implements SortierHamster { private MarkierungsHamster linkerIndexHamster = null; // markiert den linken Rand des aktuellen Teil-Arrays private MarkierungsHamster rechterIndexHamster = null; // markiert den rechten Rand des aktuellen Teil-Arrays private MarkierungsHamster linkerZeigerHamster = null; // markiert waehrend des Mischens das aktuell // betrachtete Element des ersten Teil-Arrays private MarkierungsHamster rechterZeigerHamster = null; // markiert waehrend des Mischens das aktuell // betrachtete Element des rechten Teil-Arrays private MarkierungsHamster aktuellerZeigerHamster = null; // markiert waehrend der Partitionierung das aktuell // betrachtete rechte Element private int anzahlKoernerHaufen = 0; // Anzahl der zu sortierenden Koernerhaufen // Konstruktor public MergeSortHamster(boolean mitErlaeuterungen) { super(mitErlaeuterungen); this.erlaeuterung( "Ich sortiere die Koernerhaufen auf der Basis des MergeSort-Algorithmus."); this.linkerIndexHamster = new MarkierungsHamster(this.getReihe()+1, this.getSpalte(), this.getReihe(), mitErlaeuterungen); this.linkerIndexHamster.erlaeuterung( "Ich markiere den linken Rand des aktuell betrachteten Teil-Arrays."); this.rechterIndexHamster = new MarkierungsHamster(this.getReihe()+1, this.getSpalte(), this.getReihe(), mitErlaeuterungen); this.rechterIndexHamster.erlaeuterung( "Ich markiere den rechten Rand des aktuell betrachteten Teil-Arrays."); this.linkerZeigerHamster = new MarkierungsHamster(this.getReihe()+3, this.getSpalte(), this.getReihe()+2, mitErlaeuterungen); this.linkerZeigerHamster.erlaeuterung( "Ich markiere waehrend des Mischens das aktuell betrachtete Element\n" + "des ersten Teils der Hilfskoernerhaufenreihe."); this.rechterZeigerHamster = new MarkierungsHamster(this.getReihe()+3, this.getSpalte(), this.getReihe()+2, mitErlaeuterungen); this.rechterZeigerHamster.erlaeuterung( "Ich markiere waehrend des Mischens das aktuell betrachtete Element\n" + "des zweiten Teils der Hilfskoernerhaufenreihe."); this.aktuellerZeigerHamster = new MarkierungsHamster(this.getReihe()+1, this.getSpalte(), this.getReihe(), mitErlaeuterungen); this.aktuellerZeigerHamster.erlaeuterung( "Ich markiere waehrend des Mischens das Element der Körnerhaufenreihe,\n" + "in das die Körner der Hilfskoernerhaufenreihe zurueckkopiert werden."); } // Der Standard-Hamster steht mit Blickrichtung OST irgendwo im Territorium. // Ein Vertretungshamster soll die Koernerhaufen bis zur nächsten Wand in // aufsteigender Reihenfolge sortieren. // Voraussetzung: Unterhalb des Standard-Hamsters existieren drei nicht // durch Mauern blockierte Reihen. public void sortiereKoernerHaufen() { this.erlaeuterung( "Ich zaehle nun die Anzahl der zu sortierenden Koernerhaufen."); this.anzahlKoernerHaufen = this.ermittleAnzahlKoernerHaufen(); this.erlaeuterung( "Die Anzahl der zu sortierenden Koernerhaufen betraegt " + this.anzahlKoernerHaufen + ",\nd.h. die Indizes der Haufen liegen zwischen 0 und " + (this.anzahlKoernerHaufen-1) + "."); this.mergeSort(0, this.anzahlKoernerHaufen-1); this.beendeSortierung(); } // der MergeSort-Algorithmus wird auf dem Array zwischen den // angegebenen Indizes ausgefuehrt private void mergeSort(int linkerIndex, int rechterIndex) { if (linkerIndex < rechterIndex) { int mittlererIndex = (linkerIndex + rechterIndex) / 2; mergeSort(linkerIndex, mittlererIndex); this.erlaeuterung( "Die Koernerhaufen von Index " + linkerIndex + " bis " + mittlererIndex + " sind (nun) sortiert."); mergeSort(mittlererIndex+1, rechterIndex); this.erlaeuterung( "Die Koernerhaufen von Index " + (mittlererIndex+1) + " bis " + rechterIndex + " sind (nun) sortiert."); mischen(linkerIndex, mittlererIndex, rechterIndex); } else { this.linkerIndexHamster.markiereIndex(linkerIndex); this.rechterIndexHamster.markiereIndex(rechterIndex); } } // mischt die zwei (sortierten) Teil-Arrays von linkerIndex bis // mittlererIndex und mittlererIndex+1 bis rechterIndex private void mischen(int linkerIndex, int mittlererIndex, int rechterIndex) { this.linkerIndexHamster.markiereIndex(linkerIndex); this.rechterIndexHamster.markiereIndex(rechterIndex); this.erlaeuterung( "Es folgt nun das Mischen der Koernerhaufen von Index " + linkerIndex + " bis " + mittlererIndex + " und " + (mittlererIndex+1) + " bis " + rechterIndex + ".\n" + "Zunaechst werden dazu die Koernerhaufen in eine Hilfsreihe verschoben."); // beide Teil-Arrays in das Hilfsarray (zwei Zeilen darunter) kopieren int linkerZeiger = linkerIndex; this.linkerZeigerHamster.markiereIndex(linkerZeiger); int rechterZeiger = mittlererIndex + 1; this.rechterZeigerHamster.markiereIndex(rechterZeiger); for (int i=linkerIndex; i<=rechterIndex; i++) { this.laufeZuIndex(i); int koerner = this.nimmAlle(); this.setzeBlickrichtung(Hamster.SUED); this.vor(2); this.gib(koerner); this.setzeBlickrichtung(Hamster.NORD); this.vor(2); } // jeweils das kleinere Element der beiden Teil-Arrays zurückkopieren int aktuellerZeiger = linkerIndex; this.aktuellerZeigerHamster.markiereIndex(aktuellerZeiger); this.erlaeuterung( "Beim Mischen wird der kleinere der beiden markierten " + "Koernerhaufen der Hilfsreihe an\ndie markierte Kachel der " + "eigentlichen Reihe (weißer Markierungshamster) zurueckkopiert."); while (linkerZeiger <= mittlererIndex && rechterZeiger <= rechterIndex) { if (linkerZeigerHamster.liefereAnzahlKoerner() <= rechterZeigerHamster.liefereAnzahlKoerner()) { transportiereKoerner(linkerZeigerHamster.liefereIndex()); linkerZeiger++; this.linkerZeigerHamster.markiereIndex(linkerZeiger); } else { transportiereKoerner(rechterZeigerHamster.liefereIndex()); rechterZeiger++; this.rechterZeigerHamster.markiereIndex(rechterZeiger); } aktuellerZeiger++; if (aktuellerZeiger <= rechterIndex) { this.aktuellerZeigerHamster.markiereIndex(aktuellerZeiger); } } // falls vorhanden Reste des ersten Teil-Arrays zurückkopieren while (linkerZeiger <= mittlererIndex) { transportiereKoerner(linkerZeigerHamster.liefereIndex()); linkerZeiger++; this.linkerZeigerHamster.markiereIndex(linkerZeiger); aktuellerZeiger++; if (aktuellerZeiger <= rechterIndex) { this.aktuellerZeigerHamster.markiereIndex(aktuellerZeiger); } } // falls vorhanden Reste des zweiten Teil-Arrays zurückkopieren while (rechterZeiger <= rechterIndex) { transportiereKoerner(rechterZeigerHamster.liefereIndex()); rechterZeiger++; this.rechterZeigerHamster.markiereIndex(rechterZeiger); aktuellerZeiger++; if (aktuellerZeiger <= rechterIndex) { this.aktuellerZeigerHamster.markiereIndex(aktuellerZeiger); } } } // transportiert die Koerner aus private void transportiereKoerner(int index) { this.laufeZuIndex(index); this.setzeBlickrichtung(Hamster.SUED); this.vor(2); int koerner = this.nimmAlle(); this.setzeBlickrichtung(Hamster.NORD); this.vor(2); this.laufeZuIndex(aktuellerZeigerHamster.liefereIndex()); this.gib(koerner); } private void beendeSortierung() { this.laufeZuSpalte(this.startSpalte); this.setzeBlickrichtung(Hamster.OST); this.erlaeuterung("Sortierung erfolgreich beendet!"); } }
Mit dem folgendem objektorientierten Hamster-Programm (SortierenMitMergeSort) (SortierenMitMergeSort.ham) können Sie sich von den Hamstern den MergeSort-Algorithmus demonstrieren lassen.
void main() { Hamster paul = Hamster.getStandardHamster(); String antwort = Hamster.getStandardHamster().liesZeichenkette( "Moechten Sie textuelle Erlaeuterungen (ja/nein)?"); SortierHamster sortierer = new MergeSortHamster(antwort.equals("ja")); sortierer.sortiereKoernerHaufen(); }
Der MergeSort-Algorithmus ist stabil. MergeSort arbeitet nicht in-place.
MergeSort erfordert unabhängig vom Aufbau des zu sortierenden Arrays, d.h. auch im unglücklichsten Fall, Vergleiche der Array-Elemente. n ist dabei die Anzahl an zu sortierenden Elementen. Allerdings ist zusätzlicher zu n proportionaler Speicherplatz erforderlich.
Im Web finden sich zahlreiche weitere Informationen zum MergeSort-Algorithmus, z.B. unter