Benchmark algorytmów sortowania w języku Java.

1. Cel zajęć i wstęp

Celem zadania jest:

  1. Zastosowanie polimorfizmu i interfejsów w języku Java do budowy elastycznego modułu testującego.
  2. Praktyczne zmierzenie czasu wykonania algorytmów sortowania o różnej złożoności obliczeniowej.
  3. Samodzielna implementacja algorytmu Quick Sort na podstawie formalnego pseudokodu.
  4. Modelowanie przepływu sterowania za pomocą schematu blokowego.
  5. Formułowanie wniosków w oparciu o teorię złożoności obliczeniowej $ O $.

Pomiar czasu w języku Java:

W inżynierii oprogramowania do precyzyjnego profilowania kodu stosuje się licznik nanosekundowy procesora:

long start = System.nanoTime();
// ... mierzony algorytm ...
long stop = System.nanoTime();

long durationInNano = stop - start;
double durationInMillis = durationInNano / 1_000_000.0;

Ważna uwaga metodologiczna:

Każdy algorytm musi sortować tę samą tablicę nieposortowaną. Przekazanie do kolejnego sortera tablicy już posortowanej fałszuje pomiary. Dlatego przed wywołaniem metody sort() należy utworzyć kopię danych za pomocą metody array.clone().

2. Materiały startowe

Jako bazę projektową otrzymujesz gotowy interfejs ISorter, klasę generatora danych ArrayGenerator oraz szkielet klasy pomiarowej Benchmark.

Interfejs ISorter.java

package ...;

public interface ISorter {
    void sort(int[] array);
    String getName();
}

Klasa ArrayGenerator.java

package ...;

import java.util.Random;

public class ArrayGenerator {
    /**
     * Generuje tablicę o podanym rozmiarze z losowymi wartościami z przedziału [min, max].
     */
    public static int[] generateRandom(int size, int min, int max) {
        Random random = new Random();
        int[] array = new int[size];
        int bound = (max - min) + 1;

        for (int i = 0; i < size; i++) {
            array[i] = random.nextInt(bound) + min;
        }

        return array;
    }
}

Szkielet klasy Benchmark.java

package ...;

public class Benchmark {

    public static void main(String[] args) {
        int[] testSizes = { 10_000, 50_000 };

        // Tablica polimorficzna badanych algorytmów
        ISorter[] sorters = {
            new BubbleSorter(),
            new InsertionSorter(),
            new SelectionSorter()
            // Po wykonaniu Zadania 3 odkomentuj:
            // new QuickSorter()
        };

        for (int size : testSizes) {
            System.out.println("============================================================");
            System.out.printf("ROZPOCZĘCIE TESTÓW DLA ROZMIARU: %d ELEMENTÓW%n", size);
            System.out.println("============================================================");

            int[] baseArray = ArrayGenerator.generateRandom(size, 0, 100_000);

            for (ISorter sorter : sorters) {
                // Klonujemy tablicę, aby każdy algorytm sortował identyczny zestaw danych
                int[] arrayCopy = baseArray.clone();

                long startTime = System.nanoTime();
                sorter.sort(arrayCopy);
                long endTime = System.nanoTime();

                double durationMs = (endTime - startTime) / 1_000_000.0;
                boolean isSorted = isSortedAscending(arrayCopy);

                System.out.printf("Algorytm: %-18s | Czas: %8.2f ms | Posortowano poprawnie: %b%n",
                        sorter.getName(), durationMs, isSorted);
            }
            System.out.println();
        }
    }

    private static boolean isSortedAscending(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            if (array[i] > array[i + 1]) {
                return false;
            }
        }
        return true;
    }
}

3. Treść zadań do wykonania (łącznie: 100 pkt)

Zadanie 1: Implementacja klas bazowych sorterów (35 pkt)

Utwórz trzy klasy implementujące interfejs ISorter:

  1. BubbleSorter - sortowanie bąbelkowe.
  2. InsertionSorter - sortowanie przez wstawianie.
  3. SelectionSorter - sortowanie przez wybieranie.

Każda klasa musi:

(Szczegółowe pseudokody i listy kroków tych algorytmów znajdziesz w Załączniku A do niniejszej karty).

Zadanie 2: Konfiguracja i uruchomienie modułu Benchmark (10 pkt)

  1. Zintegruj zaimplementowane klasy BubbleSorter, InsertionSorter i SelectionSorter ze szkieletem Benchmark.java.
  2. Uruchom program dla obu rozmiarów prób badawczych:
    • N1=10 000 elementów
    • N2=50 000 elementów
  3. Upewnij się, że każdy algorytm uzyskuje status: Posortowano poprawnie: true.

Zadanie 3: Tłumacz z pseudokodu - Implementacja QuickSorter (30 pkt)

Na podstawie poniższego formalnego pseudokodu algorytmu Quick Sort (wariant Lomuto) utwórz klasę QuickSorter implementującą interfejs ISorter.

Pseudokod algorytmu Quick Sort:

ALGORYTM QuickSort(A, low, high):
    JEŻELI low < high TO:
        pivotIndex = Partition(A, low, high)
        QuickSort(A, low, pivotIndex - 1)
        QuickSort(A, pivotIndex + 1, high)

ALGORYTM Partition(A, low, high):
    pivot = A[high]                 // element osiowy (ostatni element)
    i = low - 1                     // wskaźnik elementów mniejszych od pivota
    
    DLA j = low DO high - 1 WYKONAJ:
        JEŻELI A[j] <= pivot TO:
            i = i + 1
            Zamień(A[i], A[j])
            
    Zamień(A[i + 1], A[high])        // umieszczenie pivota na właściwym miejscu
    ZWRÓĆ i + 1                     // zwrócenie pozycji podziału

Wymagania implementacyjne:

  1. Metoda interfejsu public void sort(int[] array) wywołuje prywatną metodę rekurencyjną:
    quickSort(array, 0, array.length - 1);
     
  2. Metody quickSort oraz partition powinny mieć modyfikator dostępu private.
  3. Dołącz new QuickSorter() do tablicy sorters w klasie Benchmark i uruchom pełne testy porównawcze.

Zadanie 4: Schemat blokowy logiki pomiarowej (15 pkt)

Za pomocą programu graficznego (np. draw.io) sporządź znormalizowany schemat blokowy prezentujący przebieg pętli pomiarowej programu Benchmark dla pojedynczego rozmiaru tablicy.

Schemat musi zawierać:

Zadanie 5: Wnioski i złożoność obliczeniowa (10 pkt)

Na końcu pliku Benchmark.java (w komentarzu wieloliniowym /* ... */) wpisz uzyskane przez Ciebie wyniki pomiarów dla N=50 000 elementów oraz sformułuj odpowiedzi na poniższe pytania:

  1. Porównanie empiryczne: Który algorytm okazał się najszybszy dla 50 000 elementów i jaka była różnica rzędu wielkości w stosunku do najwolniejszego?

ZAŁĄCZNIK A: Specyfikacja algorytmów bazowych

1. Bubble Sort (Sortowanie bąbelkowe)

2. Selection Sort (Sortowanie przez wybieranie)

3. Insertion Sort (Sortowanie przez wstawianie)