Skip to content
IRC-Coding IRC-Coding
Algorithmen Grundlagen Komplexitätsanalyse Big O Notation Suchalgorithmen Sortieralgorithmen

Algorithmen Grundlagen: Komplexitätsanalyse, Big-O-Notation, Such- & Sortieralgorithmen

Algorithmen Grundlagen mit Komplexitätsanalyse und Big-O-Notation. Suchalgorithmen (linear, binär), Sortieralgorithmen (Bubble, Quick, Merge) mit praktischen Beispielen.

S

schutzgeist

2 min read

Algorithmen Grundlagen: Komplexitätsanalyse, Big-O-Notation, Such- & Sortieralgorithmen

Dieser Beitrag ist eine umfassende Einführung in die Algorithmen Grundlagen – inklusive Komplexitätsanalyse, Big-O-Notation, Suchalgorithmen und Sortieralgorithmen mit praktischen Beispielen.

In a Nutshell

Algorithmen sind schrittweise Anweisungen zur Problemlösung. Big-O-Notation beschreibt ihre Komplexität, Suchalgorithmen finden Elemente, Sortieralgorithmen ordnen Daten.

Kompakte Fachbeschreibung

Algorithmen sind wohldefinierte, endliche Anweisungsfolgen zur Lösung eines Problems. Sie sind die Grundlage der Informatik und Softwareentwicklung.

Komplexitätsanalyse:

  • Zeitkomplexität: Anzahl der Operationen als Funktion der Eingabegröße
  • Speicherkomplexität: Benötigter Speicherplatz
  • Big-O-Notation: Obere Schranke der Komplexität
  • Best/Average/Worst Case: Verschiedene Laufzeitszenarien

Big-O-Notation (wichtigste):

  • O(1): Konstante Zeit
  • O(log n): Logarithmische Zeit
  • O(n): Lineare Zeit
  • O(n log n): Linearithmische Zeit
  • O(n²): Quadratische Zeit
  • O(2ⁿ): Exponentielle Zeit

Prüfungsrelevante Stichpunkte

  • Algorithmen: Wohldefinierte Anweisungsfolgen zur Problemlösung
  • Big-O-Notation: Mathematische Beschreibung der Komplexität
  • Zeitkomplexität: Anzahl der Operationen abhängig von Eingabegröße
  • Suchalgorithmen: Linear Search (O(n)), Binary Search (O(log n))
  • Sortieralgorithmen: Bubble Sort (O(n²)), Quick Sort (O(n log n))
  • Best/Worst/Average Case: Verschiedene Laufzeitszenarien
  • IHK-relevant: Grundlage für effiziente Softwareentwicklung

Kernkomponenten

  1. Algorithmus-Konzept: Eingabe, Verarbeitung, Ausgabe
  2. Komplexitätsanalyse: Zeit- und Speicherplatzbedarf
  3. Big-O-Notation: Asymptotische Analyse
  4. Suchalgorithmen: Lineare und binäre Suche
  5. Sortieralgorithmen: Verschiedene Sortierstrategien
  6. Datenstrukturen: Arrays, Listen, Bäume, Graphen
  7. Rekursion: Selbstaufrufende Algorithmen
  8. Divide and Conquer: Problemlösung durch Teilung

Praxisbeispiele

1. Big-O-Notation und Komplexitätsanalyse

import java.util.*;

public class Komplexitaetsanalyse {
    
    // O(1) - Konstante Zeit
    public int getFirstElement(int[] array) {
        if (array.length == 0) {
            throw new IllegalArgumentException("Array ist leer");
        }
        return array[0];  // Immer eine Operation
    }
    
    // O(n) - Lineare Zeit
    public int findMax(int[] array) {
        if (array.length == 0) {
            throw new IllegalArgumentException("Array ist leer");
        }
        
        int max = array[0];
        for (int i = 1; i < array.length; i++) {  // n Operationen
            if (array[i] > max) {
                max = array[i];
            }
        }
        return max;
    }
    
    // O(n²) - Quadratische Zeit
    public void printPairs(int[] array) {
        for (int i = 0; i < array.length; i++) {        // n Schleifen
            for (int j = 0; j < array.length; j++) {    // n Schleifen
                System.out.println(array[i] + ", " + array[j]);
            }
        }
        // Gesamt: n * n = n² Operationen
    }
    
    // O(log n) - Logarithmische Zeit
    public int powerOfTwo(int n) {
        int result = 1;
        while (n > 0) {  // log₂(n) Schleifendurchläufe
            result *= 2;
            n /= 2;
        }
        return result;
    }
    
    // O(n log n) - Linearithmische Zeit
    public void mergeSort(int[] array) {
        if (array.length <= 1) {
            return;
        }
        
        int mid = array.length / 2;
        int[] left = Arrays.copyOfRange(array, 0, mid);
        int[] right = Arrays.copyOfRange(array, mid, array.length);
        
        mergeSort(left);    // O(log n) Rekursionstiefe
        mergeSort(right);
        
        merge(array, left, right);  // O(n) für jeden Merge
    }
    
    private void merge(int[] result, int[] left, int[] right) {
        int i = 0, j = 0, k = 0;
        
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k++] = left[i++];
            } else {
                result[k++] = right[j++];
            }
        }
        
        while (i < left.length) {
            result[k++] = left[i++];
        }
        
        while (j < right.length) {
            result[k++] = right[j++];
        }
    }
    
    // O(2ⁿ) - Exponentielle Zeit
    public int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return fibonacci(n - 1) + fibonacci(n - 2);  // 2ⁿ Aufrufe
    }
    
    // Komplexitätsanalyse mit Zeitmessung
    public void analyzeComplexity() {
        int[] sizes = {100, 1000, 10000, 100000};
        
        System.out.println("=== Komplexitätsanalyse ===");
        System.out.println("Größe\tO(1)\tO(n)\tO(n²)\tO(log n)");
        
        for (int size : sizes) {
            int[] array = new int[size];
            
            // Array mit Zufallszahlen füllen
            Random random = new Random();
            for (int i = 0; i < size; i++) {
                array[i] = random.nextInt(1000);
            }
            
            // O(1) messen
            long start = System.nanoTime();
            getFirstElement(array);
            long o1Time = System.nanoTime() - start;
            
            // O(n) messen
            start = System.nanoTime();
            findMax(array);
            long onTime = System.nanoTime() - start;
            
            // O(n²) messen (nur für kleine Arrays)
            long on2Time = 0;
            if (size <= 1000) {
                start = System.nanoTime();
                printPairs(array);
                on2Time = System.nanoTime() - start;
            }
            
            // O(log n) messen
            start = System.nanoTime();
            powerOfTwo(size);
            double olognTime = System.nanoTime() - start;
            
            System.out.printf("%d\t%d\t%d\t%d\t%.0f%n",
                             size, o1Time, onTime, on2Time, olognTime);
        }
    }
    
    public static void main(String[] args) {
        Komplexitaetsanalyse analyse = new Komplexitaetsanalyse();
        
        // Komplexitätsanalyse
        analyse.analyzeComplexity();
        
        // Big-O Demonstration
        System.out.println("\n=== Big-O Demonstration ===");
        demonstrateBigO();
        
        // Rekursion vs Iteration
        System.out.println("\n=== Rekursion vs Iteration ===");
        compareRecursionIteration();
    }
    
    private static void demonstrateBigO() {
        int n = 1000;
        Komplexitaetsanalyse demo = new Komplexitaetsanalyse();
        
        System.out.println("Demonstration mit n = " + n);
        
        // O(1) Beispiel
        int[] array = {1, 2, 3, 4, 5};
        System.out.println("O(1) - Erstes Element: " + demo.getFirstElement(array));
        
        // O(n) Beispiel
        int[] largeArray = new int[n];
        for (int i = 0; i < n; i++) {
            largeArray[i] = i;
        }
        System.out.println("O(n) - Maximum: " + demo.findMax(largeArray));
        
        // O(log n) Beispiel
        System.out.println("O(log n) - 2^" + n + " = " + demo.powerOfTwo(n));
        
        // O(n log n) Beispiel
        int[] sortArray = new int[100];
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            sortArray[i] = random.nextInt(1000);
        }
        System.out.println("O(n log n) - Merge Sort durchgeführt");
        demo.mergeSort(sortArray);
        
        // O(n²) Beispiel (kleines Array)
        int[] smallArray = {1, 2, 3, 4, 5};
        System.out.println("O(n²) - Alle Paare:");
        demo.printPairs(smallArray);
    }
    
    private static void compareRecursionIteration() {
        Komplexitaetsanalyse demo = new Komplexitaetsanalyse();
        int n = 30;
        
        System.out.println("Fibonacci n = " + n);
        
        // Rekursive Version (exponentiell)
        long start = System.nanoTime();
        int recursiveResult = demo.fibonacci(n);
        long recursiveTime = System.nanoTime() - start;
        
        // Iterative Version (linear)
        start = System.nanoTime();
        int iterativeResult = fibonacciIterative(n);
        long iterativeTime = System.nanoTime() - start;
        
        System.out.println("Rekursiv: " + recursiveResult + " (" + recursiveTime + "ns)");
        System.out.println("Iterativ: " + iterativeResult + " (" + iterativeTime + "ns)");
        System.out.println("Speedup: " + (recursiveTime / iterativeTime) + "x");
    }
    
    private static int fibonacciIterative(int n) {
        if (n <= 1) return n;
        
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    }
}

2. Suchalgorithmen

import java.util.*;

public class Suchalgorithmen {
    
    // Lineare Suche - O(n)
    public static int linearSearch(int[] array, int target) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == target) {
                return i;  // Element gefunden
            }
        }
        return -1;  // Element nicht gefunden
    }
    
    // Binäre Suche - O(log n) - Array muss sortiert sein
    public static int binarySearch(int[] sortedArray, int target) {
        int left = 0;
        int right = sortedArray.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            
            if (sortedArray[mid] == target) {
                return mid;  // Element gefunden
            } else if (sortedArray[mid] < target) {
                left = mid + 1;  // Rechts weitersuchen
            } else {
                right = mid - 1;  // Links weitersuchen
            }
        }
        
        return -1;  // Element nicht gefunden
    }
    
    // Interpolationssuche - O(log log n) im Durchschnitt
    // Funktioniert nur für gleichmäßig verteilte, sortierte Arrays
    public static int interpolationSearch(int[] sortedArray, int target) {
        int left = 0;
        int right = sortedArray.length - 1;
        
        while (left <= right && target >= sortedArray[left] && target <= sortedArray[right]) {
            if (left == right) {
                return sortedArray[left] == target ? left : -1;
            }
            
            // Interpolationsformel
            int pos = left + ((target - sortedArray[left]) * (right - left)) / 
                        (sortedArray[right] - sortedArray[left]);
            
            if (sortedArray[pos] == target) {
                return pos;
            } else if (sortedArray[pos] < target) {
                left = pos + 1;
            } else {
                right = pos - 1;
            }
        }
        
        return -1;
    }
    
    // Exponentielle Suche - O(log n) für unendlich große Arrays
    public static int exponentialSearch(int[] sortedArray, int target) {
        int n = sortedArray.length;
        
        if (sortedArray[0] == target) {
            return 0;
        }
        
        // Bereich finden, in dem das Element sein könnte
        int i = 1;
        while (i < n && sortedArray[i] <= target) {
            i = i * 2;
        }
        
        // Binäre Suche im gefundenen Bereich
        return binarySearchRange(sortedArray, i / 2, Math.min(i, n - 1), target);
    }
    
    private static int binarySearchRange(int[] array, int left, int right, int target) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            
            if (array[mid] == target) {
                return mid;
            } else if (array[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
    
    // Jump Search - O(√n) für sortierte Arrays
    public static int jumpSearch(int[] sortedArray, int target) {
        int n = sortedArray.length;
        int step = (int) Math.sqrt(n);
        int prev = 0;
        
        // Block finden, in dem das Element sein könnte
        while (sortedArray[Math.min(step, n) - 1] < target) {
            prev = step;
            step += (int) Math.sqrt(n);
            if (prev >= n) {
                return -1;
            }
        }
        
        // Lineare Suche im Block
        while (sortedArray[prev] < target) {
            prev++;
            if (prev == Math.min(step, n)) {
                return -1;
            }
        }
        
        if (sortedArray[prev] == target) {
            return prev;
        }
        
        return -1;
    }
    
    // Performance-Vergleich der Suchalgorithmen
    public static void compareSearchAlgorithms() {
        Random random = new Random();
        int[] sizes = {1000, 10000, 100000, 1000000};
        
        System.out.println("=== Suchalgorithmen Performance-Vergleich ===");
        System.out.println("Größe\tLinear\tBinär\tInterpolation\tJump\tExponential");
        
        for (int size : sizes) {
            int[] array = new int[size];
            
            // Sortiertes Array erstellen
            for (int i = 0; i < size; i++) {
                array[i] = i;
            }
            
            // Zufälliges Ziel auswählen
            int target = random.nextInt(size);
            
            // Lineare Suche
            long start = System.nanoTime();
            int linearResult = linearSearch(array, target);
            long linearTime = System.nanoTime() - start;
            
            // Binäre Suche
            start = System.nanoTime();
            int binaryResult = binarySearch(array, target);
            long binaryTime = System.nanoTime() - start;
            
            // Interpolationssuche
            start = System.nanoTime();
            int interpolationResult = interpolationSearch(array, target);
            long interpolationTime = System.nanoTime() - start;
            
            // Jump Search
            start = System.nanoTime();
            int jumpResult = jumpSearch(array, target);
            long jumpTime = System.nanoTime() - start;
            
            // Exponentielle Suche
            start = System.nanoTime();
            int exponentialResult = exponentialSearch(array, target);
            long exponentialTime = System.nanoTime() - start;
            
            System.out.printf("%d\t%d\t%d\t%d\t\t%d\t%d%n",
                             size, linearTime, binaryTime, interpolationTime, jumpTime, exponentialTime);
            
            // Ergebnisse überprüfen
            assert linearResult == target;
            assert binaryResult == target;
            assert interpolationResult == target;
            assert jumpResult == target;
            assert exponentialResult == target;
        }
    }
    
    public static void main(String[] args) {
        // Test-Arrays erstellen
        int[] unsortedArray = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42};
        int[] sortedArray = {11, 12, 22, 25, 34, 42, 50, 64, 76, 88, 90};
        
        System.out.println("=== Suchalgorithmen Demo ===");
        
        // Lineare Suche
        int target = 25;
        int index = linearSearch(unsortedArray, target);
        System.out.println("Lineare Suche: " + target + " gefunden an Index " + index);
        
        // Binäre Suche
        index = binarySearch(sortedArray, target);
        System.out.println("Binäre Suche: " + target + " gefunden an Index " + index);
        
        // Interpolationssuche
        index = interpolationSearch(sortedArray, 76);
        System.out.println("Interpolationssuche: 76 gefunden an Index " + index);
        
        // Jump Search
        index = jumpSearch(sortedArray, 42);
        System.out.println("Jump Search: 42 gefunden an Index " + index);
        
        // Exponentielle Suche
        index = exponentialSearch(sortedArray, 88);
        System.out.println("Exponentielle Suche: 88 gefunden an Index " + index);
        
        // Performance-Vergleich
        compareSearchAlgorithms();
        
        // Suchalgorithmen-Eigenschaften
        printSearchAlgorithmProperties();
    }
    
    private static void printSearchAlgorithmProperties() {
        System.out.println("\n=== Suchalgorithmen Eigenschaften ===");
        
        String[][] algorithms = {
            {"Lineare Suche", "O(n)", "Unsortiert", "Einfach"},
            {"Binäre Suche", "O(log n)", "Sortiert", "Effizient"},
            {"Interpolationssuche", "O(log log n)", "Sortiert, gleichverteilt", "Sehr effizient"},
            {"Jump Search", "O(√n)", "Sortiert", "Gut für große Arrays"},
            {"Exponentielle Suche", "O(log n)", "Sortiert", "Unendliche Arrays"}
        };
        
        System.out.println("Algorithmus\t\tZeitkomplexität\tVoraussetzung\t\tBeschreibung");
        System.out.println("---------\t\t--------------\t--------------\t\t-----------");
        
        for (String[] algo : algorithms) {
            System.out.printf("%-20s\t%-15s\t%-20s\t%s%n", algo[0], algo[1], algo[2], algo[3]);
        }
    }
}

3. Sortieralgorithmen

import java.util.*;

public class Sortieralgorithmen {
    
    // Bubble Sort - O(n²)
    public static void bubbleSort(int[] array) {
        int n = array.length;
        boolean swapped;
        
        for (int i = 0; i < n - 1; i++) {
            swapped = false;
            
            for (int j = 0; j < n - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    // Elemente tauschen
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                    swapped = true;
                }
            }
            
            // Wenn keine Vertauschungen stattfanden, ist das Array sortiert
            if (!swapped) {
                break;
            }
        }
    }
    
    // Selection Sort - O(n²)
    public static void selectionSort(int[] array) {
        int n = array.length;
        
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            
            // Minimum im unsortierten Teil finden
            for (int j = i + 1; j < n; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            
            // Minimum mit dem aktuellen Element tauschen
            if (minIndex != i) {
                int temp = array[i];
                array[i] = array[minIndex];
                array[minIndex] = temp;
            }
        }
    }
    
    // Insertion Sort - O(n²) worst case, O(n) best case
    public static void insertionSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int key = array[i];
            int j = i - 1;
            
            // Elemente verschieben, bis die richtige Position gefunden ist
            while (j >= 0 && array[j] > key) {
                array[j + 1] = array[j];
                j--;
            }
            
            array[j + 1] = key;
        }
    }
    
    // Quick Sort - O(n log n) average, O(n²) worst case
    public static void quickSort(int[] array) {
        quickSortRecursive(array, 0, array.length - 1);
    }
    
    private static void quickSortRecursive(int[] array, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(array, low, high);
            
            quickSortRecursive(array, low, pivotIndex - 1);
            quickSortRecursive(array, pivotIndex + 1, high);
        }
    }
    
    private static int partition(int[] array, int low, int high) {
        int pivot = array[high];  // Letztes Element als Pivot
        int i = (low - 1);  // Index des kleineren Elements
        
        for (int j = low; j < high; j++) {
            if (array[j] < pivot) {
                i++;
                swap(array, i, j);
            }
        }
        
        swap(array, i + 1, high);
        return i + 1;
    }
    
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    // Merge Sort - O(n log n)
    public static void mergeSort(int[] array) {
        if (array.length <= 1) {
            return;
        }
        
        int mid = array.length / 2;
        int[] left = Arrays.copyOfRange(array, 0, mid);
        int[] right = Arrays.copyOfRange(array, mid, array.length);
        
        mergeSort(left);
        mergeSort(right);
        
        merge(array, left, right);
    }
    
    private static void merge(int[] result, int[] left, int[] right) {
        int i = 0, j = 0, k = 0;
        
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k++] = left[i++];
            } else {
                result[k++] = right[j++];
            }
        }
        
        while (i < left.length) {
            result[k++] = left[i++];
        }
        
        while (j < right.length) {
            result[k++] = right[j++];
        }
    }
    
    // Heap Sort - O(n log n)
    public static void heapSort(int[] array) {
        int n = array.length;
        
        // Max-Heap bauen
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(array, n, i);
        }
        
        // Elemente aus dem Heap extrahieren
        for (int i = n - 1; i > 0; i--) {
            swap(array, 0, i);
            heapify(array, i, 0);
        }
    }
    
    private static void heapify(int[] array, int n, int i) {
        int largest = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        
        if (left < n && array[left] > array[largest]) {
            largest = left;
        }
        
        if (right < n && array[right] > array[largest]) {
            largest = right;
        }
        
        if (largest != i) {
            swap(array, i, largest);
            heapify(array, n, largest);
        }
    }
    
    // Performance-Vergleich der Sortieralgorithmen
    public static void compareSortingAlgorithms() {
        Random random = new Random();
        int[] sizes = {1000, 5000, 10000, 20000};
        
        System.out.println("=== Sortieralgorithmen Performance-Vergleich ===");
        System.out.println("Größe\tBubble\tSelection\tInsertion\tQuick\tMerge\tHeap");
        
        for (int size : sizes) {
            // Test-Array erstellen
            int[] originalArray = new int[size];
            for (int i = 0; i < size; i++) {
                originalArray[i] = random.nextInt(10000);
            }
            
            long[] times = new long[6];
            String[] names = {"Bubble", "Selection", "Insertion", "Quick", "Merge", "Heap"};
            
            // Bubble Sort
            int[] array = originalArray.clone();
            long start = System.nanoTime();
            bubbleSort(array);
            times[0] = System.nanoTime() - start;
            
            // Selection Sort
            array = originalArray.clone();
            start = System.nanoTime();
            selectionSort(array);
            times[1] = System.nanoTime() - start;
            
            // Insertion Sort
            array = originalArray.clone();
            start = System.nanoTime();
            insertionSort(array);
            times[2] = System.nanoTime() - start;
            
            // Quick Sort
            array = originalArray.clone();
            start = System.nanoTime();
            quickSort(array);
            times[3] = System.nanoTime() - start;
            
            // Merge Sort
            array = originalArray.clone();
            start = System.nanoTime();
            mergeSort(array);
            times[4] = System.nanoTime() - start;
            
            // Heap Sort
            array = originalArray.clone();
            start = System.nanoTime();
            heapSort(array);
            times[5] = System.nanoTime() - start;
            
            // Ergebnisse ausgeben
            System.out.printf("%d", size);
            for (long time : times) {
                System.out.printf("\t%d", time);
            }
            System.out.println();
        }
    }
    
    // Stabilitätstest
    public static void testStability() {
        System.out.println("\n=== Stabilitätstest ===");
        
        // Array mit Duplikaten
        int[] array = {5, 2, 8, 5, 1, 9, 3, 5};
        
        System.out.println("Original: " + Arrays.toString(array));
        
        // Bubble Sort (stabil)
        int[] bubbleArray = array.clone();
        bubbleSort(bubbleArray);
        System.out.println("Bubble Sort: " + Arrays.toString(bubbleArray));
        
        // Quick Sort (nicht stabil)
        int[] quickArray = array.clone();
        quickSort(quickArray);
        System.out.println("Quick Sort: " + Arrays.toString(quickArray));
        
        // Merge Sort (stabil)
        int[] mergeArray = array.clone();
        mergeSort(mergeArray);
        System.out.println("Merge Sort: " + Arrays.toString(mergeArray));
    }
    
    public static void main(String[] args) {
        // Test-Array
        int[] array = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42};
        
        System.out.println("=== Sortieralgorithmen Demo ===");
        
        // Bubble Sort
        int[] bubbleArray = array.clone();
        bubbleSort(bubbleArray);
        System.out.println("Bubble Sort: " + Arrays.toString(bubbleArray));
        
        // Selection Sort
        int[] selectionArray = array.clone();
        selectionSort(selectionArray);
        System.out.println("Selection Sort: " + Arrays.toString(selectionArray));
        
        // Insertion Sort
        int[] insertionArray = array.clone();
        insertionSort(insertionArray);
        System.out.println("Insertion Sort: " + Arrays.toString(insertionArray));
        
        // Quick Sort
        int[] quickArray = array.clone();
        quickSort(quickArray);
        System.out.println("Quick Sort: " + Arrays.toString(quickArray));
        
        // Merge Sort
        int[] mergeArray = array.clone();
        mergeSort(mergeArray);
        System.out.println("Merge Sort: " + Arrays.toString(mergeArray));
        
        // Heap Sort
        int[] heapArray = array.clone();
        heapSort(heapArray);
        System.out.println("Heap Sort: " + Arrays.toString(heapArray));
        
        // Performance-Vergleich
        compareSortingAlgorithms();
        
        // Stabilitätstest
        testStability();
        
        // Sortieralgorithmen-Eigenschaften
        printSortingAlgorithmProperties();
    }
    
    private static void printSortingAlgorithmProperties() {
        System.out.println("\n=== Sortieralgorithmen Eigenschaften ===");
        
        String[][] algorithms = {
            {"Bubble Sort", "O(n²)", "In-place", "Stabil", "Einfach"},
            {"Selection Sort", "O(n²)", "In-place", "Nicht stabil", "Einfach"},
            {"Insertion Sort", "O(n²)", "In-place", "Stabil", "Kleine Arrays"},
            {"Quick Sort", "O(n log n)", "In-place", "Nicht stabil", "Schnell"},
            {"Merge Sort", "O(n log n)", "Out-of-place", "Stabil", "Zuverlässig"},
            {"Heap Sort", "O(n log n)", "In-place", "Nicht stabil", "Garantiert"}
        };
        
        System.out.println("Algorithmus\t\tZeitkomplexität\tPlatz\t\tStabilität\tBeschreibung");
        System.out.println("---------\t\t--------------\t-----\t\t---------\t-----------");
        
        for (String[] algo : algorithms) {
            System.out.printf("%-20s\t%-15s\t%-15s\t%-15s\t%s%n", algo[0], algo[1], algo[2], algo[3], algo[4]);
        }
    }
}

Big-O-Notation Übersicht

KomplexitätBeschreibungBeispielWachstum
O(1)Konstante ZeitArray-Zugriff1
O(log n)LogarithmischBinäre Suchelog₂(n)
O(n)LinearLineare Suchen
O(n log n)LinearithmischMerge Sortn·log(n)
O(n²)QuadratischBubble Sort
O(2ⁿ)ExponentiellRekursive Fibonacci2ⁿ

Suchalgorithmen Vergleich

AlgorithmusZeitkomplexitätVoraussetzungBest Use Case
Linear SearchO(n)KeineKleine, unsortierte Arrays
Binary SearchO(log n)SortiertGroße, sortierte Arrays
InterpolationO(log log n)Sortiert, gleichverteiltNumerische Daten
Jump SearchO(√n)SortiertGroße Arrays mit Sprunggröße
ExponentialO(log n)SortiertUnendliche Arrays

Sortieralgorithmen Vergleich

AlgorithmusZeitkomplexitätPlatzkomplexitätStabilIn-place
Bubble SortO(n²)O(1)JaJa
Selection SortO(n²)O(1)NeinJa
Insertion SortO(n²)O(1)JaJa
Quick SortO(n log n)O(log n)NeinJa
Merge SortO(n log n)O(n)JaNein
Heap SortO(n log n)O(1)NeinJa

Algorithmus-Design-Prinzipien

Divide and Conquer

  1. Divide: Problem in kleinere Teilprobleme zerlegen
  2. Conquer: Teilprobleme rekursiv lösen
  3. Combine: Lösungen kombinieren

Beispiele: Quick Sort, Merge Sort, Binary Search

Greedy Algorithms

  1. Lokal optimale Entscheidungen treffen
  2. Hoffnung auf globale Optimalität

Beispiele: Dijkstra, Kruskal, Huffman Coding

Dynamic Programming

  1. Optimalstruktur: Überlappende Teilprobleme
  2. Memoization: Zwischenergebnisse speichern

Beispiele: Fibonacci, Knapsack Problem

Performance-Optimierung

Raum-Zeit-Tradeoff

// Speicherplatz für Geschwindigkeit tauschen
public class FibonacciMemoization {
    private static Map<Integer, Long> memo = new HashMap<>();
    
    public static long fibonacci(int n) {
        if (n <= 1) return n;
        
        if (memo.containsKey(n)) {
            return memo.get(n);
        }
        
        long result = fibonacci(n - 1) + fibonacci(n - 2);
        memo.put(n, result);
        return result;
    }
}

Early Termination

// Optimierte lineare Suche mit Sentinel
public static int optimizedLinearSearch(int[] array, int target) {
    int n = array.length;
    
    // Letztes Element als Sentinel
    if (array[n - 1] == target) {
        return n - 1;
    }
    
    // Letztes Element durch Target ersetzen
    int last = array[n - 1];
    array[n - 1] = target;
    
    int i = 0;
    while (array[i] != target) {
        i++;
    }
    
    // Original-Wert wiederherstellen
    array[n - 1] = last;
    
    return i < n - 1 ? i : -1;
}

Vorteile und Nachteile

Vorteile von Algorithmen-Analyse

  • Performance-Vorhersage: Abschätzung der Laufzeit
  • Algorithmus-Wahl: Richtiges Verfahren auswählen
  • Optimierung: Flaschenhälse identifizieren
  • Skalierbarkeit: Wachstumsverhalten verstehen

Nachteile

  • Theoretische Annahmen: Ignoriert Konstanten
  • Praktische Unterschiede: Hardware-Einflüsse
  • Komplexität: Mathematische Analyse aufwändig
  • Over-Engineering: Vorzeitige Optimierung

Häufige Prüfungsfragen

  1. Was ist der Unterschied zwischen Best, Average und Worst Case? Best Case: optimaler Pfad, Average Case: erwartete Performance, Worst Case: schlechtester Pfad.

  2. Warum ist Binary Search O(log n) und nicht O(n)? Durch Halbierung des Suchbereichs bei jedem Schritt wird die Suche logarithmisch beschleunigt.

  3. Wann verwendet man Insertion Sort statt Quick Sort? Bei kleinen Arrays oder fast sortierten Daten, wo Insertion Sort O(n) erreicht.

  4. Was bedeutet In-place bei Sortieralgorithmen? Der Algorithmus sortiert ohne zusätzlichen Speicherplatz (O(1) Platz).

Wichtigste Quellen

  1. https://en.wikipedia.org/wiki/Big_O_notation
  2. https://www.geeksforgeeks.org/fundamentals-of-algorithms/
  3. https://mitpress.mit.edu/books/introduction-algorithms
Zurück zum Blog
Share:

Ähnliche Beiträge