web-dev-qa-db-de.com

Rekursion von Mergesort verstehen

Die meisten Mergesort-Implementierungen, die ich sehe, sind ähnlich. Einführung in Algorithmen buchen zusammen mit Online-Implementierungen, die ich suche. Meine Rekursions-Chops gehen nicht viel weiter, als sich mit der Fibonacci-Generation zu beschäftigen (was ziemlich einfach war). Vielleicht sind es die vielen Rekursionen, die mich in den Sinn kommen, aber ich kann nicht einmal durch den Code gehen und verstehen, was los ist, noch bevor ich überhaupt getroffen habe die Merge-Funktion.

Wie geht das durch? Gibt es eine Strategie oder eine Lektüre, die ich erfahren sollte, um den Prozess hier besser zu verstehen?

void mergesort(int *a, int*b, int low, int high)
{
    int pivot;
    if(low<high)
    {
        pivot=(low+high)/2;
        mergesort(a,b,low,pivot);
        mergesort(a,b,pivot+1,high);
        merge(a,b,low,pivot,high);
    }
}

und die Verschmelzung (obwohl ich ehrlich gesagt festgefahren bin, bevor ich überhaupt zu diesem Teil komme)

void merge(int *a, int *b, int low, int pivot, int high)
{
    int h,i,j,k;
    h=low;
    i=low;
    j=pivot+1;

    while((h<=pivot)&&(j<=high))
    {
        if(a[h]<=a[j])
        {
            b[i]=a[h];
            h++;
        }
        else
        {
            b[i]=a[j];
            j++;
        }
        i++;
    }
    if(h>pivot)
    {
        for(k=j; k<=high; k++)
        {
            b[i]=a[k];
            i++;
        }
    }
    else
    {
        for(k=h; k<=pivot; k++)
        {
            b[i]=a[k];
            i++;
        }
    }
    for(k=low; k<=high; k++) a[k]=b[k];
}
19
2c2c

Ich denke, dass der Funktionsname "sort" in MergeSort ein bisschen irreführend ist. Er sollte eigentlich "divide" heißen.

Hier ist eine Visualisierung des laufenden Algorithmus. 

enter image description here

Bei jeder Rekursion der Funktion wird an einer kleineren und kleineren Unterteilung des Eingabearrays gearbeitet, beginnend mit der linken Hälfte. Jedes Mal, wenn die Funktion von der Rekursion zurückkehrt, wird sie fortgesetzt und beginnt entweder mit der rechten Hälfte zu arbeiten, oder kehrt erneut zu einer größeren Hälfte zurück.

So was

[************************]mergesort
[************]mergesort(lo,mid)
[******]mergesort(lo,mid)
[***]mergesort(lo,mid)
[**]mergesort(lo,mid)
 [**]mergesort(mid+1,hi)
[***]merge
   [***]mergesort(mid+1,hi)
   [**]mergesort*(lo,mid)
    [**]mergesort(mid+1,hi)
   [***]merge
[******]merge
      [******]mergesort(mid+1,hi)
      [***]mergesort(lo,mid)
      [**]mergesort(lo,mid)
       [**]mergesort(mid+1,hi)
      [***]merge
         [***]mergesort(mid+1,hi)
         [**]mergesort(lo,mid)
           [**]mergesort(mid+1,hi)
         [***]merge
      [******]merge
[************]merge
            [************]mergesort(mid+1,hi)
            [******]mergesort(lo,mid)
            [***]mergesort(lo,mid)
            [**]mergesort(lo,mid)
             [**]mergesort(mid+1,hi)
            [***]merge
               [***]mergesort(mid+1,hi)
               [**]mergesort(lo,mid)
                 [**]mergesort(mid+1,hi)
               [***]merge
            [******]merge
                  [******]mergesort(mid+1,hi)
                  [***]mergesort(lo,mid)
                  [**]mergesort*(lo,mid)
                    [**]mergesort(mid+1,hi)
                  [***]merge
                     [***]mergesort(mid+1,hi)    
                     [**]mergesort(lo,mid)
                      [**]mergesort(mid+1,hi)
                     [***]merge
                  [******]merge
            [************]merge
[************************]merge
14
slashdottir

ZUSAMMENFÜHREN, SORTIEREN:

1) Teilen Sie das Array in zwei Hälften
2) Sortieren Sie die linke Hälfte
3) Sortieren Sie die rechte Hälfte
4) Füge die beiden Hälften zusammen

 enter image description here

 enter image description here

18
abe312

Es liegt auf der Hand, diese Zusammenführungs-Sortierung auf einem kleinen Array, beispielsweise Größe 8 (Potenz von 2 ist hier günstig), auf Papier auszuprobieren. Stellen Sie sich vor, Sie sind ein Computer, der den Code ausführt, und sehen, ob er etwas klarer wird. 

Ihre Frage ist etwas unklar, weil Sie nicht erklären, was Sie verwirrend finden, aber es klingt, als würden Sie versuchen, die rekursiven Anrufe in Ihrem Kopf abzurollen. Was vielleicht eine gute Sache ist oder nicht, aber ich denke, es kann leicht dazu führen, dass Sie zu viel im Kopf haben. Anstatt zu versuchen, den Code vom Anfang bis zum Ende zu verfolgen, können Sie das Konzept abstrakt verstehen. Zusammenführen, sortieren:

  1. Teilt das Array in zwei Hälften
  2. Sortiert die linke Hälfte
  3. Sortiert die rechte Hälfte
  4. Führt die beiden Hälften zusammen

(1) sollte für Sie ziemlich offensichtlich und intuitiv sein. Für Schritt (2) ist dies die Schlüsseleinsicht: Die linke Hälfte eines Arrays ... ist ein Array. Vorausgesetzt, Ihre Zusammenführungssortierung funktioniert , sollte es in der Lage sein, die linke Hälfte des Arrays zu sortieren. Recht? Schritt (4) ist eigentlich ein ziemlich intuitiver Teil des Algorithmus. Ein Beispiel sollte es trivial machen:

at the start
left: [1, 3, 5], right: [2, 4, 6, 7], out: []

after step 1
left: [3, 5], right: [2, 4, 6, 7], out: [1]

after step 2
left: [3, 5], right: [4, 6, 7], out: [1, 2]

after step 3
left: [5], right: [4, 6, 7], out: [1, 2, 3]

after step 4
left: [5], right: [6, 7], out: [1, 2, 3, 4]

after step 5
left: [], right: [6, 7], out: [1, 2, 3, 4, 5]

after step 6
left: [], right: [7], out: [1, 2, 3, 4, 5, 6]

at the end
left: [], right: [], out: [1, 2, 3, 4, 5, 6, 7]

Wenn Sie also davon ausgehen, dass Sie (1) und (4) verstehen, wäre eine andere Möglichkeit, Merge-Sorte zu denken, die folgende. Stellen Sie sich vor, jemand anderes schrieb mergesort() und Sie sind zuversichtlich, dass es funktioniert. Dann können Sie diese Implementierung von mergesort() verwenden, um zu schreiben:

sort(myArray)
{
    leftHalf = myArray.subArray(0, myArray.Length/2);
    rightHalf = myArray.subArray(myArray.Length/2 + 1, myArray.Length - 1);

    sortedLeftHalf = mergesort(leftHalf);
    sortedRightHalf = mergesort(rightHalf);

    sortedArray = merge(sortedLeftHalf, sortedRightHalf);
}

Beachten Sie, dass sort keine Rekursion verwendet. Es sagt nur "sortiere beide Hälften und füge sie dann zusammen". Wenn Sie das Zusammenführungsbeispiel oben verstanden haben, sehen Sie hoffentlich intuitiv, dass diese sort-Funktion das zu tun scheint, was sie sagt ... sortieren.

Wenn Sie es genauer betrachten, sieht sort() ziemlich genau wie mergesort() aus! Das ist, weil es mergesort() ist (außer dass es keine Basisfälle gibt, weil es nicht rekursiv ist!).

Aber so denke ich gerne an rekursive Funktionen. Angenommen, die Funktion funktioniert, wenn Sie sie aufrufen. Behandeln Sie es wie eine Blackbox, die das tut, was Sie brauchen. Wenn Sie diese Annahme treffen, ist es oft einfach, herauszufinden, wie Sie diese Blackbox ausfüllen. Können Sie die Eingabe für eine bestimmte Eingabe in kleinere Eingaben aufteilen, um sie an Ihre Blackbox anzuschließen? Nachdem Sie das Problem gelöst haben, müssen Sie nur noch die Basisfälle zu Beginn Ihrer Funktion behandeln (in solchen Fällen müssen Sie keine rekursiven Aufrufe machen.) mergesort([]) gibt beispielsweise ein leeres Array zurück rekursiv nicht an mergesort()).

Schließlich ist dies ein wenig abstrakt, aber eine gute Möglichkeit, Rekursion zu verstehen, besteht darin, mathematische Beweise mithilfe von Induktion zu schreiben. Die gleiche Strategie, die zum Schreiben eines Beweises durch Induktion verwendet wird, wird zum Schreiben einer rekursiven Funktion verwendet:

Mathebeweis:

  • Zeigen Sie, dass der Anspruch für die Basisfälle zutrifft
  • Angenommen, es gilt für Eingaben, die kleiner als einige n sind.
  • Verwenden Sie diese Annahme, um zu zeigen, dass es für eine Eingabe der Größe n noch gilt.

Rekursive Funktion:

  • Behandeln Sie die Basisfälle
  • Angenommen, Ihre rekursive Funktion arbeitet mit Eingaben, die kleiner sind als einige n.
  • Verwenden Sie diese Annahme, um eine Eingabe der Größe n zu behandeln.
8
rliu

In Bezug auf den Rekursionsteil der Zusammenführungsart habe ich diese Seite als sehr hilfreich empfunden. Sie können dem Code folgen, während er ausgeführt wird. Es zeigt Ihnen, was zuerst ausgeführt wird und was als Nächstes folgt.

Tom

6

Ich entschuldige mich, wenn dies so beantwortet wurde. Ich gebe zu, dass dies nur eine Skizze ist und keine tiefgehende Erklärung. 

Obwohl es nicht offensichtlich ist, zu sehen, wie der eigentliche Code der Rekursion entspricht, konnte ich die Rekursion im Allgemeinen auf diese Weise verstehen.

Nehmen Sie als Beispiel den unsortierten Satz {2,9,7,5}. Der merge_sort-Algorithmus wird aus Gründen der Kürze nachstehend mit "ms" bezeichnet. Dann können wir die Operation wie folgt skizzieren:

schritt 1: ms (ms (2), ms (9)), ms (7), ms (5))

schritt 2: ms (ms ({2}, {9}), ms ({7}, {5}))

schritt 3: ms ({2,9}, {5,7})

schritt 4: {2,5,7,9}

Es ist wichtig zu wissen, dass merge_sort eines Singuletts (wie {2}) einfach das Singlet (ms (2) = {2}) ist, so dass wir auf der tiefsten Rekursionsebene unsere erste Antwort erhalten. Die verbleibenden Antworten taumeln dann wie Dominosteine, wenn die inneren Rekursionen abgeschlossen sind und zusammengefügt werden.

Ein Teil des Genies des Algorithmus ist die Art und Weise, wie die rekursive Formel von Schritt 1 automatisch durch ihre Konstruktion erstellt wird. Was mir dabei geholfen hat, war zu denken, wie man den ersten Schritt von einer statischen Formel zu einer allgemeinen Rekursion macht.

3
Alan G.

die mergesort() teilt das Array einfach in zwei Hälften, bis die if-Bedingung fehlschlägt, nämlich low < high. Wenn Sie mergesort() zweimal aufrufen: eine mit low bis pivot und zweitens mit pivot+1 bis high, werden die Sub-Arrays noch weiter unterteilt.

Nehmen wir ein Beispiel:

a[] = {9,7,2,5,6,3,4}
pivot = 0+6/2 (which will be 3)
=> first mergesort will recurse with array {9,7,2} : Left Array
=> second will pass the array {5,6,3,4} : Right Array

Es wiederholt sich so lange, bis sich in jedem left sowie right Array .. ein Element befindet. __ Am Ende haben Sie etwas Ähnliches:

L : {9} {7} {2} R : {5} {6} {3} {4} (each L and R will have further sub L and R)
=> which on call to merge will become

L(L{7,9} R{2}) : R(L{5,6} R{3,4})
As you can see that each sub array are getting sorted in the merge function.

=> on next call to merge the next L and R sub arrays will get in order
L{2,7,9} : R{3,4,5,6}

Now both L and R sub array are sorted within
On last call to merge they'll be merged in order

Final Array would be sorted => {2,3,4,5,6,7,9}

Sehen Sie sich die Antwortschritte von @roliu an

3
Dangling Cruze

Wenn Sie die rekursive Methode aufrufen, führt sie die echte Funktion nicht gleichzeitig aus, wenn sie in den Stapelspeicher stapelt. Und wenn die Bedingung nicht erfüllt ist, geht es zur nächsten Zeile.

Beachten Sie, dass dies Ihr Array ist:

int a[] = {10,12,9,13,8,7,11,5};

So funktioniert Ihre Sortiermethode wie folgt:

mergeSort(arr a, arr empty, 0 , 7);
mergeSort(arr a, arr empty, 0, 3);
mergeSort(arr a, arr empty,2,3);
mergeSort(arr a, arr empty, 0, 1);

after this `(low + high) / 2 == 0` so it will come out of first calling and going to next:

    mergeSort(arr a, arr empty, 0+1,1);

for this also `(low + high) / 2 == 0` so it will come out of 2nd calling also and call:

    merger(arr a, arr empty,0,0,1);
    merger(arr a, arr empty,0,3,1);
    .
    .
    So on

Daher werden alle Sortierwerte in leeren Arrern gespeichert. Dies hilft möglicherweise zu verstehen, wie die rekursive Funktion funktioniert

0
Milind

Ich weiß, dass dies eine alte Frage ist, aber ich wollte meine Gedanken darüber werfen, was mir dabei geholfen hat, die Zusammenführungsart zu verstehen.

Es gibt zwei große Teile, um die Sortierung zusammenzuführen

  1. Aufteilen des Arrays in kleinere Brocken (Teilen)
  2. Zusammenfügen des Arrays (Eroberung)

Die Rolle der Wiederholung ist einfach der teilende Teil.

Ich denke, das, was die meisten Leute verwirrt, ist, dass sie denken, es gibt eine Menge Logik in der Aufteilung und in der Entscheidung, was zu spalten ist, aber die eigentliche Logik der Sortierung geschieht auf der merge . Die Rekursion ist einfach dazu da, die erste Hälfte zu teilen und dann die zweite Hälfte wirklich nur zu schleifen und Dinge zu kopieren.

Ich sehe einige Antworten, die Pivots erwähnen, aber Ich würde empfehlen, das Wort "Pivot" nicht mit der Merge-Sortierung zu verknüpfen, da dies eine einfache Möglichkeit ist, die Merge-Sortierung mit quicksort zu verwechseln. Sie sind beide "Divide and Conquer" -Algorithmen. Bei der Zusammenführungssortierung geschieht die Aufteilung immer in der Mitte, während Sie bei Quicksort mit der Aufteilung klug vorgehen können, wenn Sie einen optimalen Drehpunkt auswählen.

0
aug

Prozess, um das Problem in Unterprobleme zu unterteilen Das gegebene Beispiel wird Ihnen helfen, Rekursion zu verstehen. int A [] = {Anzahl des zu verkürzenden Elements.}, int p = 0; (Liebhaberindex). int r = A. Länge - 1 (höherer Index).

class DivideConqure1 {
void devide(int A[], int p, int r) {
    if (p < r) {
        int q = (p + r) / 2; // divide problem into sub problems.
        devide(A, p, q);   //divide left problem into sub problems
        devide(A, q + 1, r); //divide right problem into sub problems
        merger(A, p, q, r);  //merger the sub problem
    }
}

void merger(int A[], int p, int q, int r) {
    int L[] = new int[q - p + 1];
    int R[] = new int[r - q + 0];

    int a1 = 0;
    int b1 = 0;
    for (int i = p; i <= q; i++) {  //store left sub problem in Left temp
        L[a1] = A[i];
        a1++;
    }
    for (int i = q + 1; i <= r; i++) { //store left sub problem in right temp
        R[b1] = A[i];
        b1++;
    }
    int a = 0;
    int b = 0; 
    int c = 0;
    for (int i = p; i < r; i++) {
        if (a < L.length && b < R.length) {
            c = i + 1;
            if (L[a] <= R[b]) { //compare left element<= right element
                A[i] = L[a];
                a++;
            } else {
                A[i] = R[b];
                b++;
            }
        }
    }
    if (a < L.length)
        for (int i = a; i < L.length; i++) {
            A[c] = L[i];  //store remaining element in Left temp into main problem 
            c++;
        }
    if (b < R.length)
        for (int i = b; i < R.length; i++) {
            A[c] = R[i];  //store remaining element in right temp into main problem 
            c++;
        }
}
0
Shravan Kumar