Nie jesteś zalogowany
Opis: W temacie omówione zostaną zagadnienia związane z oceną efektywności algorytmów pod względem czasu działania i wykorzystania pamięci. Przedstawione zostaną podstawowe rodzaje złożoności obliczeniowej, notacja O(n) oraz metody porównywania algorytmów na podstawie ich wydajności.
Każdy algorytm potrzebuje pewnej ilości czasu na wykonanie wszystkich swoich operacji. Czas wykonania algorytmu określa, jak długo algorytm będzie działał dla danych wejściowych o określonym rozmiarze. Jest to jeden z najważniejszych sposobów oceny efektywności algorytmu.
W praktyce nie analizuje się zwykle rzeczywistego czasu podawanego w sekundach czy milisekundach. Taki wynik zależy bowiem od wielu czynników, między innymi od szybkości procesora, ilości pamięci RAM czy używanego języka programowania. Zamiast tego analizuje się liczbę operacji, które algorytm musi wykonać.
Im więcej operacji wykonuje algorytm, tym więcej czasu potrzebuje na zakończenie działania.
Największy wpływ na czas działania ma rozmiar danych wejściowych. Oznacza się go najczęściej literą n.
Jeżeli algorytm ma przetworzyć:
10 liczb, wykona niewielką liczbę operacji,
1000 liczb, wykona znacznie więcej operacji,
1 000 000 liczb, liczba operacji może być bardzo duża.
Dlatego podczas analizy sprawdza się, jak wzrasta liczba wykonywanych operacji wraz ze wzrostem wartości n.
Podczas szacowania czasu wykonania analizuje się najważniejsze instrukcje algorytmu. Mogą to być:
porównania wartości,
przypisania do zmiennych,
działania matematyczne,
odczytywanie danych,
wywołania funkcji.
Nie ma potrzeby liczenia każdej pojedynczej instrukcji. Najważniejsze jest określenie, jak zmienia się liczba operacji przy zwiększaniu rozmiaru danych.

Źródło: Jakub Piskorowski
Rozważmy algorytm obliczający sumę liczb zapisanych w tablicy.
suma = 0
Dla każdej liczby w tablicy:
suma = suma + liczba
Wypisz sumaJeżeli tablica zawiera 5 elementów, instrukcja dodawania zostanie wykonana 5 razy a ilość wykonanych operacji w całym programie wyniesie 7.
Liczba elementów (n) | Liczba operacji |
|---|---|
5 | 7 |
10 | 12 |
100 | 102 |
1000 | 1002 |
Widzimy, że liczba operacji rośnie proporcjonalnie do liczby elementów. Im większa tablica, tym dłużej działa algorytm.
Czas działania algorytmu nie zawsze jest taki sam. Często zależy od konkretnych danych wejściowych.
Wyróżnia się trzy podstawowe przypadki:
najlepszy przypadek - algorytm wykonuje najmniej operacji,
średni przypadek - typowa liczba operacji dla przeciętnych danych,
najgorszy przypadek - algorytm wykonuje najwięcej operacji.
Najczęściej analizowany jest najgorszy przypadek, ponieważ pokazuje maksymalny czas potrzebny na wykonanie algorytmu.
Dla małych zbiorów danych różnice między algorytmami często są niezauważalne. Jednak przy dużej liczbie danych mogą być ogromne. Algorytm wykonujący tysiąc operacji będzie działał znacznie szybciej niż algorytm wykonujący milion operacji, nawet jeśli oba rozwiązują ten sam problem.
Dlatego analiza czasu wykonania pozwala:
ocenić efektywność algorytmu,
porównywać różne rozwiązania tego samego problemu,
wybierać algorytmy, które poradzą sobie z dużą ilością danych.
Oprócz czasu wykonania, drugim kluczowym aspektem oceny algorytmu jest pamięciochłonność, czyli ilość pamięci operacyjnej (RAM), jaką algorytm potrzebuje do poprawnego działania.
Każdy algorytm, poza danymi wejściowymi, wykorzystuje dodatkową pamięć na przechowywanie zmiennych, struktur danych oraz informacji pomocniczych potrzebnych w trakcie obliczeń. Analiza pamięciochłonności pozwala określić, jak bardzo "zasobożerny" jest dany algorytm pod względem pamięci.
Pamięć wykorzystywana przez algorytm nie ogranicza się tylko do danych wejściowych. Można ją podzielić na kilka elementów:
dane wejściowe - np. tablica liczb, tekst, graf,
zmienne pomocnicze - np. liczniki, sumy, wskaźniki,
struktury danych tworzone w trakcie działania - np. dodatkowe tablice, stosy, kolejki,
reprezentacja wywołań funkcji - stos wywołań (call stack).
W analizie algorytmów najczęściej interesuje nas dodatkowa pamięć, czyli ta, którą algorytm zużywa ponad dane wejściowe.

Źródło: Jakub Piskorowski
Pod względem pamięciochłonności algorytmy można podzielić na dwie podstawowe grupy:
Pamięć stała - Algorytm wykorzystuje stałą ilość pamięci, niezależnie od rozmiaru danych wejściowych. Oznacza to, że niezależnie czy przetwarzamy 10 czy 10 000 elementów, ilość dodatkowej pamięci pozostaje taka sama.
Pamięć zależna od danych - Algorytm zużywa pamięć proporcjonalnie do rozmiaru danych wejściowych. Im większe dane, tym większe zapotrzebowanie na pamięć.
W praktyce programistycznej pamięć jest zasobem ograniczonym. Nawet jeśli komputer jest szybki, może nie poradzić sobie z algorytmem, który wymaga zbyt dużej ilości pamięci.
Analiza pamięciochłonności pozwala:
uniknąć przepełnienia pamięci (tzw. out of memory),
projektować algorytmy działające na dużych zbiorach danych,
porównywać rozwiązania nie tylko pod kątem szybkości, ale też zasobów.
Rozważmy algorytm sumujący elementy tablicy:
suma = 0
Dla każdego elementu w tablicy:
suma = suma + element
Wypisz sumaW tym przypadku algorytm używa:
jednej zmiennej suma,
zmiennej iteracyjnej (np. i),
danych wejściowych (tablicy).
Dodatkowa pamięć nie zależy od liczby elementów tablicy.
Wniosek: pamięciochłonność jest stała.
nowa_tablica = []
Dla każdego elementu w starej_tablicy:
dodaj element do nowa_tablicaTutaj sytuacja wygląda inaczej. Algorytm tworzy nową strukturę danych o rozmiarze zależnym od wejścia.
Jeżeli:
tablica ma 10 elementów -> nowa tablica ma 10 elementów,
tablica ma 1000 elementów -> nowa tablica ma 1000 elementów.
Wniosek: pamięciochłonność rośnie liniowo wraz z rozmiarem danych.
Tak jak w przypadku czasu wykonania, również pamięciochłonność można opisywać symbolicznie (np. O(1), O(n)), ale na tym etapie ważniejsze jest zrozumienie sensu:
O(1) - stałe zużycie pamięci,
O(n) - pamięć rośnie wraz z liczbą danych,
O(n²) - pamięć rośnie bardzo szybko przy dużych danych.
Pamięciochłonność algorytmu określa, ile dodatkowej pamięci potrzebuje algorytm do wykonania obliczeń. Jest to kluczowy parametr szczególnie w przypadku dużych zbiorów danych lub systemów o ograniczonych zasobach.
Analiza pamięci pozwala świadomie decydować, czy lepiej wybrać algorytm szybszy, czy taki, który zużywa mniej pamięci – w zależności od potrzeb konkretnego problemu.
Podczas analizy algorytmów często chcemy określić, jak zmienia się czas działania lub zużycie pamięci wraz ze wzrostem liczby danych wejściowych. Do tego celu wykorzystuje się notację O, nazywaną również notacją dużego O(ang. Big O notation).
Notacja O nie podaje dokładnego czasu działania algorytmu w sekundach. Zamiast tego opisuje tempo wzrostu liczby operacji wykonywanych przez algorytm wraz ze wzrostem rozmiaru danych wejściowych.
Dzięki temu można porównywać algorytmy niezależnie od użytego komputera czy języka programowania.
Załóżmy, że dwa programy rozwiązują ten sam problem:
pierwszy wykonuje zadanie w 0,5 sekundy na jednym komputerze,
drugi w 0,3 sekundy na innym komputerze.
Na podstawie samego czasu trudno stwierdzić, który algorytm jest lepszy. Wynik zależy od sprzętu i warunków testu.
Notacja O pozwala skupić się na tym, jak szybko rośnie liczba operacji, gdy zwiększamy ilość danych.
W notacji O symbol n oznacza rozmiar danych wejściowych.
Przykładowo:
liczba elementów w tablicy,
liczba znaków w tekście,
liczba rekordów w bazie danych.
Jeżeli algorytm przetwarza 100 liczb, to n = 100. Jeżeli przetwarza 1000 liczb, to n = 1000.
W notacji O nie interesują nas dokładne liczby operacji. Liczy się jedynie tempo wzrostu.
Przykładowo:
O(n)
O(2n)
O(5n)
są traktowane tak samo, ponieważ wszystkie rosną proporcjonalnie do wartości n.
Podobnie:
O(n² + n)
O(n² + 100)
upraszcza się do: O(n²)
ponieważ przy bardzo dużych wartościach n składnik n² ma największy wpływ na liczbę operacji.
Notacja O pozwala:
przewidywać zachowanie algorytmu dla dużych zbiorów danych,
porównywać różne rozwiązania tego samego problemu,
wybierać bardziej efektywne algorytmy,
oceniać zarówno czas działania, jak i pamięciochłonność.
Jest to uniwersalny sposób opisu efektywności algorytmów stosowany w informatyce na całym świecie.
Rozważmy dwa algorytmy wyszukiwania liczby w tablicy:
Sprawdza elementy jeden po drugim.
Dla każdego elementu:
jeśli element = szukana_liczba
zakończW najgorszym przypadku musi przejrzeć wszystkie elementy.
Złożoność: O(n)
Porównuje każdy element z każdym innym.
Dla każdego elementu:
dla każdego kolejnego elementu:
wykonaj porównaniePrzy wzroście liczby danych liczba porównań rośnie bardzo szybko.
Złożoność: O(n²)
Dla małej tablicy różnica może być niewielka, ale dla tysięcy lub milionów elementów algorytm O(n) będzie znacznie szybszy.
Złożoność obliczeniowa opisuje, jak zmienia się liczba operacji wykonywanych przez algorytm w zależności od rozmiaru danych wejściowych (n). Różne algorytmy mogą rosnąć w bardzo różnym tempie - od niemal niezmiennego, aż po bardzo szybki wzrost wymagający ogromnej liczby operacji.
Złożoność stała oznacza, że liczba operacji wykonywanych przez algorytm nie zależy od liczby danych wejściowych. Niezależnie od tego, czy przetwarzamy 10, 1000 czy 1 000 000 elementów, algorytm wykonuje zawsze podobną, stałą liczbę operacji. Typowym przykładem jest dostęp do konkretnego elementu w tablicy po indeksie.
Przykład:
element = tablica[5]Algorytm od razu pobiera wartość z konkretnej pozycji, bez przeglądania innych elementów.
Charakterystyka:
bardzo szybka i przewidywalna,
najlepsza możliwa złożoność,
wykres: linia pozioma (stała wartość).
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(1) | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |

Źródło: Jakub Piskorowski
Złożoność liniowa oznacza, że liczba operacji rośnie proporcjonalnie do liczby danych wejściowych. Jeżeli liczba elementów się podwaja, liczba operacji również się podwaja.
Przykład:
suma = 0
dla każdego elementu w tablicy:
suma = suma + elementKażdy element jest przetwarzany dokładnie jeden raz.
Charakterystyka:
bardzo częsta w praktyce,
wydajna dla dużych danych,
wykres: linia rosnąca pod stałym kątem.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(n) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

Źródło: Jakub Piskorowski
Złożoność kwadratowa oznacza, że liczba operacji rośnie proporcjonalnie do kwadratu liczby danych. W praktyce oznacza to, że każdy element jest porównywany z wieloma innymi elementami.
Przykład:
dla każdego elementu w tablicy:
dla każdego innego elementu:
porównaj elementyCharakterystyka
bardzo szybki wzrost liczby operacji,
dobra tylko dla małych zbiorów danych,
wykres: krzywa coraz bardziej stroma.
Przykład:
10 elementów -> ~100 operacji
100 elementów -> ~10 000 operacji
1000 elementów -> ~1 000 000 operacji
n | 1 | 4 | 9 | 16 | 25 | 36 | 49 | 64 | 81 | 100 | 121 | 144 | 169 | 196 | 225 | 256 | 289 | 324 | 361 | 400 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(n²) | 1 | 4 | 9 | 16 | 25 | 36 | 49 | 64 | 81 | 100 | 121 | 144 | 169 | 196 | 225 | 256 | 289 | 324 | 361 | 400 |

Źródło: Jakub Piskorowski
Złożoność logarytmiczna oznacza, że liczba operacji rośnie bardzo wolno, nawet gdy dane wejściowe są duże. Najczęściej występuje w algorytmach, które dzielą problem na mniejsze części.
Przykład - wyszukiwanie binarne:
dopóki zakres nie będzie pusty:
sprawdź środkowy element
zawęź zakres do połowyKażdy krok zmniejsza liczbę elementów o połowę.
Charakterystyka:
bardzo wydajna dla dużych danych,
typowa dla algorytmów "dziel i zwyciężaj",
wykres: bardzo wolno rosnąca krzywa, spłaszczająca się.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(log₂ n) | 0,0 | 1,0 | 1,6 | 2,0 | 2,3 | 2,6 | 2,8 | 3,0 | 3,2 | 3,3 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 | 4,0 | 4,1 | 4,2 | 4,2 | 4,3 |

Źródło: Jakub Piskorowski
Złożoność stała - O(1)
Złożoność liniowa - O(n)
Złożoność kwadratowa - O(n²)
Złożoność logarytmiczna - O(log n)

Źródło: Jakub Piskorowski
Dotychczas omówione zostały cztery podstawowe rodzaje złożoności: stała O(1), liniowa O(n), kwadratowa O(n²) oraz logarytmiczna O(log n). W praktyce informatycznej można jednak spotkać również inne klasy złożoności, opisujące algorytmy o różnym tempie wzrostu liczby operacji. Poznanie ich pozwala lepiej zrozumieć, dlaczego niektóre algorytmy działają bardzo szybko nawet dla dużych zbiorów danych, podczas gdy inne stają się niepraktyczne już przy stosunkowo niewielkich wartościach n. Poniżej przedstawiono najważniejsze dodatkowe rodzaje złożoności, które często pojawiają się podczas analizy algorytmów.
Złożoność liniowo-logarytmiczna - O(n log n)
Złożoność sześcienna - O(n³)
Złożoność wykładnicza - O(2ⁿ)
Złożoność silniowa - O(n!)
Złożoność pierwiastkowa - O(√n)
To jedna z najważniejszych klas w praktyce informatycznej.
Występuje w algorytmach, które:
dzielą dane na części (log n),
ale każdą część muszą jeszcze liniowo przetworzyć (n).
Przykłady:
sortowanie przez scalanie (Merge Sort),
szybkie sortowanie (Quick Sort - średnio).
Charakterystyka:
bardzo wydajna dla dużych danych,
"złoty standard" sortowania.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(n log₂ n) | 0,0 | 2,0 | 4,8 | 8,0 | 11,6 | 15,5 | 19,7 | 24,0 | 28,5 | 33,2 | 38,1 | 43,0 | 48,1 | 53,3 | 58,6 | 64,0 | 69,5 | 75,1 | 80,7 | 86,4 |

Źródło: Jakub Piskorowski
Liczba operacji rośnie jak sześcian liczby danych.
Przykład - trzy zagnieżdżone pętle:
dla i:
dla j:
dla k:
operacjaCharakterystyka:
bardzo wolna dla większych danych,
stosowana głównie w prostych lub macierzowych obliczeniach.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(n³) | 1 | 8 | 27 | 64 | 125 | 216 | 343 | 512 | 729 | 1000 | 1331 | 1728 | 2197 | 2744 | 3375 | 4096 | 4913 | 5832 | 6859 | 8000 |

Źródło: Jakub Piskorowski
To jedna z najbardziej kosztownych złożoności. Każdy dodatkowy element podwaja liczbę operacji.
Przykłady:
problem plecakowy (naiwne rozwiązanie),
generowanie wszystkich podzbiorów.
Charakterystyka:
bardzo szybki wzrost,
praktycznie nieużywana dla dużych n.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(2ⁿ) | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768 | 65536 | 131072 | 262144 | 524288 | 1048576 |

Źródło: Jakub Piskorowski
Jeszcze bardziej ekstremalna niż wykładnicza.
Przykład:
problem komiwojażera (brute force),
generowanie wszystkich permutacji.
Charakterystyka:
ekstremalnie wolna,
nawet dla małych n (np. 15-20) staje się niepraktyczna.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(n!) | 1 | 2 | 6 | 24 | 120 | 720 | 5 tys. | 40 tys. | 363 tys. | 3,6 mln | 40 mln | 479 mln | 6,2 mld | 87 mld | 1,3 bln | 21 bln | 356 bln | 6,4 bld | 122 bld | 2,4 tryl. |

Źródło: Jakub Piskorowski
Rzadsza, ale bardzo ciekawa.
Przykład: sprawdzanie liczb pierwszych metodą optymalizowaną (do √n).
Charakterystyka:
szybsza niż O(n),
wolniejsza niż O(log n),
często spotykana w teorii liczb.
n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
O(√n) | 1,00 | 1,41 | 1,73 | 2,00 | 2,24 | 2,45 | 2,65 | 2,83 | 3,00 | 3,16 | 3,32 | 3,46 | 3,61 | 3,74 | 3,87 | 4,00 | 4,12 | 4,24 | 4,36 | 4,47 |
© 2026 Piskorowski Jakub. All rights reserved.