Przejdź do głównej treści
Grafika przedstawia ukryty obrazek

Algorytmy sortowania danych – przegląd i porównanie wydajności

Grafika SVG CV

Sortowanie jest jednym z podstawowych problemów w informatyce. Polega na uporządkowaniu elementów zbioru według określonego kryterium (np. rosnąco lub malejąco). Wybór odpowiedniego algorytmu sortowania ma duże znaczenie, zwłaszcza w przypadku pracy z dużymi zbiorami danych. W artykule przedstawiono najważniejsze algorytmy sortowania, ich działanie oraz porównanie złożoności obliczeniowej.

1. Algorytmy proste

1.1 Sortowanie bąbelkowe (Bubble Sort)

  • Idea: porównywanie sąsiednich elementów i zamiana ich miejscami, aż do uzyskania uporządkowanej listy.
  • Złożoność:
    • Czasowa:
      • najlepszy przypadek: O(n) (dla już posortowanej tablicy),
      • średni i najgorszy przypadek: O(n²).
    • Pamięciowa: O(1).
  • Zalety: prostota implementacji.
  • Wady: bardzo niska wydajność przy większych zbiorach danych.
function bubbleSort(arr) {
  const a = [...arr];
  for (let i = 0; i < a.length; i++) {
    for (let j = 0; j < a.length - i - 1; j++) {
      if (a[j] > a[j+1]) {
        [a[j], a[j+1]] = [a[j+1], a[j]];
      }
    }
  }
  return a;
}

1.2 Sortowanie przez wstawianie (Insertion Sort)

  • Idea: elementy są wstawiane kolejno na odpowiednie miejsce w już częściowo posortowanej tablicy.
  • Złożoność:
    • najlepszy przypadek: O(n),
    • średni i najgorszy przypadek: O(n²).
  • Zalety: dobre działanie dla małych lub częściowo posortowanych zbiorów.
  • Wady: nieefektywne dla dużych zbiorów.
function insertionSort(arr) {
  const a = [...arr];
  for (let i = 1; i < a.length; i++) {
    let key = a[i];
    let j = i - 1;
    while (j >= 0 && a[j] > key) {
      a[j+1] = a[j];
      j--;
    }
    a[j+1] = key;
  }
  return a;
}

1.3 Sortowanie przez wybieranie (Selection Sort)

  • Idea: w każdej iteracji wybierany jest najmniejszy element i przenoszony na początek.
  • Złożoność:
    • zawsze: O(n²).
  • Zalety: prostota implementacji, niewielka liczba zamian elementów.
  • Wady: duża liczba porównań, mała wydajność.
function selectionSort(arr) {
  const a = [...arr];
  for (let i = 0; i < a.length; i++) {
    let minIdx = i;
    for (let j = i + 1; j < a.length; j++) {
      if (a[j] < a[minIdx]) minIdx = j;
    }
    if (minIdx !== i) {
      [a[i], a[minIdx]] = [a[minIdx], a[i]];
    }
  }
  return a;
}

1.4 Comb Sort

  • Idea: Rozwinięcie Bubble Sort. Zamiast porównywać sąsiednie elementy, porównuje elementy w pewnym odstępie („gap”), który maleje z każdą iteracją (najczęściej przez współczynnik ok. 1.3). Usuwa tzw. „żółwie” — małe wartości przesuwające się bardzo wolno na początek.
  • Złożoność:
    • Średnia: około O(n² / 2ᵖ), gdzie p to liczba iteracji (zwykle lepsza niż Bubble),
    • Najgorszy przypadek: O(n²),
    • Najlepszy przypadek: O(n log n) (dla bardzo dobrze dobranego współczynnika).
  • Wady: Nadal kwadratowa złożoność dla dużych danych. Nie jest stabilny. Wymaga dobrania optymalnego współczynnika „shrink”.
function combSort(arr) {  
  const a = [...arr];
  const shrink = 1.3;
  let gap = a.length;
  let swapped = true;

  while (gap > 1 || swapped) {
    gap = Math.floor(gap / shrink);
    if (gap < 1) gap = 1;

    swapped = false;
    for (let i = 0; i + gap < a.length; i++) {      
      if (a[i] > a[i + gap]) {
        [a[i], a[i + gap]] = [a[i + gap], a[i]];
        swapped = true;        
      }
    }
  }

  return a;
}

1.5 Shell Sort

  • Idea: Ulepszony Insertion Sort — zaczyna od porównywania elementów oddzielonych dużym odstępem (tzw. gap), który w każdej iteracji maleje. Dzięki temu szybko przesuwa elementy blisko właściwego miejsca.
  • Złożoność:
    • Średnia: od O(n log n) do O(n^1.5) w zależności od przyjętej sekwencji odstępów,
    • Najgorszy przypadek: O(n²), *Najlepszy przypadek (już prawie posortowana tablica): O(n log n).
  • Wady: Złożoność trudna do dokładnego określenia (zależna od sekwencji odstępów). Nie jest algorytmem stabilnym (elementy równe mogą zmienić kolejność).
function shellSort(arr) {  
  const a = [...arr];
  let gap = Math.floor(a.length / 2);

  while (gap > 0) {
    for (let i = gap; i < a.length; i++) {
      const temp = a[i];
      let j = i;

      while (j >= gap && a[j - gap] > temp) {
        a[j] = a[j - gap];        
        j -= gap;
      }

      a[j] = temp;      
    }
    gap = Math.floor(gap / 2);
  }

  return a;
}

1.6 Bogo Sort

  • Idea: Najbardziej nieefektywny (i żartobliwy) algorytm sortowania. Polega na losowym tasowaniu elementów tablicy aż do momentu, gdy tablica przypadkowo stanie się posortowana.
  • Złożoność: Średnio i w najgorszym przypadku ma złożoność O(n!), ponieważ w najgorszym razie trzeba sprawdzić wszystkie możliwe permutacje.
  • Wady: Ekstremalnie wolny i nieprzewidywalny.

W praktyce bezużyteczny (stosowany tylko edukacyjnie lub humorystycznie). Może nie zakończyć się w rozsądnym czasie dla nawet małych tablic (np. n > 6).

function bogoSort(arr) {  
  const a = [...arr];

  function isSorted(a) {
    for (let i = 1; i < a.length; i++) {
      if (a[i - 1] > a[i]) return false;
    }
    return true;
  }

  function shuffle(a) {
    for (let i = a.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [a[i], a[j]] = [a[j], a[i]];
    }
  }

  let safety = 10000; // zabezpieczenie przed nieskończoną pętlą

  while (!isSorted(a) && safety-- > 0) {
    shuffle(a);
  }

  return a;
}

2. Algorytmy zaawansowane

2.1 Sortowanie szybkie (Quick Sort)

  • Idea: wybór elementu (pivot), podział tablicy na dwie części i rekurencyjne sortowanie podtablic.
  • Złożoność:
    • najlepszy i średni przypadek: O(n log n),
    • najgorszy przypadek: O(n²) (dla źle dobranego pivota).
  • Pamięciowa: O(log n) (rekurencja).
  • Zalety: bardzo szybki w praktyce.
  • Wady: wrażliwy na wybór pivota.
function quickSort(arr) {
  const a = [...arr];
  quickRec(a, 0, a.length - 1);
  return a;

  function quickRec(a, lo, hi) {
    if (lo < hi) {
      const p = partition(a, lo, hi);
      quickRec(a, lo, p - 1);
      quickRec(a, p + 1, hi);
    }
  }

  function partition(a, lo, hi) {
    const pivot = a[hi];
    let i = lo;
    for (let j = lo; j < hi; j++) {
      if (a[j] < pivot) {
        [a[i], a[j]] = [a[j], a[i]];
        i++;
      }
    }
    [a[i], a[hi]] = [a[hi], a[i]];
    return i;
  }
}

2.2 Sortowanie przez scalanie (Merge Sort)

  • Idea: dziel i zwyciężaj – dzielenie tablicy na mniejsze części i scalanie posortowanych fragmentów.
  • Złożoność:
    • zawsze: O(n log n).
  • Pamięciowa: O(n) (potrzebne dodatkowe tablice).
  • Zalety: stabilność, przewidywalny czas działania.
  • Wady: wymaga dodatkowej pamięci.
function mergeSort(arr) {
  const a = [...arr];
  mergeRec(a, 0, a.length - 1);
  return a;

  function mergeRec(a, l, r) {
    if (l >= r) return;
    const m = Math.floor((l + r) / 2);
    mergeRec(a, l, m);
    mergeRec(a, m+1, r);
    merge(a, l, m, r);
  }

  function merge(a, l, m, r) {
    const left = a.slice(l, m+1);
    const right = a.slice(m+1, r+1);
    let i = 0, j = 0, k = l;
    while (i < left.length && j < right.length) {
      if (left[i] <= right[j]) {
        a[k++] = left[i++];
      } else {
        a[k++] = right[j++];
      }
    }
    while (i < left.length) { a[k++] = left[i++]; }
    while (j < right.length) { a[k++] = right[j++]; }
  }
}

2.3 Sortowanie przez kopcowanie (Heap Sort)

  • Idea: budowa kopca i wyciąganie największego elementu, aż do uzyskania posortowanej listy.
  • Złożoność:
    • zawsze: O(n log n).
  • Pamięciowa: O(1).
  • Zalety: przewidywalność, brak dodatkowej pamięci.
  • Wady: wolniejszy od Quick Sorta w praktyce.
function heapSort(arr) {
  const a = [...arr];
  const n = a.length;

  function heapify(n, i) {
    let largest = i;
    const l = 2*i + 1;
    const r = 2*i + 2;
    if (l < n) { if (a[l] > a[largest]) largest = l; }
    if (r < n) { if (a[r] > a[largest]) largest = r; }
    if (largest !== i) {
      [a[i], a[largest]] = [a[largest], a[i]];
      heapify(n, largest);
    }
  }

  // build heap
  for (let i = Math.floor(n/2) - 1; i >= 0; i--) heapify(n, i);
  // extract elements
  for (let i = n - 1; i > 0; i--) {
    [a[0], a[i]] = [a[i], a[0]];
    heapify(i, 0);
  }
  return a;
}

3. Algorytmy specjalne

3.1 Sortowanie przez zliczanie (Counting Sort)

  • Idea: Zlicza, ile razy występuje każda wartość w tablicy, a następnie na tej podstawie odtwarza posortowany wynik. Działa tylko dla liczb całkowitych z ograniczonego zakresu.
  • Złożoność:
    • Czasowa: O(n + k), gdzie k to zakres wartości (max - min + 1),
    • Przestrzenna: O(k) (dodatkowa tablica zliczeń).
  • Wady: Nie działa na liczbach zmiennoprzecinkowych ani ciągach znaków bez modyfikacji. Nieskuteczny, gdy zakres wartości (k) jest dużo większy od liczby elementów (n).
function countingSort(arr) {  
  const a = [...arr];

  const min = Math.min(...a);
  const max = Math.max(...a);
  const count = Array(max - min + 1).fill(0);

  for (let i = 0; i < a.length; i++) {
    count[a[i] - min]++;    
  }

  let index = 0;
  for (let i = 0; i < count.length; i++) {
    while (count[i]-- > 0) {
      a[index++] = i + min;      
    }
  }

  return a;
}

3.2 Sortowanie kubełkowe (Bucket Sort)

  • Idea: rozdzielanie elementów do kubełków (przedziałów), a następnie sortowanie w każdym kubełku i scalanie wyników.
  • Złożoność: O(n + k), gdzie k to liczba kubełków.
  • Wady: zależne od rozkładu danych.
function bucketSort(arr) {
  if (arr.length === 0) return [];

  // Tworzymy puste kubełki
  const numBuckets = Math.floor(Math.sqrt(arr.length)) || 1;
  const buckets = Array.from({ length: numBuckets }, () => []);

  // Znajdujemy minimalną i maksymalną wartość, by rozłożyć elementy proporcjonalnie
  const minValue = Math.min(...arr);
  const maxValue = Math.max(...arr);
  const range = maxValue - minValue || 1;

  // Rozdzielamy elementy do kubełków
  for (let num of arr) {
    const index = Math.floor(((num - minValue) / range) * (numBuckets - 1));
    buckets[index].push(num);
  }

  // Sortujemy każdy kubełek i łączymy w jedną tablicę
  return buckets.flatMap(bucket => bucket.sort((a, b) => a - b));
}

3.3 Sortowanie pozycyjne (Radix Sort)

  • Idea: sortowanie według kolejnych cyfr/liczb znaczących.
  • Złożoność: O(nk), gdzie k to liczba cyfr.
  • Zalety: bardzo szybkie dla liczb całkowitych.
  • Wady: ograniczone zastosowanie (nie działa bezpośrednio na wszystkich typach danych).
function radixSort(arr) {
  if (arr.length === 0) return [];

  const maxNum = Math.max(...arr);
  let divisor = 1;
  let result = [...arr];

  // Pętla przez kolejne miejsca dziesiętne
  while (Math.floor(maxNum / divisor) > 0) {
    const buckets = Array.from({ length: 10 }, () => []);

    for (let num of result) {
      const digit = Math.floor((num / divisor) % 10);
      buckets[digit].push(num);
    }

    result = buckets.flat();
    divisor *= 10;
  }

  return result;
}

4. Porównanie algorytmów sortowania

Algorytm Najlepszy przypadek Średni przypadek Najgorszy przypadek Pamięć dodatkowa
Bubble Sort O(n) O(n²) O(n²) O(1)
Insertion Sort O(n) O(n²) O(n²) O(1)
Selection Sort O(n²) O(n²) O(n²) O(1)
Comb Sort O(n log n) O(n² / 2ᵖ) O(n²) O(1)
Shell Sort O(n log n) O(n^1.5) O(n²) O(1)
Bogo Sort O(n) (teoretycznie) O(n!) O(n!) O(1)
Quick Sort O(n log n) O(n log n) O(n²) O(log n)
Merge Sort O(n log n) O(n log n) O(n log n) O(n)
Heap Sort O(n log n) O(n log n) O(n log n) O(1)
Counting Sort O(n + k) O(n + k) O(n + k) O(k)
Bucket Sort O(n + k) O(n + k) O(n²) O(n + k)
Radix Sort O(nk) O(nk) O(nk) O(n + k)

Porównanie złożoności obliczeniowej

Wizualizacja teoretycznych złożoności: O(n), O(n log n), O(n²). Możesz przełączać konkretne algorytmy.

Włącz/wyłącz algorytmy

Algorytmy zaawansowane

Legenda i wskazówki

Tutaj wykres przedstawia krzywe znormalizowane (domyślnie). Możesz zmienić Maksymalny rozmiar n, przełączyć skalę osi Y na logarytmiczną, lub wyłączyć normalizację, by zobaczyć surowe kształty krzywych.

  • O(n²) — algorytmy proste (bardzo strome dla dużych n).
  • O(n log n) — algorytmy efektywne dostępne w standardowych bibliotekach.
  • Pobierz wykres przyciskiem, aby dołączyć go do dokumentacji.

Wizualizacja algorytmów sortowania

Kolor czerwony oznacza aktualnie porównywane / zamieniane elementy.

5. Playground - wypróbuj kod!

Wpisz JavaScript i sprawdź wynik bezpiecznie w odizolowanym środowisku.

Uwaga

W tym edytorze console.log() wypisuje wynik w okienku poniżej. Możesz też użyć print() – działa identycznie. W funkcjach asynchronicznych np. setTimeout() używaj print() ponieważ console.log() nie zadziała w okienku poniżej.

Wynik:

6. Dla jakich danych najlepszy jest dany algorytm sortowania

Algorytm Najlepiej działa dla... Uzasadnienie / Charakterystyka
Bubble Sort Małe lub prawie posortowane zbiory Bardzo prosty, ale powolny dla dużych danych; stabilny.
Insertion Sort Małe lub prawie posortowane tablice Działa bardzo szybko przy niewielkich przesunięciach; stabilny.
Selection Sort Małe tablice, gdzie stabilność nie jest ważna Prosty i przewidywalny, ale zawsze O(n²); mało zamian.
Comb Sort Średniej wielkości dane, losowe wartości Szybszy od Bubble; dobrze radzi sobie z danymi bez wzorca.
Shell Sort Średniej wielkości tablice, dowolne dane Działa dobrze przy różnym rozkładzie; prosty i szybki bez dodatkowej pamięci.
Bogo Sort Tylko cele edukacyjne lub humorystyczne Bezużyteczny praktycznie – losuje aż trafi porządek.
Quick Sort Duże zbiory losowych danych Bardzo szybki w praktyce; mała pamięć dodatkowa; niestabilny.
Merge Sort Duże dane wymagające stabilności Świetny dla dużych tablic lub danych na dysku; stabilny, ale wymaga O(n) pamięci.
Heap Sort Duże tablice, gdy ważna jest mała pamięć Bardzo przewidywalny, dobry kompromis między szybkością a zużyciem pamięci; niestabilny.
Counting Sort Dane całkowite w małym, znanym zakresie Bardzo szybki (O(n+k)), nieporównawczy, ale wymaga ograniczonego zakresu.
Bucket Sort Dane rzeczywiste równomiernie rozłożone Bardzo wydajny dla danych np. z [0,1); wymaga odpowiedniego rozkładu.
Radix Sort Duże zbiory liczb całkowitych o podobnej długości Bardzo szybki, stabilny, nieporównawczy; świetny dla np. ID, kodów, ZIPów.

7. Szybkie podsumowanie typu zastosowań

Typ danych / sytuacja Najlepsze algorytmy Dlaczego
Małe tablice (n < 50) Insertion, Bubble, Selection Proste i intuicyjne; brak dużej różnicy czasowej.
Prawie posortowane dane Insertion, Bubble, Shell Minimalna liczba porównań i przesunięć.
Losowe duże dane Quick, Merge, Heap O(n log n) — najbardziej wydajne dla ogólnych przypadków.
Duże dane, gdy stabilność jest ważna Merge, Radix, Counting Zachowują kolejność elementów równych.
Liczby całkowite z małego zakresu Counting, Radix Nieporównawcze — liniowy czas.
Liczby rzeczywiste w zakresie [0,1) Bucket Równomierny rozkład = niemal liniowy czas.
Dane o nieznanym rozkładzie Quick, Heap, Shell Dobre wyniki bez wcześniejszej wiedzy o danych.
Cele dydaktyczne / żart Bogo Pokazuje, jak NIE sortować danych

8. Szybkie wnioski

  • Najbardziej uniwersalne: Quick Sort, Merge Sort, Heap Sort
  • Najlepsze dla małych danych: Insertion Sort
  • Najlepsze dla danych numerycznych z ograniczonym zakresem: Counting / Radix / Bucket
  • Najgorszy w praktyce: Bogo Sort (czysto pokazowy)
18 września 2025 106

Kategorie

programowanie

Tagi

algorytmy

Dziękujemy!
()
Wymiana doświadczeń

Masz podobne doświadczenia?

Chętnie poznam Twoją perspektywę i porozmawiam o tym temacie szerzej.

Napisz do mnie

Każda perspektywa może wnieść coś wartościowego do dyskusji.

Twoja prywatność i pliki cookies

  1. Ta strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania – m.in. do poprawnego wyświetlania treści, zapamiętania podstawowych ustawień przeglądarki oraz zapewnienia stabilności serwisu.
  2. Nie stosuję plików cookies w celach marketingowych, reklamowych ani analitycznych.
  3. Strona ma charakter wyłącznie informacyjny i nie zawiera formularzy kontaktowych, rejestracyjnych ani zakupowych, przez które dane mogłyby być przesyłane na serwer.
  4. Nie zbieram danych osobowych podczas zwykłego korzystania z witryny.
  5. Serwis nie korzysta z certyfikatu SSL, jednak ze względu na informacyjny charakter strony nie jest wymagane przesyłanie poufnych danych. Zalecam jednak, aby nigdy nie wpisywać haseł ani danych osobowych na stronach bez szyfrowanego połączenia.
  6. Korzystając z tej strony, wyrażasz zgodę na używanie wyłącznie niezbędnych plików cookies.

Więcej informacji znajdziesz w mojej polityce prywatności.