Inhaltsverzeichnis

Mergesort

Vorbemerkungen

Mergesort und Quicksort sind essentielle Bestandteile der heutigen informationstechnologischen Infrastruktur - ohne diese beiden Algorithmen sähe die Welt anders aus, da praktisch alle IT Systeme ständig Daten sortieren müssen.

Grundsätzliche Funktionsweise

Mergesort ist eine Divide-and-Conquer Algorithmus, der prinzipiell folgendermaßen funktioniert:

Zentrale Teilaufgabe: Merge

Das Zusammenfügen der sortierten Teilarrays ist ein zentraler Bestandteil des Mergesort-Verfahrens, darum schauen wir uns das hier an einem Beispiel nochmal etwas genauer an, wie man diesen Vorgang verstehen und implementieren kann.

Gegeben sind also zwei sortierte Teilarrays, die zu einem sortierten Array zusammengefügt werden sollen:

Dazu gehen wir wie folgt vor:

i und j laufen jetzt durch die Teilarrays, die zugehörigen Elemente werden verglichen. Das kleinere der beiden Elemente wird ins ursprüngliche Array übernommen und der jeweilige Index inkrementiert. Gelangt ein Index ans Ende, kann der Rest des unvollständig abgearbeiteten Teilarrays übernommen werden.


(A1)

Implementiere im Bluej-Projekt https://codeberg.org/qg-info-unterricht/algs4-sort-bluej in der Klasse StandaloneMerge das Zusammenführen der beiden Teilarrays a und b in der Methode merge() (ohne Parameter). Die Hinweise sind gestufte Hilfen, die du bei Problemen nacheinander ansehen kannst.

Hinweis 1:

Hinweis 2:

Hinweis 3:

Hinweis 4:

Lösungsvorschlag


(A2)

Gib zunächst getrennt den Aufwand der Splitphase und der Mergephase in O-Notation an und begründe deine Aussage. Überlege dann, wie daraus abgeleitet der generelle Aufwand für den gesamten Mergesort-Algorithmus ist.

Mergesort: Versuch 1


(A3) Naiver Mergesort

Implementiere unter Verwendung der oben implementierten merge-Methode einen naiven Mergesort-Algorithmus in der Methode public String[] msort(String[] a). Eine Vorlage findest du im Bluej-Projekt https://codeberg.org/qg-info-unterricht/algs4-sort-bluej in der Klasse MergeSortNaiv. Eine Methode public String[] merge(String[] s, String[] t) ist entsprechend bereits implementiert.

Orientiere dich an folgendem Pseudocode:

msort: String[] a
  wenn a.laenge ist 1: 
    return a
  ende wenn
  
  mitte = a.lange/2
  
  ta1=teilarray von a von 0 bis mitte-1
  ta2=teilarray von mitte bis a.laenge-1
  
  return merge(msort(ta1), msort(ta2))

Arrayerzeugung ist teuer

Prinzipiell funktioniert unser "naiver Mergesort", jedoch ist die häufig auftretende Operation der Arrayerzeugung und das Hineinkopieren der Array-Elemente beim Teilen des Arrays teuer und sollte vermieden werden.

Merge reloaded

Wir befassen uns zunächst nochmal mit dem Merge-Vorgang und implementieren mit unseren Kenntnissen vom ersten Versuch die Methode

public void merge(String[] a, String[] speicher, int min, int mitte, int max) 

in der Klasse StandaloneMerge. Um diese Methode aufrufen zu können, müssen wir die beiden Arrays a und b im Konstruktor zusammenfügen und außerdem ein Speicherarray derselben Länge erzeugen, das man dem Methodenaufruf mitgeben kann.


(A4)

Der "Kniff" ist später, dass man das Speicher-Array vor den rekursiven Aufrufen nur einmal erzeugt und dann mit den Parametern min, mitte und max steuert, welche Bereiche des Arrays zusammengefügt werden sollen. So muss man nicht in der Rekursion neue Arrays erzeugen, wie beim naiven Ansatz.

Es ist also wichtig, beim Übertragen der Elemente ins Speicher-Array in der merge Methode bei min zu beginnen und bei max zu enden, ebenso wie die beiden Indizes i und j bei min und bei mitte+1 beginnen:

public void merge(String[] a, String[] speicher, int min, int mitte, int max) {
 
        // Übertragen der Elemente von min bis max in 
        // das bereits vorhandene Speicherarray
        for(int i=min; i<=max;i++) {
            speicher[i]=a[i];
        }
 
        int i=min;
        int j=mitte+1;
 
        for(int k=min; k<=max; k++) {
         // Die vier Fälle von oben
        }
 
        System.out.print("In einem Speicherarray: ");
        System.out.println(Arrays.toString(a));
    }

(A5)

Vervollständige die Implementierung, indem du die vier Fälle passend in die neue Methode überträgst.

Mergesort mit einem Speicher

Unter Verwendung der neuen Merge-Methode können wir jetzt eine Mergesort Implementation erzeugen, die nicht in jedem Rekursionsschritt neue Arrays erzeugen muss.


(A7)

Im BlueJ-Projekt findest du eine Methode MergeSort, in der dieses Vorgehen vorbereitet ist. Vervollständige die Implementation.

Material

FilenameFilesizeLast modified
01_mergesort_merge.odp301.9 KiB16.02.2023 07:28
01_mergesort_merge.pdf142.5 KiB16.02.2023 07:28
merge.drawio.png4.7 KiB15.02.2023 17:39
merge2.drawio.png12.7 KiB15.02.2023 17:49
mergesort.drawio.png24.9 KiB15.02.2023 17:31
mergesort1.drawio.png62.0 KiB15.02.2023 21:43