next up previous contents
Nächste Seite: 2.8 Zusammenfassung und Anmerkungen Aufwärts: 2. Sortieren Vorherige Seite: 2.6 QuickSort: Sortieren durch   Inhalt

Unterabschnitte


2.7 MergeSort: Sortieren durch Mischen

MergeSort ist wie QuickSort ein rekursiver Sortieralgorithmus.

2.7.1 Algorithmus

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++];
    }

  }
}

2.7.2 Visualisierendes Hamster-Programm

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();
}

2.7.3 Analyse des Algorithmus

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, $\approx$ $n$ $log(n)$ 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


next up previous contents
Nächste Seite: 2.8 Zusammenfassung und Anmerkungen Aufwärts: 2. Sortieren Vorherige Seite: 2.6 QuickSort: Sortieren durch   Inhalt
Dietrich Boles 2005-04-18