Wszystkie zagadnienia: Algorytmy
zaawansowany merge sortdziel i zwyciężajmatura rozszerzona z informatyki

Sortowanie przez scalanie

Rekurencyjnie dzieli tablicę na połowy i scala posortowane części. Złożoność O(n log n).

01

Opis

Sortowanie przez scalanie układa liczby po kolei, dzieląc problem na mniejsze. Najpierw tniesz tablicę na pół, każdą połowę znów na pół, i tak dalej – aż zostaną same pojedyncze liczby. Pojedyncza liczba jest już „ułożona" sama w sobie.

Potem sklejasz je z powrotem. Bierzesz dwa gotowe, ułożone kawałki i łączysz je w jeden większy, też ułożony. Patrzysz na pierwszą liczbę z lewego kawałka i pierwszą z prawego, wybierasz mniejszą i dopisujesz ją do wyniku. Powtarzasz, aż przepiszesz wszystko.

Zawsze tak samo szybko. Ten sposób działa tyle samo czasu niezależnie od tego, czy liczby były wymieszane, czy prawie ułożone. Cena za to jest taka, że potrzebuje dodatkowego miejsca – drugiej tablicy na sklejane kawałki.

Na maturze: to sztandarowy przykład metody „dziel i zwyciężaj" – dużego zadania rozbitego na mniejsze takie same.

Wizualizacja

Podział – dzielimy na połowy, aż zostaną pojedyncze elementy:

5 2 8 1 6 3
5 2 81 6 3
5 281 63
528163

Scalanie – łączymy posortowane fragmenty z powrotem w większe:

2 581 63
2 5 81 3 6
1 2 3 5 6 8

Prześledź krok po kroku, jak algorytm porządkuje tablicę. Porównywane elementy są podświetlone, a te, które trafiły na swoje docelowe miejsce, zmieniają kolor na zielony.

5
2
8
1
6
3

Naciśnij „Krok", aby wykonać jedną operację, albo „Auto", aby uruchomić całą animację.

02

Pseudokod

funkcja scal(L, P):
  wynik ← pusta lista
  i ← 0
  j ← 0
  // przechodzimy obie tablice jednocześnie
  dopóki i < długość(L) oraz j < długość(P):
    jeżeli L[i] ≤ P[j]:
      dodaj L[i] do wynik
      i ← i + 1
    w przeciwnym razie:
      dodaj P[j] do wynik
      j ← j + 1
  // dopisujemy pozostałe elementy
  dopóki i < długość(L):
    dodaj L[i] do wynik
    i ← i + 1
  dopóki j < długość(P):
    dodaj P[j] do wynik
    j ← j + 1
  zwróć wynik

funkcja sortuj_przez_scalanie(T):
  jeżeli długość(T) ≤ 1:
    zwróć T
  srodek ← długość(T) div 2
  L ← sortuj_przez_scalanie(T[0..srodek - 1])
  P ← sortuj_przez_scalanie(T[srodek..koniec])
  zwróć scal(L, P)
03

Implementacja w Pythonie i C++

def scal(lewa, prawa):
    wynik = []
    i = j = 0
    while i < len(lewa) and j < len(prawa):
        if lewa[i] <= prawa[j]:
            wynik.append(lewa[i])
            i += 1
        else:
            wynik.append(prawa[j])
            j += 1
    # dopisujemy pozostałe elementy z dłuższej tablicy
    wynik.extend(lewa[i:])
    wynik.extend(prawa[j:])
    return wynik

def sortowanie_przez_scalanie(tablica):
    if len(tablica) <= 1:
        return tablica
    srodek = len(tablica) // 2
    lewa = sortowanie_przez_scalanie(tablica[:srodek])
    prawa = sortowanie_przez_scalanie(tablica[srodek:])
    return scal(lewa, prawa)

# Przykład użycia
dane = [64, 25, 12, 22, 11, 90, 3]
print(sortowanie_przez_scalanie(dane))
# Wynik: [3, 11, 12, 22, 25, 64, 90]
04

Najczęściej zadawane pytania

Czy sortowanie przez scalanie jest sortowaniem w miejscu?

Nie – w fazie scalania powstaje dodatkowa tablica, do której trafiają posortowane elementy z obu fragmentów, a dopiero potem wynik wraca do oryginalnej tablicy. Stąd złożoność pamięciowa O(n) (patrz sekcja „Złożoność").

Jak można usprawnić sortowanie przez scalanie?

Rekurencję warto przerywać wcześniej: dla bardzo krótkich fragmentów sortowanie przez wstawianie wykonuje mniej operacji niż dalsze dzielenie i scalanie. To dokładnie ta sama sztuczka, na której opiera się Timsort.

Dla jakich danych lepiej sprawdza się sortowanie przez scalanie niż szybkie?

Sortowanie przez scalanie radzi sobie równie dobrze z każdym zestawem danych – dużym, małym, dowolnie rozłożonym. Sortowanie szybkie bywa za to szybsze w praktyce na małych tablicach, głównie dzięki mniejszym stałym i lepszemu wykorzystaniu pamięci podręcznej procesora.

Czy sortowanie przez scalanie jest adaptacyjne?

Nie – ma dokładnie taką samą złożoność w najlepszym, średnim i najgorszym przypadku, niezależnie od tego, jak ułożone są dane wejściowe. To odróżnia je od sortowania przez wstawianie, które na prawie posortowanych danych działa niemal liniowo.

Kiedy sortowanie przez scalanie wykonuje najwięcej porównań?

Złożoność O(n log n) jest gwarantowana zawsze, ale sama liczba porównań w fazie scalania bywa różna. Jest największa, gdy elementy obu scalanych fragmentów przeplatają się na zmianę w finalnej kolejności (np. scalanie [1, 3] i [2, 4]) – wtedy trzeba porównać niemal każdą parę, zanim wybierze się mniejszy element.

05

Ćwiczenie

Policz wartości dla sortowania przez scalanie i wpisz wynik, potem kliknij Sprawdź.

Liczba poziomów podziału dla tablicy o 8 elementach to , a dla 4 elementów to . Scalając dwie posortowane połówki o długościach 4 i 4 wykonasz co najwyżej porównań.

Uzupełnij tabelę (liczba poziomów podziału to log₂ z liczby elementów):

Liczba elementówPoziomy podziału
2
16
32

Sortowanie przez scalanie jest stabilne, więc odpowiedz . Jego złożoność pamięciowa to .

Złożoność obliczeniowa

NajgorszyO(n log n)
NajlepszyO(n log n)
PamięciowaO(n)

Czas O(n log n) – zawsze. Dzielenie tworzy log n poziomów rekurencji (za każdym razem połowa), a na każdym poziomie scalanie przegląda wszystkie n elementów. Daje to log n poziomów × O(n) pracy = O(n log n). Co ważne, ten rząd jest gwarantowany niezależnie od danych – nie ma złego przypadku, jak w sortowaniu szybkim.

Pamięć O(n). Przy scalaniu tworzymy tablice pomocnicze na wyniki – to koszt O(n). Rekurencja odkłada dodatkowo po jednej ramce na każdy poziom podziału, czyli O(log n) na stosie. Ponieważ w Big-O liczy się składnik dominujący, a n rośnie szybciej niż log n, całość zapisujemy jako O(n). Ta dodatkowa pamięć to główna wada w porównaniu z algorytmami sortującymi w miejscu.

Przećwicz w zadaniach maturalnych

Do tego hasła nie mamy jeszcze przypisanych pojedynczych zadań. Cały spis zadań maturalnych z tego działu jest tutaj:

Zadania z działu: Programowanie

↗

Powiązane zagadnienia