Schlagwort-Archive: Java

Algorithmen und Datenstrukturen: Der QuickSort in Java

Der Algorithmus

Der QuickSort-Algorithmus ist ein vorwiegend rekursiver Sortieralgorithmus nach dem Teile- und Herrsche-Prinzip. Er lässt sich aber auch iterativ implementieren. Er ruft sich so lange selber auf, bis sich zwei Pointer auf Array-Elemente von links und rechts treffen oder eine Teilliste in einem Rekursionsschritt nur noch aus einem Element besteht. Alle Array-Elemente auf der linken und rechten Stack-Seite eines festgelegtem oder zufällig gewähltem Pivot-Elements sind nach jedem Rekursionsschritt jeweils kleiner (linke Seite) oder größer (rechte Seite). Dabei wird die zu sortierende Liste in zwei Teillisten („linke“ und „rechte“ Teilliste) getrennt.

Oder anders: Quicksort wählt ein sogenanntes Pivotelement aus der Liste aus. Alle Elemente, die kleiner als das Pivotelement sind, kommen in die linke Teilliste, und alle, die größer sind, in die rechte Teilliste. Die Elemente, die gleich dem Pivotelement sind, können sich beliebig auf die Teillisten verteilen. Nach der Aufteilung sind die Elemente der linken Liste kleiner oder gleich den Elementen der rechten Liste.

Anschließend muss man also noch jede Teilliste in sich sortieren, um die Sortierung zu vollenden. Dazu wird der Quicksort-Algorithmus jeweils auf der linken und auf der rechten Teilliste ausgeführt. Jede Teilliste wird dann wieder in zwei Teillisten aufgeteilt und auf diese jeweils wieder der Quicksort-Algorithmus angewandt, und so weiter. Diese Selbstaufrufe werden als Rekursion bezeichnet. Wenn eine Teilliste der Länge eins oder null auftritt, so ist diese bereits sortiert und es erfolgt der Abbruch der Rekusion.

Der Prinzip:

  1. Auswahl eines festem oder zufälligem Pivot-Elements (muss nicht das kleinste Element im Array sein)
  2. Sortierung aller kleineren Zahlen als das Pivot-Element auf die linke Seite des Stapels/Stacks
  3. Sortierung aller größeren Zahlen als das Pivot-Element auf die rechte Seite des Stapels/Stacks
  4. Rekursiver Aufruf für die linke Seite des Stapels/Stacks bis sich die zwei Left- und Right-Pointer überschneiden, weil keine kleinere Zahl mehr gefunden wurde, die links vom Pivot-Element einsortiert werden kann.
  5. Rekursiver Aufruf für die rechte Seite des Stapels/Stacks bis sich die zwei Left- und Right-Pointer überschneiden, weil keine größere Zahl mehr gefunden wurde, die rechts vom Pivot-Element einsortiert werden kann.
package AlgoDat;

public class QuickSort {
    // Zu sortierendes Array
    private int myArray[] = {22, 6, 2, 4, 10, 3, 9, 7, 5, 8, 1};

    // Hält die Klasse als instanziertes Objekt
    @SuppressWarnings("unused")
    private static QuickSort program;
    private java.util.Random rnd = new java.util.Random();

    // Hilfsfunktion für das Ausgeben des Arrays
    public void OutputOfIntArray(int myArray[])
    {
        if (myArray != null)
        {
            for (int i = 0; i < myArray.length; i++) {
                if (i > 0) System.out.print(";");
                System.out.print(myArray[i]);
            }

            System.out.println();
        }
    }

    // Generiert ein zufälliges Integer-Array der Größe size mit Werten zwischen 0 und upperLimit
    public int[] generateIntegerArrayOfSize(int size, int upperLimit)
    {       
        int[] generatedArray = new int[size];
        
        for (int i = 0; i < generatedArray.length; i++)
        {
            generatedArray[i] = rnd.nextInt(upperLimit);
        }

        return generatedArray;
    }

    public void makeQuickSort(int[] arrayToSort, int fromIndex, int toIndex)
    {
        // Rekursionsabbruch
        if (fromIndex >= toIndex)
        {
            return;
        }

        // Das Pivot-Element muss beim Teile-Herrsche-Prinzip nicht die kleinste Zahl im Array sein 
        // und kann willkürlich gewählt werden - wie hier das letzte Element des Array
        // int pivot = arrayToSort[toIndex];
        // Im Average Case performt der Quicksort aber besser, wenn es zufällig ausgewählt wird
        
        // Zufälliges Pivot-Element?
        boolean zufallsPivotElement = true;
        int pivotIndex = zufallsPivotElement ? pivotIndex = rnd.nextInt(toIndex - fromIndex) + fromIndex : toIndex;
        int pivot = arrayToSort[pivotIndex];
        System.out.println("Ich will das Pivot am Index: " + pivotIndex + "("+ pivot +")");

        // Vertausche das Pivot-Element damit es am Schluss der Liste steht
        this.vertausche(arrayToSort, pivotIndex, toIndex);
        
        // Diese Pointer beinhalten den Index der Elemente, die miteinander verglichen und 
        // ggfs. vertauscht werden, wenn sie kleiner/größer als das Pivot sind.
        int leftPointer = fromIndex;
        int rightPointer = toIndex - 1;

        // Partitioniere, so lange wie sich die Pointer nicht in die Quere kommen
        while (leftPointer < rightPointer)
        {
            // Verschiebe leftPointer so lange, bis ein Element gefunden wird, was größer als das Pivot ist
            // (oder wie sich die Pointer nicht überschneiden)
            while (arrayToSort[leftPointer] <= pivot && leftPointer < rightPointer)
            {
                leftPointer++;
            }

            // Verschiebe rightPointer so lange, bis ein Element gefunden wird, was größer als das Pivot ist
            // (oder wie sich die Pointer nicht überschneiden)
            while (arrayToSort[rightPointer] >= pivot && leftPointer < rightPointer)
            {
                rightPointer--;
            }

            // Vertausche die Elemente am Index von leftPointer und rightPointer
            this.vertausche(arrayToSort, leftPointer, rightPointer);
        }

        // Vertausche die Elemente am Array-Index von leftPointer und toIndex
        if(arrayToSort[leftPointer] > arrayToSort[toIndex]) 
        {
            this.vertausche(arrayToSort, leftPointer, toIndex);
        }
        else
        {
            leftPointer = toIndex;
        }

        // QuickSort für die linke Seite vom Pivot-Element
        this.makeQuickSort(arrayToSort, fromIndex, leftPointer - 1);

        // QuickSort für die rechte Seite vom Pivot-Element
        this.makeQuickSort(arrayToSort, leftPointer + 1, toIndex);
    }

    public void vertausche(int[] arrayToSwap, int idx1, int idx2)
    {
        int swapVar = arrayToSwap[idx1];
        arrayToSwap[idx1] = arrayToSwap[idx2];
        arrayToSwap[idx2] = swapVar;
    }

    // Konstruktor
    public QuickSort()
    {
        System.out.print("Vorher: ");
        this.OutputOfIntArray(myArray);

        long startZeit = System.nanoTime();

        // this.OutputOfIntArray(this.generateIntegerArrayOfSize(1000, 100));
        this.makeQuickSort(myArray, 0, myArray.length - 1);

        long endZeit = System.nanoTime();

        System.out.print("Nachher: ");
        this.OutputOfIntArray(myArray);

        System.out.println("Ich habe " + (endZeit - startZeit) + " ns gebraucht.");
    }

    public static void main(String[] args) 
    {
        // Instanziere aus den statischem Programm ein echtes Objekt
        // damit nicht alle Methoden und Variablen static sein müssen.
        program = new QuickSort();
    }
}

Ausgabe

Vorher: 22;6;2;4;10;3;9;7;5;8;1
Ich will das Pivot am Index: 0(22)
Ich will das Pivot am Index: 4(10)
Ich will das Pivot am Index: 1(6)
Ich will das Pivot am Index: 0(1)
Ich will das Pivot am Index: 2(2)
Ich will das Pivot am Index: 3(4)
Ich will das Pivot am Index: 7(7)
Ich will das Pivot am Index: 7(8)
Nachher: 1;2;3;4;5;6;7;8;9;10;22
Ich habe 29708200 ns gebraucht.

Komplexität: O-Notation (Ordnung)

Der Quick-Sort hat im Best- und im Average-Case die Komplexität O(n* log(n)) und im Worst-Case die Komplexität O(n²)

Algorithmen und Datenstrukturen: Der Merge Sort in Java

Der Algorithmus

Der MergeSort-Algorithmus ist ein stabiler Sortieralgorithmus nach dem Teile-und-Herrsche-Prinzip. Im ersten Teil wird das Array rekursiv so lange halbiert, bis in jeder Liste nur noch 1 Element vorhanden ist. Bei einer Liste mit einem Element geht man davon aus, dass die Liste automatisch als sortiert gilt. Danach können alle nachfolgenden Operationen von zwei sortierten Listen ausgehen, wodurch weniger Operationen beim Zusammenführen ausreichen um ein neues sortiertes Array zu erhalten.

Im zweiten Teil werden die bereits sortierten Listenhälften verglichen und mit der Komplexität O(n) je rekursiver Iteration verglichen und zusammengeführt (siehe den nachfolgenden JAVA Code).

package AlgoDat;

public class MergeSort {
    // Zu sortierendes Array
    private int myArray[] = {22, 6, 2, 4, 10, 3, 9, 7, 5, 8, 1};

    // Hält die Klasse als instanziertes Objekt
    @SuppressWarnings("unused")
    private static MergeSort program;

    // Hilfsfunktion für das Ausgeben des Arrays
    public void OutputOfIntArray(int myArray[])
    {
        if (myArray != null)
        {
            for (int i = 0; i < myArray.length; i++) {
                if (i > 0) System.out.print(";");
                System.out.print(myArray[i]);
            }

            System.out.println();
        }
    }

    public void mergeSort(int myArray[])
    {
        // zunächst wird das Array ab der Hälfte in zwei
        // Arrays links und rechts geteilt, das passiert
        // rekursiv ... und zwar so lange bis jedes Element
        // für sich nur noch 1x vorhanden ist (Teile-Herrsche-Prinzip).
        // Das Teilen ist damit erledigt und nun sollte da
        // Problem dadurch beherrschbarer werden -> nun 
        // werden die Einzelelemente wieder in Arrays sortiert. 
        // Die Abbruchbedingung der Rekursion ist, wenn die Liste 
        // nur noch ein einziges Element hat, wobei die Liste bei
        // einem einzigem Element als sortiert gilt.
        if (myArray.length == 1) return;

        // weist bei ungeraden Zahlen eine abgerundete Ganzzahl zu
        int indexHaelfte = myArray.length / 2;

        int[] linkeHaelfte = new int[indexHaelfte];

        // Die abgerundete Ganzzahl kann von der Länge abgezogen werden
        // um die Größe des rechten Arrays zu erhalten.
        int[] rechteHaelfte = new int[myArray.length - indexHaelfte];

        // Befülle die linke Hälfte 
        for (int i = 0; i < indexHaelfte; i++)
        {
            linkeHaelfte[i] = myArray[i];
        }

        // Befülle die rechte Hälfte 
        for (int i = indexHaelfte; i < myArray.length; i++)
        {
            rechteHaelfte[i - indexHaelfte] = myArray[i];
        }

        // Hier ist der rekursive Aufruf, indem die beiden Hälften an
        // die mergeSort-Methode selbst übergeben wird.
        this.mergeSort(linkeHaelfte);
        this.mergeSort(rechteHaelfte);

        // Hier werden die beiden Arrays wieder kombiniert (geMerged)
        this.merge(myArray, linkeHaelfte, rechteHaelfte);
    }

    private void merge(int[] mergeArray, int[] linkeHaelfte, int[] rechteHaelfte)
    {
        System.out.print("Vergleiche linke Hälfte: ");
        this.OutputOfIntArray(linkeHaelfte);
        System.out.print("mit rechter Hälfte ");
        this.OutputOfIntArray(rechteHaelfte);

        int iteratorLinks = 0, iteratorRechts = 0, iteratorMergeArray = 0;

        // Da die linke und reche Hälfte bereits sortiert sind, funktioniert die Zuweisung
        // in ein neues Array mit einer einzigen Schleife der Komplexität/Ordnung O(n).
        while (iteratorLinks < linkeHaelfte.length && iteratorRechts < rechteHaelfte.length)
        {
            if (linkeHaelfte[iteratorLinks] <= rechteHaelfte[iteratorRechts])
            {
                mergeArray[iteratorMergeArray] = linkeHaelfte[iteratorLinks];
                iteratorLinks++;
            }
            else
            {
                mergeArray[iteratorMergeArray] = rechteHaelfte[iteratorRechts];
                iteratorRechts++;
            }

            iteratorMergeArray++;
        }

        // Wenn noch Elemente in der linken Hälfte waren, die nicht verglichen wurden,
        // werden diese dem Merged Array hinzugefügt
        while (iteratorLinks < linkeHaelfte.length)
        {
            mergeArray[iteratorMergeArray] = linkeHaelfte[iteratorLinks];
            iteratorLinks++;
            iteratorMergeArray++;
        }

        // Wenn noch Elemente in der rechten Array-Hälfte waren, die nicht verglichen wurden,
        // werden diese dem Merged Array hinzugefügt
        while (iteratorRechts < rechteHaelfte.length)
        {
            mergeArray[iteratorMergeArray] = rechteHaelfte[iteratorRechts];
            iteratorRechts++;
            iteratorMergeArray++;
        }
    }

    // Konstruktor
    public MergeSort()
    {
        System.out.print("Vorher: ");
        this.OutputOfIntArray(myArray);

        // Wir lagern alles weitere in eine eigene Methode aus, 
        // da MergeSort ein rekursiver Algorithmus ist, dessen 
        // Funktion aufgerufen werden muss und beeginnen mit dem 
        // unsortierten Array
        this.mergeSort(myArray);

        System.out.print("Nachher: ");
        this.OutputOfIntArray(myArray);
    }

    public static void main(String[] args) 
    {
        // Instanziere aus den statischem Programm ein echtes Objekt
        // damit nicht alle Methoden und Variablen static sein müssen.
        program = new MergeSort();
    }
}

Ausgabe

Vorher: 22;6;2;4;10;3;9;7;5;8;1
Vergleiche linke Hälfte: 22
mit rechter Hälfte 6
Vergleiche linke Hälfte: 4
mit rechter Hälfte 10
Vergleiche linke Hälfte: 2
mit rechter Hälfte 4;10
Vergleiche linke Hälfte: 6;22
mit rechter Hälfte 2;4;10
Vergleiche linke Hälfte: 9
mit rechter Hälfte 7
Vergleiche linke Hälfte: 3
mit rechter Hälfte 7;9
Vergleiche linke Hälfte: 8
mit rechter Hälfte 1
Vergleiche linke Hälfte: 5
mit rechter Hälfte 1;8
Vergleiche linke Hälfte: 3;7;9
mit rechter Hälfte 1;5;8
Vergleiche linke Hälfte: 2;4;6;10;22
mit rechter Hälfte 1;3;5;7;8;9
Nachher: 1;2;3;4;5;6;7;8;9;10;22

Komplexität: O-Notation (Ordnung)

Der MergeSort besteht aus 3 Teilen, die sich zu der Gesamtkomplexität zusammensetzen.

Teil 1: (Rekursives) Teilen

Wenn wir ein Array der Größe n in zwei Hälften teilen, benötigen wir

Schritte. Hierbei handelt es sich um den Logarithmus dualis, also den Logarithmus zur Basis 2 (wessen Basis für die 2 Hälften spricht, in die aufgeteilt wird). Dies liegt also daran, dass wir die Liste in jeder Rekursionsebene halbieren. Wir fragen hier also mit welcher Zahl man 2 potenzieren muss um n zu erhalten.

Wenn wir in dem unsortierten Array 11 Elemente haben, würden wir also fragen, mit welcher Zahl wir 2 potenzieren müssen um 11 zu erhalten. Den Exponenten den wir hier erhalten wäre 4:

Wir runden die Kommazahl immer auf die nächste volle Zahl auf, da wir keine halben Schritte machen können. Die Zahl 4 entspricht hier auch der Rekursionstiefe, die benötigt wird bis der aktuelle Rekursions-Heap nur noch aus einem Element bekommt, was zu Abbruch der Rekursion führt.

Der erste Teil besitzt somit die Ordnung:

Teil 2 und 3: Sortieren und Mergen

Der Schritt des Zusammenführens (des Mergens) beider Liste ist mit der Sortierung kombiniert. Hier wird vorausgesetzt dass bereits sortierte Listenhälften vorliegen, da man zwei bereits sortierte Arrays mit der Komplexität n vergleichen kann. Beim Zusammenführen der sortierten Teillisten benötigen wir

Zeit. Dieser Schritt ist linear abhängig von der Gesamtanzahl der Elemente.

Gesamt-Komplexität

Somit ergibt sich die Gesamtkomplexität:

im Worst-, Normal- und Best-Case.

Algorithmen und Datenstrukturen: Der original Bubble Sort in Java

Der Algorithmus

Beim „Bubble Sort“ markiert die äußere Schleife das letzte Element des noch unsortierten Bereichs, während die innere Schleife anfangs das erste Element markiert und bei jeder Iteration bis zu dem Element, was die äußere Schleife markiert, läuft. Jedes Element der aktuellen Iteration der inneren Schleife wird mit dem Nachfolgeelement der aktuellen Iteration verglichen. Ist ein Element kleiner/größer (je nachdem wie der Vergleichsoperator „gedreht“ ist) wird getauscht. Auf diese Weise „blubbert“ die größte Zahl immer bis zum Anfang des sortierten Bereichs, welchen die äußere Schleife markiert, nach oben.

package AlgoDat;

public class BubbleSort {
    // Zu sortierendes Array
    private int myArray[] = {22, 6, 2, 4, 10, 3, 9, 7, 5, 8, 1};

    // Hält die Klasse als instanziertes Objekt
    @SuppressWarnings("unused")
    private static BubbleSort program;

    // Hilfsfunktion für das Ausgeben des Arrays
    public void OutputOfIntArray(int myArray[])
    {
        if (myArray != null)
        {
            for (int i = 0; i < myArray.length; i++) {
                if (i > 0) System.out.print(";");
                System.out.print(myArray[i]);
            }

            System.out.println();
        }
    }

    // Konstruktor
    public BubbleSort()
    {
        System.out.print("Vorher: ");
        this.OutputOfIntArray(myArray);

        // Äußere Schleife: Laufe das zu sortierende Array von Rechts nach links durch,
        // damit der bereits sortierte Bereich rechts wächst
        for (int i = myArray.length; i > 1 ; i--)
        {
            System.out.println("Iteration " + i);

            // Innere Schleife: Laufe das Array bis zum bereits sortierten Bereich der
            //                  äußeren Schleife durch
            for (int j = 0; j < i - 1; j++)
            {
                // Tausche die Array-Inhalte 
                if (myArray[j] > myArray[j + 1])
                {
                    this.vertausche(myArray, j, j + 1);

                    System.out.print("Tausche: ");
                    this.OutputOfIntArray(myArray);
                }
            }
        }

        System.out.print("Nachher: ");
        this.OutputOfIntArray(myArray);
    }

    public void vertausche(int[] arrayToSwap, int idx1, int idx2)
    {
        int swapVar = arrayToSwap[idx1];
        arrayToSwap[idx1] = arrayToSwap[idx2];
        arrayToSwap[idx2] = swapVar;
    }

    public static void main(String[] args) 
    {
        // Instanziere aus den statischem Programm ein echtes Objekt
        // damit nicht alle Methoden und Variablen static sein müssen.
        program = new BubbleSort();
    }
}

Ausgabe

Vorher: 22;6;2;4;10;3;9;7;5;8;1
Iteration 11
Tausche: 6;22;2;4;10;3;9;7;5;8;1
Tausche: 6;2;22;4;10;3;9;7;5;8;1
Tausche: 6;2;4;22;10;3;9;7;5;8;1
Tausche: 6;2;4;10;22;3;9;7;5;8;1
Tausche: 6;2;4;10;3;22;9;7;5;8;1
Tausche: 6;2;4;10;3;9;22;7;5;8;1
Tausche: 6;2;4;10;3;9;7;22;5;8;1
Tausche: 6;2;4;10;3;9;7;5;22;8;1
Tausche: 6;2;4;10;3;9;7;5;8;22;1
Tausche: 6;2;4;10;3;9;7;5;8;1;22
Iteration 10
Tausche: 2;6;4;10;3;9;7;5;8;1;22
Tausche: 2;4;6;10;3;9;7;5;8;1;22
Tausche: 2;4;6;3;10;9;7;5;8;1;22
Tausche: 2;4;6;3;9;10;7;5;8;1;22
Tausche: 2;4;6;3;9;7;10;5;8;1;22
Tausche: 2;4;6;3;9;7;5;10;8;1;22
Tausche: 2;4;6;3;9;7;5;8;10;1;22
Tausche: 2;4;6;3;9;7;5;8;1;10;22
Iteration 9
Tausche: 2;4;3;6;9;7;5;8;1;10;22
Tausche: 2;4;3;6;7;9;5;8;1;10;22
Tausche: 2;4;3;6;7;5;9;8;1;10;22
Tausche: 2;4;3;6;7;5;8;9;1;10;22
Tausche: 2;4;3;6;7;5;8;1;9;10;22
Iteration 8
Tausche: 2;3;4;6;7;5;8;1;9;10;22
Tausche: 2;3;4;6;5;7;8;1;9;10;22
Tausche: 2;3;4;6;5;7;1;8;9;10;22
Iteration 7
Tausche: 2;3;4;5;6;7;1;8;9;10;22
Tausche: 2;3;4;5;6;1;7;8;9;10;22
Iteration 6
Tausche: 2;3;4;5;1;6;7;8;9;10;22
Iteration 5
Tausche: 2;3;4;1;5;6;7;8;9;10;22
Iteration 4
Tausche: 2;3;1;4;5;6;7;8;9;10;22
Iteration 3
Tausche: 2;1;3;4;5;6;7;8;9;10;22
Iteration 2
Tausche: 1;2;3;4;5;6;7;8;9;10;22
Nachher: 1;2;3;4;5;6;7;8;9;10;22

Komplexität: O-Notation (Ordnung)

Diese Version des Bubble Sort-Algorithmus hat im Worst-, Average- und Best-Case eine Laufzeitkomplexität von O(n²)

Es gibt eine optimierte Version des Bubble Sort Algorithmus, der hier im Blog im Artikel „Der optimierte Bubble Sort in Java“ vorgestellt wird.

Algorithmen und Datenstrukturen: Der Selection Sort in Java

Der Algorithmus

package AlgoDat;

public class SelectionSort {
    // Zu sortierendes Array
    private int myArray[] = {22, 6, 2, 4, 10, 3, 9, 7, 5, 8, 1};

    // Hält die Klasse als instanziertes Objekt
    @SuppressWarnings("unused")
    private static SelectionSort program;

    // Hilfsfunktion für das Ausgeben des Arrays
    public void OutputOfIntArray(int myArray[])
    {
        if (myArray != null)
        {
            for (int i = 0; i < myArray.length; i++) {
                if (i > 0) System.out.print(";");
                System.out.print(myArray[i]);
            }

            System.out.println();
        }
    }

    // Konstruktor
    public SelectionSort()
    {
        System.out.print("Vorher: ");
        this.OutputOfIntArray(myArray);

        // Laufe das zu sortierende Array von Anfang bis Ende durch
        for (int idxSortierterBereich = 0; idxSortierterBereich < myArray.length - 1 ; idxSortierterBereich++)
        {
            // Starte an der Index-Position der äußersten Schleife - davor ist schon alles sortiert
            int indexPivotElement = idxSortierterBereich;

            for (int idxUnsortierterBereich = idxSortierterBereich + 1; idxUnsortierterBereich < myArray.length; idxUnsortierterBereich++)
            {
                // ... und merke dir das kleinste Element
                if (myArray[indexPivotElement] > myArray[idxUnsortierterBereich])
                {
                    indexPivotElement = idxUnsortierterBereich;
                }
            }

            // Dieser Code tauscht das neu gefundene Minimum mit dem Element am aktuellen Index der äußeren Schleife                
            int swapVar = myArray[indexPivotElement];
            myArray[indexPivotElement] = myArray[idxSortierterBereich];
            myArray[idxSortierterBereich] = swapVar;

            System.out.print("Tausche: ");
            this.OutputOfIntArray(myArray);
        }

        System.out.print("Nachher: ");
        this.OutputOfIntArray(myArray);
    }

    public static void main(String[] args) 
    {
        // Instanziere aus den statischem Programm ein echtes Objekt
        // damit nicht alle Methoden und Variablen static sein müssen.
        program = new SelectionSort();
    }
}

Ausgabe

Vorher: 22;6;2;4;10;3;9;7;5;8;1
Tausche: 1;6;2;4;10;3;9;7;5;8;22
Tausche: 1;2;6;4;10;3;9;7;5;8;22
Tausche: 1;2;3;4;10;6;9;7;5;8;22
Tausche: 1;2;3;4;10;6;9;7;5;8;22
Tausche: 1;2;3;4;5;6;9;7;10;8;22
Tausche: 1;2;3;4;5;6;9;7;10;8;22
Tausche: 1;2;3;4;5;6;7;9;10;8;22
Tausche: 1;2;3;4;5;6;7;8;10;9;22
Tausche: 1;2;3;4;5;6;7;8;9;10;22
Tausche: 1;2;3;4;5;6;7;8;9;10;22
Nachher: 1;2;3;4;5;6;7;8;9;10;22

Komplexität: O-Notation (Ordnung)

Zwei verschaltete Schleifen.
Die äußere Schleife läuft von 1 bis n;
Die innere Schleife läuft vom Element der äußeren Schleife bis Schluss -> also n/2, da der Bereich immer kleiner wird.

O(T(n)) = O(n²)

Algorithmen und Datenstrukturen: Der Insertion Sort in Java

Der Algorithmus

Markant sind die zwei verschachtelten Schleifen, wobei die innere Schleife meistens eine While-Schleife mit 2 Bedingungen ist. Ein Index, welcher die Position der Trennung vom sortierten (links) und vom unsortierten (rechts) Bereich präsentiert, wird runtergezählt und das Array-Element an der Index-Position entspricht nach Ende der Schleife der Array-Position, mit der ein gemerktes Element getauscht werden kann. Während der sortierte Bereich (immer links) mit dem ersten Element des unsortierten Bereichs (immer rechts), welches sich gemerkt wird, verglichen wird, werden alle Elemente bis zu diesem Punkt um eins nach rechts gerückt. Dadurch existiert die zu tauschende Position nach diesem Schritt zwei Mal und wird durch das gemerkte Element ausgetauscht.

Die zweite Bedingung der inneren While-Schleife verhindert, das der runterzählende Index negativ wird.

package AlgoDat;

class InsertionSort {
    // Zu sortierendes Array
    private int myArray[] = {22, 6, 2, 4, 10, 3, 9, 7, 5, 8, 1};
    
    // Hält die Klasse InsertionSort als instanziertes Objekt
    @SuppressWarnings("unused")
    private static InsertionSort program;

    // Hilfsfunktion für das Ausgeben des Arrays
    public void OutputOfIntArray(int myArray[])
    {
        if (myArray != null)
        {
            for (int i = 0; i < myArray.length; i++) {
                if (i > 0) System.out.print(";");
                System.out.print(myArray[i]);
            }

            System.out.println();
        }
    }

    // Konstruktor
    public InsertionSort()
    {
        this.OutputOfIntArray(myArray);

        // Bei 1 beginnen, da das Element mit dem Index 0 bereits als sortiert gilt 
        for (int idxSortierterBereich = 1; idxSortierterBereich < myArray.length; idxSortierterBereich++)
        {
            // Merke dir das erste Element vom unsortierten Bereich
            int swapVar = myArray[idxSortierterBereich];
            System.out.println("Gemerkt vor dem Aufrücken: " + swapVar);

            // Das erste unsortierte Element auf der rechten Seite wird in den bereits sortierten Bereich 
            // auf der linken Seite eingefügt, womit der unsortierte Bereich immer weiter nach rechts rückt
            // und dann verschwindet.
            int idxUnsortierterBereich = idxSortierterBereich; 
            System.out.println("Der unsortierte Bereich beginnt bei Index: " + idxUnsortierterBereich);

            // Laufe im Array von rechts nach links, so lange wie vorige Element noch größer wie 
            // das erste Element vom unsortierten Bereich ist und der Bereich nicht negativ wird
            while (idxUnsortierterBereich > 0 && myArray[idxUnsortierterBereich - 1] > swapVar)
            {
                // Alles eins nach rechts im Array rücken bis zum bereits sortierten Bereich
                myArray[idxUnsortierterBereich] = myArray[idxUnsortierterBereich - 1] ;
                idxUnsortierterBereich--;

                System.out.print("Nach rechts aufrücken: ");
                this.OutputOfIntArray(myArray);
            }

            System.out.println("Tausche Stelle " + (idxUnsortierterBereich + 1) + " (" + myArray[idxUnsortierterBereich] + 
            ") mit gemerkter Stelle " + (idxSortierterBereich + 1) + " (" + swapVar + ")");

            myArray[idxUnsortierterBereich] = swapVar;

            System.out.print("Getauscht: ");
            this.OutputOfIntArray(myArray);
        }
    }

    public static void main(String[] args) 
    {
        // Instanziere aus den statischem Programm ein echtes Objekt
        // damit nicht alle Methoden und Variablen static sein müssen.
        program = new InsertionSort();
    }
}

Ausgabe

22;6;2;4;10;3;9;7;5;8;1
Gemerkt vor dem Aufrücken: 6
Der unsortierte Bereich beginnt bei Index: 1
Nach rechts aufrücken: 22;22;2;4;10;3;9;7;5;8;1
Tausche Stelle 1 (22) mit gemerkter Stelle 2 (6)
Getauscht: 6;22;2;4;10;3;9;7;5;8;1
Gemerkt vor dem Aufrücken: 2
Der unsortierte Bereich beginnt bei Index: 2
Nach rechts aufrücken: 6;22;22;4;10;3;9;7;5;8;1
Nach rechts aufrücken: 6;6;22;4;10;3;9;7;5;8;1
Tausche Stelle 1 (6) mit gemerkter Stelle 3 (2)
Getauscht: 2;6;22;4;10;3;9;7;5;8;1
Gemerkt vor dem Aufrücken: 4
Der unsortierte Bereich beginnt bei Index: 3
Nach rechts aufrücken: 2;6;22;22;10;3;9;7;5;8;1
Nach rechts aufrücken: 2;6;6;22;10;3;9;7;5;8;1
Tausche Stelle 2 (6) mit gemerkter Stelle 4 (4)
Getauscht: 2;4;6;22;10;3;9;7;5;8;1
Gemerkt vor dem Aufrücken: 10
Der unsortierte Bereich beginnt bei Index: 4
Nach rechts aufrücken: 2;4;6;22;22;3;9;7;5;8;1
Tausche Stelle 4 (22) mit gemerkter Stelle 5 (10)
Getauscht: 2;4;6;10;22;3;9;7;5;8;1
Gemerkt vor dem Aufrücken: 3
Der unsortierte Bereich beginnt bei Index: 5
Nach rechts aufrücken: 2;4;6;10;22;22;9;7;5;8;1
Nach rechts aufrücken: 2;4;6;10;10;22;9;7;5;8;1
Nach rechts aufrücken: 2;4;6;6;10;22;9;7;5;8;1
Nach rechts aufrücken: 2;4;4;6;10;22;9;7;5;8;1
Tausche Stelle 2 (4) mit gemerkter Stelle 6 (3)
Getauscht: 2;3;4;6;10;22;9;7;5;8;1
Gemerkt vor dem Aufrücken: 9
Der unsortierte Bereich beginnt bei Index: 6
Nach rechts aufrücken: 2;3;4;6;10;22;22;7;5;8;1
Nach rechts aufrücken: 2;3;4;6;10;10;22;7;5;8;1
Tausche Stelle 5 (10) mit gemerkter Stelle 7 (9)
Getauscht: 2;3;4;6;9;10;22;7;5;8;1
Gemerkt vor dem Aufrücken: 7
Der unsortierte Bereich beginnt bei Index: 7
Nach rechts aufrücken: 2;3;4;6;9;10;22;22;5;8;1
Nach rechts aufrücken: 2;3;4;6;9;10;10;22;5;8;1
Nach rechts aufrücken: 2;3;4;6;9;9;10;22;5;8;1
Tausche Stelle 5 (9) mit gemerkter Stelle 8 (7)
Getauscht: 2;3;4;6;7;9;10;22;5;8;1
Gemerkt vor dem Aufrücken: 5
Der unsortierte Bereich beginnt bei Index: 8
Nach rechts aufrücken: 2;3;4;6;7;9;10;22;22;8;1
Nach rechts aufrücken: 2;3;4;6;7;9;10;10;22;8;1
Nach rechts aufrücken: 2;3;4;6;7;9;9;10;22;8;1
Nach rechts aufrücken: 2;3;4;6;7;7;9;10;22;8;1
Nach rechts aufrücken: 2;3;4;6;6;7;9;10;22;8;1
Tausche Stelle 4 (6) mit gemerkter Stelle 9 (5)
Getauscht: 2;3;4;5;6;7;9;10;22;8;1
Gemerkt vor dem Aufrücken: 8
Der unsortierte Bereich beginnt bei Index: 9
Nach rechts aufrücken: 2;3;4;5;6;7;9;10;22;22;1
Nach rechts aufrücken: 2;3;4;5;6;7;9;10;10;22;1
Nach rechts aufrücken: 2;3;4;5;6;7;9;9;10;22;1
Tausche Stelle 7 (9) mit gemerkter Stelle 10 (8)
Getauscht: 2;3;4;5;6;7;8;9;10;22;1
Gemerkt vor dem Aufrücken: 1
Der unsortierte Bereich beginnt bei Index: 10
Nach rechts aufrücken: 2;3;4;5;6;7;8;9;10;22;22
Nach rechts aufrücken: 2;3;4;5;6;7;8;9;10;10;22
Nach rechts aufrücken: 2;3;4;5;6;7;8;9;9;10;22
Nach rechts aufrücken: 2;3;4;5;6;7;8;8;9;10;22
Nach rechts aufrücken: 2;3;4;5;6;7;7;8;9;10;22
Nach rechts aufrücken: 2;3;4;5;6;6;7;8;9;10;22
Nach rechts aufrücken: 2;3;4;5;5;6;7;8;9;10;22
Nach rechts aufrücken: 2;3;4;4;5;6;7;8;9;10;22
Nach rechts aufrücken: 2;3;3;4;5;6;7;8;9;10;22
Nach rechts aufrücken: 2;2;3;4;5;6;7;8;9;10;22
Tausche Stelle 1 (2) mit gemerkter Stelle 11 (1)
Getauscht: 1;2;3;4;5;6;7;8;9;10;22

Komplexität: O-Notation (Ordnung)

O(T(n)) = O(n^2/2+n/2-n) = O(n^2/2) = O (n^2)

Die äußere Schleife läuft von 1 bis n-1, während die innere While-Schleife vom ersten Element des unsortierten Bereichs bis zu der Stelle der richtige Einfügeposition läuft.

Äußere Schleife: Iteriert n-1 mal.
Innere Schleife: Iteriert 1x für Element 1, 2x für Element 2, 3x für Element 3, … n mal für Element n, was zu einer Laufzeit von

führt. Daraus folgt:

Additive Bestandteile, Faktoren und Konstanten fallen bei der Bestimmung der Ordnung weg, daher ist die Ordnung O(n²). Die Domäne ist der dominante Teil der Ordnung – sie ist n² .