Kurs:Algorithmen und Datenstrukturen/Kapitel 2/BubbleSort

BubbleSort

Bearbeiten

Herleitung

Bearbeiten

Ausgehend von der Definition eines sortierten Arrays

 

(wir verwenden hier das " " fuer eine allgemeine Ordnungsrelation), koennen wir einen ersten, naiven Algorithmus zum sortieren eines Arrays konstruieren:

Wir durchlaufen das Array und vergleichen dabei jedes Element   (beginnend bei i = 0) mit dem darauffolgenden Element  . Sind die zwei Elemente nicht sortiert, so werden sie vertauscht. Wir wiederholen diesen Vorgang bis das Array sortiert ist.


Implementierung in Oberon

Bearbeiten

In Oberon würde eine PROCEDURE, welche folgenden Algorithmus für Daten des Typs INTEGER implementiert, wie folgt aussehen:

 1. PROCEDURE BubbleSort* ( VAR a : ARRAY OF INTEGER ; n : INTEGER );
 2.     VAR
 3.         i : INTEGER;
 4.         temp, swaps : INTEGER;
 5.     BEGIN

            (* Arraylänge überprüfen *)
 6.         IF n > LEN(a) THEN
 7.             Out.String("Error: n groesser als Arraylaenge!"); Out.Ln;
 8.             RETURN;
 9.         END;

            (* Wiederhole bis alles sortiert ist... *)
10.         REPEAT
11.             swaps := 0;

                (* Für jedes Element... *)
12.             FOR i := 0 TO n-2 DO

                    (* Vergleiche und Vertausche mit Nachbar *)
13.                 IF ~(a[i] <= a[i+1]) THEN
14.                     temp := a[i]; a[i] := a[i+1]; a[i+1] := temp;
15.                     INC(swaps);
16.                 END

17.             END

18.         UNTIL swaps = 0;
19.     END BubbleSort;

Am Anfang unserer Prozedur (Zeile 6) überprüfen wir zuerst, ob n innerhalb der Grenzen des Arrays liegt. Die Schleife von Zeile 10 bis 18 ist die Hauptschleife unseren Algorithmus. Wir beachten, dass die Schleife in Zeile 12 von 0 bis n-2 geht, da die Array-Nummerierung in Oberon bei 0 anfängt.

Die Variable swaps gibt an, wieviele Vertauschungen im Durchgang vorgenommen wurden. Wurde nichts vertauscht, so ist unser Array gemäß Definition sortiert und wir können aufhören (Zeile 14).

Funktioniert sowas überhaupt?

Bearbeiten

Wir können unsere Sortierprozedur in ein Modul namens Sorting packen und mit ein paar Zahlen testen:

MODULE Sorting;

    IMPORT
        In, Out;
        
    CONST
        MaxN = 100;
        
    PROCEDURE BubbleSort* ( VAR a : ARRAY OF INTEGER ; n : INTEGER );
        ...
        END BubbleSort;
        
    PROCEDURE Go*;
        VAR
            a : ARRAY MaxN OF INTEGER;
            i, n : INTEGER;
        BEGIN
            In.Open; In.Int(n);
            FOR i := 0 TO n-1 DO In.Int(a[i]); END;
            BubbleSort(a,n);
            FOR i := 0 TO n-1 DO Out.Int(a[i],10); END;
            Out.Ln;
        END Go;

BEGIN
    Out.Open;
END Sorting.

Sorting.Go 5 3 4 2 5 1 ~

Und sie funktioniert tatsächlich. Die übergebenen Zahlen werden in aufsteigender Reihenfolge sortiert und ausgegeben. Funktioniert das aber auch auf jeden Fall?

Was sicher ist, ist dass im ersten Durchlauf das (nach unserer Relation) größte Element zur letzten Stelle des Arrays verschoben wird. Dies kann man damit zeigen, dass, falls   das groesste Element ist,   immer versagen wird und das Element an der Stelle   vertauscht wird. Das gleiche spielt sich dann ab, bis das größte Element am Ende des Arrays bei   sitzt. Aus dieser Position fällt es auch nie wieder heraus, denn, ist   das größte Element, so wird   fuer jede Besetzung von   gelten.

Sitzt das größte Element nach dem ersten Durchlauf am Ende des Arrays, so kann das zweitgrößte Element nach dem gleichen Prinzip ungehindert zur zweitletzten Stelle rutschen, und so weiter bis alle Elemente sortiert sind.

Wegen dieses Hinaufrutschens der Elemente, ähnlich des Aufsteigens von Blasen in einer Flüssigkeit, heißt dieser Algorithmus in der Literatur auch BubbleSort.

Was kostet das?

Bearbeiten

Worst-Case Analyse

Bearbeiten

Im ersten Durchlauf führt BubbleSort   Vergleiche und, im schlimmsten Fall   Vertauschungen aus.

Im k-ten Durchlauf werden   Vergleiche benötigt.

Average-Case Analyse

Bearbeiten

Die average-case Analyse ist wesentlich komplizierter. Wir betrachten hierzu das Schicksal eines einzelnen Elements  . Um an der richtigen Stelle im Array zu landen, wird   von allen Elementen, welche größer sind als es selber und links von ihm liegen, "überholt" werden. Zudem muss es selber alle Elemente, welche kleiner sind als es selber und rechts von ihm liegen, selber überholen.

Für ein Element an der Stelle  , welches eigentlich an die Stelle   gehören würde, ist die durchschnittliche Anzahl Elemente, welche größer wären, aber links von ihm liegen

 

Anders ausgedrückt: wählen wir   Elemente aus, die mit einer Wahrscheinlichkeit   kleiner sind als das  -te Element. Analog dazu sitzen im Durchschnitt rechts von unserem Element

 

Elemente, die kleiner wären als unser Element. Die zu erwartende Anzahl Vertauschungen für ein Element, welches an die  -te Stelle gehört und mit Wahrscheinlichkeit   an der Stelle   sitzt, ist somit

 

Die durchschnittliche Anzahl Vertauschungen ist somit unabhängig von der eigentlichen Stelle des Elements. Bei   Elementen haben wir somit im Durchschnitt

 

Vertauschungen. Der zusätzliche Faktor   kommt daher, dass jede Vertauschung beiden vertauschten Elementen zugute kommt. Die Anzahl Vertauschungen im average-case ist somit auch in der Größenordnung  .

Um die Anzahl der durchschnittlich zu erwartenden Durchläufe zu berechnen, müssen wir beachten, dass pro Durchlauf eines der größeren Elemente links weggeschoben wird. Die kleineren Elemente rechts können in einem einzigen Durchlauf übersprungen werden. Dies geschieht aber selten in einem einzigen Zug: es können rechts größere Elemente dazwischenliegen, welche unser Element ausbremsen. Es bestimmt also dasjenige Element, welches die meisten Durchläufe benötigt, wieviele Durchläufe im Schnitt notwendig sind. Dieses Element ist dasjenige, für das   maximal ist -- also das Element, das am weitesten rechts von der ihm zugehörigen Stelle steht.

Wir betrachten die Wahrscheinlichkeit  , dass in einer Permutation von   Elementen kein Element weiter als   rechts von seiner angestammten Position steht.

Für   ist die Lösung trivialerweise  , denn es kann beim besten Willen kein Element   Schritte von seinem angestandenen Platz stehen, weil das Array gerade mal   Elemente hat:

 

Die Lösung für   ist ähnlich trivial: einen Abstand von   ist nur dann möglich, wenn das kleinste Element -- nennen wir es   an der letzten Stelle im Array   steht. Wir haben also nur   von   Möglichkeiten,   irgendwo unterzubringen:

 

Für   ist die Lage ähnlich: wir können   weder an der Stelle   noch   platzieren und   nicht an der Stelle   hinlegen. Wir haben also für     von   und für   nur   von   (wir haben ja schon   dazwischen platziert) Möglichkeiten:

 

Folgt man dieser Logik weiter, erhalten wir die Formel

 

Die Wahrscheinlichkeit nun, dass ein unsortiertes Array der Länge   einen maximalen rechten Abstand von genau   enthält ist

 
  • Durchschnittstiefe =  
  • gibts dafür eine elegante, geschlossene Form? Sieht aus wie eine Binomialverteilung...