Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

[C++] Algorytmy biblioteki standardowej: cheetsheet

[artykuł]

Wprowadzenie

Biblioteka standardowa C++ (potocznie, aczkolwiek niepoprawnie zwana STLem) to nie tylko kontenery, ale także algorytmy.
Algorytmy z biblioteki standardowej są tym co warto znać, będąc więcej niż początkującym programistą C++. Standardy kodowania wielu firm programistycznych zalecają używanie algorytmów zamiast "gołych pętli". Ponadto np. certyfikaty znajomości programowania, m.in. C++ Certified Professional Programmer – CPP (udzielany przez C++ Institute), kładą bardzo duży nacisk na biegłe władanie tymi algorytmami.
W dobie AI do kodowania nadal znajomość algorytmów jest pożądana, gdyż z moich obserwacji AI samo z siebie ich nie stosuje, raczej trzeba mu napisać, że znoobił i powinien użyć algorytmów, wtedy przyzna nam racje i zrobi co sugerujemy, ale wpierw musimy znać algorytmy.

Dlaczego algorytmy lepsze od pętli

Jak to powiedział Św. Jan Paweł II: "Człowiek nie jest w stanie pracować, gdy nie widzi sensu pracy, gdy sens ten przestaje być dla niego przejrzysty, gdy zostaje mu niejako przesłonięty", tak więc poniżej zestawiam argumenty za stosowaniem algorytmów:
  • Są poprawne - robiąc to samo samodzielnie jednak łatwo się pomylić
  • Poprawiają czytelność - lepiej oddają intencje w zwięzłym kodzie.
  • Są dojrzałe - były udoskonalane przez wiele standardów, obsługują naprawdę wiele przypadków i pamiętają o szczegółach
  • Są wydajne – implementacje algorytmów z biblioteki standardowej są zwykle bardzo dopracowane. Często korzystają z trików niedostępnych (lub trudno dostępnych) w zwykłym kodzie użytkownika: lepszej współpracy z optymalizatorem kompilatora oraz niskopoziomowych instrukcji procesora (intrinsics – specjalne funkcje, które mapują się bezpośrednio na instrukcje procesora, np. SIMD). Dzięki temu potrafią być szybsze od ręcznie napisanej pętli, nawet gdy wydaje się, że pętla robi dokładnie to samo.
  • Standardy kodowania zalecają/wymagają ich użycie - nie tylko ogólny standard kodowania CppCoreGuidelines, ale też standardy kodowania w wielu firmach specjalizujących się w pisaniu kodu.
  • Przyśpieszają pisanie kodu - często jest szybciej oddelegować pracę do zaimplementowanego nazwanego algorytmu niż samemu go robić.
  • Są zoptymalizowane pod przypadek ogólny.

Skąd nazwa STL i czemu jest niepoprawna

C++ na początku nie posiadał kontenerów oraz algorytmów, dopiero Alexander Stepanov, pracujący w HP Labs, ok. 1992 roku dokonał ich implementacji i to się nazywało STL (Standard Template Library), następnie Stepanov zaprezentował bibliotekę komitetowi ANSI/ISO C++, a jego prace w większości weszły do standardu C++98, od tej pory jednak w każdym kolejnym standardzie biblioteka standardowa jest poszerzana. Poprawnym określeniem jest obecnie "biblioteka standardowa" (bardziej szczegółowa argumentacja w książce "Wysoce wydajny C++").

Jak się uczyć algorytmów

Istnieje strona referencyjna zawierająca wykaz algorytmów dostępnych w bibliotece standardowej C++, ale ta lista mimo iż jest idealnym miejscem do sprawdzenia szczegółów, to jednak jest ona ciężka w nauce dla nieznających tych algorytmów. Zwłaszcza, że C++ się zmienia i pojawiają się nie tylko implementacje kolejnych algorytmów, ale też mechanizmy wpływające na prawie wszystkie algorytmy m.in. "Execution policies".

Niegdyś wspaniałym źródłem wiedzy o algorytmach jest książka STL w praktyce Scotta Meyersa - jednakże problemem jest to, że książka ta jest sprzed C++11, przez co wiele informacji się zdezaktualizowało (nawet mimo iż C++ trzyma olbrzymią kompatybilność wsteczną to jednak pewne mechanizmy wyleciały).

Rozpoczynamy naukę algorytmów

Poniżej moje przemyślenia jak się ich uczyć, oraz zestawienia tabelaryczne wraz z tym co ważne. Poniższy opis nie zastąpi szczegółowej dokumentacji na stronie CppReference, ale pozwoli ogarniać algorytmy, oraz być referencją do szybkiego sprawdzenia czy przypominania.

Warunki wstępne

Algorytmy są poniekąd zwieńczeniem biblioteki standardowej C++, wymagane jest płynne posługiwanie się C++, szablonami, wyrażeniami lambda, jak również kontenerami. Jeśli nie spełniasz tych kryteriów uzupełnij przed przejściem dalej.

Podział algorytmów

Algorytmy (w bazowej formie) dzielą się na kilka grup:
  • operacje niemodyfikujące
  • operacje modyfikujące

Powyższe można jeszcze podzielić bardziej szczegółowo np. (są to grupy wg uznania, nie standardu, można więc podzielić inaczej):

Sposoby korzystania z algorytmów w różnych standardach C++

Zasadniczo skupię się w tym artykule na najbardziej "tradycyjnej" formie korzystania z algorytmów, czyli przez podawanie zakresów, jest to przykładowo forma:
C/C++
vector v = { 1, 2, 3, 4, 5 };
auto print =[ ]( auto e ) { cout << e << endl; };
std::for_each( v.begin(), v.end(), print );

Dlaczego zakres

Algorytmy są na tyle ogólne, że są w stanie współpracować nie tylko z kontenerami, ale też z tablicami, oraz też zakresami (ang. ranges , którym poświęciłem oddzielny artykuł). Stąd będę używał ogólnego określenia w tym artykule "zakres" - bo określa to wszystko z czym algorytmy współpracują, co prawda najczęściej będę pod spodem używał kontenerów do demonstracji algorytmów.

Od C++20 istnieje wersja bazująca na zakresach (ang. ranges), w której zamiast pary iteratorów podaje się dane (np. kontener lub tablicę o statycznym rozmiarze), a reszta jest niemalże analogiczna. Sugeruję jednak zrozumieć podstawową wersję na początku. Więcej o zakresach w innym artykule mojego autorstwa. Poza tym algorytmy oparte na zakresach często są wolniejsze, oraz mają pewne wady (opisane w wystąpieniu Nico Josuttis). Przykład użycia wersji opartej na zakresach:
std::ranges::for_each( v, print );

Jest to jednakże mechanizm znacznie bardziej zaawansowany niż tylko inny zestaw argumentów. Dodam też, że zestaw algorytmów z
std::ranges
 ma algorytmy, których nie ma w
std::
 np.
std::ranges::contains
,
std::ranges::contains_subrange
,
std::ranges::starts_with
,
std::ranges::ends_with
.

W C++17 wprowadzili do algorytmów możliwość sprecyzowania tzw. execution policy, czyli jak algorytmy mają być wykonywane (teoretycznie można wskazać wielowątkowe wykonanie, czy wykonanie na GPU), te algorytmy mają jako pierwszy argument obiekt polityki wykonania (execution policy) z nagłówka
< execution >
:
  • std::execution::seq
  • std::execution::par
  • std::execution::par_unseq
  • std::execution::unseq
     (wprowadzone w C++20)
Przykład użycia:
std::for_each( std::execution::par, v.begin(), v.end(), print );


Zanim się zachwycisz: pułapki execution policy

Krótki kubeł zimnej wody dla każdego, kto sądzi, że wstawienie
std::execution::par
 przy dowolnym algorytmie automatycznie da przyspieszenie:
  • Brak gwarancji przyspieszenia. Standard mówi tylko, że implementacja MOŻE użyć równoległości. Nie musi. Implementacja
    par
     może być identyczna jak
    seq
     — i to jest w pełni zgodne ze standardem.
  • Narzut wątków może pokonać zysk. Uruchomienie puli wątków (thread pool) kosztuje czas, a dla małych danych ten koszt łatwo przewyższa zysk z równoległości —
    par
     bywa wtedy wolniejszy od
    seq
    . To nie tylko teoria: zarówno Intel TBB, jak i implementacja sortowania równoległego w GCC mają wbudowany próg odcięcia rzędu 500 elementów, poniżej którego fragmenty i tak sortowane są sekwencyjnie — nawet autorzy tych implementacji uznali równoległość poniżej tego progu za nieopłacalną.
  • Wsparcie zależy od implementacji i platformy (stan na 2026 r. — sprawdź swój konkretny toolchain, temat szybko się zmienia). GCC z libstdc++ (od GCC 9) wymaga zlinkowania z TBB (
    - ltbb
    ) jako backendu dla
    par
    /
    par_unseq
    — bez tego bywa to błąd linkera, a bywa, że po prostu cicho wykona się sekwencyjnie (zależnie od wersji). MSVC ma wbudowane wsparcie od Visual Studio 2017 15.7 (część algorytmów już od 15.5 jako eksperymentalne). Clang/libc++ ma sytuację najbardziej niejednoznaczną: nagłówki istnieją i się kompilują, ale faktyczny backend wielowątkowy dla większości algorytmów wciąż bywa niekompletny —
    par
     może po cichu zachowywać się jak
    seq
    .
  • Callable musi być thread-safe. Gdy używasz
    par
    , Twój funktor/lambda może być wywołany równolegle z wielu wątków. Dostęp do danych współdzielonych bez synchronizacji = wyścig danych (data race) = UB.
  • Wyjątki zachowują się inaczej. Jeśli callable rzuci wyjątek w trybie
    par
    , algorytm wywołuje
    std::terminate()
     — nie propaguje wyjątku!
  • par_unseq
    jest najbardziej restrykcyjny. Callable nie może blokować mutexów, alokować pamięci ani używać wyjątków. Naruszenie to UB.
Kiedy więc używać
par
/
par_unseq
? Tylko gdy: (1) dane są duże (setki tysięcy elementów i więcej), (2) operacja jest ciężka obliczeniowo (nie prosty sort), (3) sprawdziłeś benchmarkiem, że naprawdę jest szybciej, (4) callable jest thread-safe. Bez pomiaru — nie warto.

Jaki kawałek algorytmów będzie przedstawiony

Wszystkich algorytmów jest około stu, każdy z nich ma wiele wersji, które się zmieniały między standardami C++ (dochodziły nowsze wersje, lub się robiły bardziej
constexpr
), dlatego postaram się przedstawić najważniejsze wersje, z uwzględnieniem wymagań, pewnych pułapek, często złożoności obliczeniowej, oraz też standardu C++, jednakże będą to skrótowe informacje.

Algorytmy niemodyfikujące Cheetsheet

Poniżej lista wszystkich algorytmów określonych jako niemodyfikujące. Co prawda, jak ktoś chce to może przy ich pomocy dokonać pewnych zmian (np. jeśli ww.
std::for_each
 jako funkcje dostanie coś modyfikującego elementy, a elementy nie będą
const
).

Poniżej są wymienione algorytmy, niektóre z nich nie mają podanych wymagań, jednakże wymagania "oczywiste" są:
  • z początku zakresu należy móc dotrzeć do końca zakresu
  • równocześnie inny wątek nie powinien modyfikować zakresu

Najpierw szybki indeks do przypomnienia — pełny opis ze składnią, złożonością i uwagami jest pod tabelką (parametry skrócone: f/l = first/last, f1/l1/f2/l2 = first1/last1/first2/last2 funkcje/predykaty wg liczby argumentów: g1 = funkcja jednoargumentowa, g2 = funkcja wieloargumentowa, p1 = predykat jednoargumentowy, p2 = predykat dwuargumentowy — generator bezargumentowy (gen) nie występuje w tej sekcji, pojawi się przy algorytmach modyfikujących). Dopisek ", c2?" oznacza opcjonalny parametr porównujący dwa elementy — czyli że istnieją dwie wersje: jedna z domyślnym
operator ==
, a druga z możliwością wstrzyknięcia własnego "porównywacza". Typ zwracany podany jest po dwukropku;
it
 oznacza, że zwraca iterator.

Poniższa tabela zawiera pewne informacje w sposób bardziej skrótowy, a tym samym niezgodnym z sygnaturą czy typami w C++

Podane O(N) to zawsze przypadek pesymistyczny — samo duże O z definicji jest górnym ograniczeniem, więc już oznacza "co najwyżej rzędu N", nigdy "zawsze dokładnie N" (to dopiero byłoby Θ(N)). W algorytmach wyszukujących (
all_of
/
any_of
/
none_of
,
find
*,
search
*,
adjacent_find
,
mismatch
,
equal
) realny czas pracy bywa krótszy, bo kończą się wcześniej, gdy wynik jest już przesądzony — fachowo nazywa się to short-circuit (wcześniejsze zakończenie), NIE "leniwe wartościowanie" (lazy evaluation to inny mechanizm — odraczanie obliczenia wartości do momentu, gdy jest naprawdę potrzebna, a nie przerywanie pętli). Najlepszy przypadek to wtedy Ω(1). Wyjątek:
for_each
,
for_each_n
,
count
,
count_if
 zawsze odwiedzają każdy element z osobna — tu O(N) jest ścisłe, czyli Θ(N).

algorytm standard opis
for_each( f, l, g1 )->g1
C++98 Wywołaj g1 dla każdego elementu i zwróć g1
for_each_n( f, n, g1 )->it
C++17 Wywołaj g1 na pierwszych n elementach
all_of( f, l, p1 )->bool
C++11 Czy p1 prawdziwy dla wszystkich elementów?
any_of( f, l, p1 )->bool
C++11 Czy p1 prawdziwy dla co najmniej jednego elementu?
none_of( f, l, p1 )->bool
C++11 Czy wszystkie elementy nie spełniają p1?
find( f, l, value )->it
C++98 Znajdź pozycje pierwszego elementu równego value albo l
find_if( f, l, p1 )->it
C++98 Znajdź pozycje pierwszego elementu spełniającego p1 albo l
find_if_not( f, l, p1 )->it
C++11 Znajdź pozycje pierwszego elementu niespełniającego p1 albo l
find_end( f1, l1, f2, l2, c2 ? )->it
C++98 Znajdź pozycje ostatniego wystąpienie drugiego [f2,l2) zakresu w pierwszym [f1,l1)
find_first_of( f1, l1, f2, l2, c2 ? )->it
C++98 Znajdź pierwszą pozycje dowolnego elementu z zakresu [f2, l2]
adjacent_find( f, l, c2 ? )->it
C++98 Znajdź początek pierwszej pary równych sąsiednich elementów
count( f, l, value )->ptrdiff
C++98 Ile elementów równych value
count_if( f, l, p1 )->ptrdiff
C++98 Ile elementów spełnia p1
C/C++
mismatch( f1, l1, f2, c2 ? )->pair < it1, it2 >
mismatch( f1, l1, f2, l2, c2 ? )->pair < it1, it2 > /* C++14, bezpieczniejsza*/
C++98 / C++14 Pierwsza para różniących się elementów z dwóch zakresów
C/C++
equal( f1, l1, f2, c2 ? )->bool
equal( f1, l1, f2, l2, c2 ? )->bool /*C++14, bezpieczniejsza*/
C++98 / C++14 Czy dwa zakresy są identyczne
search( f1, l1, f2, l2, c2 ? )->it

search( f1, l1, f2, l2, searcher )->it /*C++17*/
 C++98 / C++17 Znajdź pierwsze wystąpienie drugiego zakresu w pierwszym
search_n( f, l, n, value, c2 ? )->it
C++98 Znajdź pozycje pierwszych n kolejnych elementów równych value

std::for_each (też w dokumentacji: » Język C++» standard C++» Algorytmyfor_each funkcja)
Złożoność: O(N) (ściśle: Θ(N)
f
 jest wywoływane dokładnie N razy, bez wyjątku)
C/C++
template < class InputIt, class UnaryFunc >
UnaryFunc for_each( InputIt first, InputIt last, UnaryFunc f );
Wykonuje funkcję
f
 dla każdego elementu zakresu —
f
 może być funktorem, lambdą, ale też zwykłą funkcją (bez wrappera typu
ptr_fun
). Z założenia nie modyfikuje elementów, ale jeśli podamy funktor modyfikujący, a zakres nie będzie stały (nie const-iteratory), modyfikacja się uda. Zwraca podaną funkcję/funktor — dzięki temu można odzyskać stan funktora po przejściu całego zakresu.

std::for_each_n (C++17)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class Size, class UnaryFunc >
InputIt for_each_n( InputIt first, Size n, UnaryFunc f );
Stosuje
f
 do pierwszych
n
 elementów od
first
 (
n
ujemne to UB). W odróżnieniu od
for_each
 zwraca iterator
first + n
, nie obiekt
f
 — nie da się więc odzyskać stanu funktora. Przydatne, gdy nie mamy pod ręką
last
 albo liczy się tylko określona liczba początkowych elementów.

std::all_of / std::any_of / std::none_of (C++11)
Złożoność: O(N) w najgorszym przypadku (Θ(N) — patrz uwaga o short-circuit wyżej), Θ(1) w najlepszym
C/C++
template < class InputIt, class UnaryPred >
bool all_of( InputIt first, InputIt last, UnaryPred predicate );
bool any_of( InputIt first, InputIt last, UnaryPred predicate );
bool none_of( InputIt first, InputIt last, UnaryPred predicate );
Sprawdzają, czy predykat jest prawdziwy: dla wszystkich / dla przynajmniej jednego / dla żadnego elementu.
Zachowanie na PUSTYM zakresie to nie przypadek ani UB, tylko logika pustego kwantyfikatora (vacuous truth):
all_of
 i
none_of
 zwracają
true
,
any_of
 zwraca
false
.

std::find / std::find_if / std::find_if_not (też w dokumentacji: » Język C++» standard C++» Algorytmyfind funkcja / » Język C++» standard C++» Algorytmyfind_if funkcja)
Złożoność: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym (patrz uwaga o short-circuit wyżej)
C/C++
InputIt find( InputIt first, InputIt last, const T & value );
InputIt find_if( InputIt first, InputIt last, UnaryPred predicate ); // C++98
InputIt find_if_not( InputIt first, InputIt last, UnaryPred predicate ); // C++11
find
zwraca pierwszy element równy
value
;
find_if
 pierwszy element, dla którego predykat zwraca
true
;
find_if_not
 — dla którego zwraca
false
. Każdy z nich zwraca
last
, gdy nic nie pasuje.

std::find_end (też w dokumentacji: » Język C++» standard C++» Algorytmyfind_end funkcja)
Złożoność: O(S·(N−S+1)) w najgorszym przypadku (to gwarancja standardu dla wersji bez searchera, nie dowiedziona optymalna granica — algorytmy typu KMP  potrafią to zrobić w O(N+S), więc Θ tu nie przypisuję)
C/C++
template < class ForwardIt1, class ForwardIt2 >
ForwardIt1 find_end( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, ForwardIt2 last2 );

template < class ForwardIt1, class ForwardIt2, class BinaryPred >
ForwardIt1 find_end( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, ForwardIt2 last2, BinaryPred pred );
Wyszukuje ostatnie wystąpienie podzakresu wyznaczonego przez
first2, last2
 w zakresie źródłowym
first1, last1
. Zwraca iterator na początek znalezionej podsekwencji, lub
last1
, gdy nie znaleziono.
To nie jest kwestia "kierunku szukania", tylko tego, KTÓRE dopasowanie ma zostać zwrócone — zawsze ostatnie. Standard wymaga jedynie
ForwardIterator
 (implementacja może więc iść w przód, pamiętając ostatnie trafienie); złożoność to co najwyżej S·(N−S+1) porównań, gdzie S i N to długości obu zakresów. Implementacja MOŻE (nie musi) przeszukiwać od końca do początku, jeśli iteratory są co najmniej
BidirectionalIterator
 — bywa to szybsze w praktyce, ale nie zmienia złożoności pesymistycznej.

std::find_first_of
Złożoność: O(N1·N2) w najgorszym przypadku (ściśle: Θ(N1·N2)), gdzie N1 = długość
first1, last1
, N2 = długość
first2, last2
C/C++
template < class InputIt, class ForwardIt >
InputIt find_first_of( InputIt first1, InputIt last1, ForwardIt first2, ForwardIt last2 );

template < class InputIt, class ForwardIt, class BinaryPred >
InputIt find_first_of( InputIt first1, InputIt last1, ForwardIt first2, ForwardIt last2, BinaryPred pred );
Zwraca pierwszy element zakresu
first1, last1
, który jest równy któremukolwiek z elementów zakresu
first2, last2
 (domyślnie wg
operator ==
).

std::adjacent_find
Złożoność: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym
C/C++
template < class ForwardIt >
ForwardIt adjacent_find( ForwardIt first, ForwardIt last );

template < class ForwardIt, class BinaryPred >
ForwardIt adjacent_find( ForwardIt first, ForwardIt last, BinaryPred pred );
Przeszukuje zakres do momentu znalezienia pierwszej pary sąsiadujących, równych sobie elementów. Zwraca iterator na pierwszy z tej pary (lub
last
, gdy nie znaleziono).

std::count / std::count_if (też w dokumentacji: » Język C++» standard C++» Algorytmycount funkcja / » Język C++» standard C++» Algorytmycount_if funkcja)
Złożoność: O(N) (ściśle: Θ(N) — zawsze dokładnie N porównań/wywołań, patrz uwaga wyżej)
C/C++
template < class InputIt, class T >
typename std::iterator_traits < InputIt >::difference_type
count( InputIt first, InputIt last, const T & value );

template < class InputIt, class UnaryPred >
typename std::iterator_traits < InputIt >::difference_type
count_if( InputIt first, InputIt last, UnaryPred predicate );
Zliczają liczbę elementów równych
value
 / spełniających predykat. Zawsze dokładnie N porównań/wywołań predykatu — w odróżnieniu od
find *
 nie przerywają się wcześniej, bo muszą przejść cały zakres. Zwracają
iterator_traits < InputIt >::difference_type
 (zwykle
ptrdiff_t
), nie
int
 — dla naprawdę wielkich zakresów to istotne.

std::mismatch
Złożoność: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym
C/C++
template < class InputIt1, class InputIt2 >
std::pair < InputIt1, InputIt2 > mismatch( InputIt1 first1, InputIt1 last1, InputIt2 first2 );
template < class InputIt1, class InputIt2, class BinaryPred >
std::pair < InputIt1, InputIt2 > mismatch( InputIt1 first1, InputIt1 last1, InputIt2 first2, BinaryPred p );

// bezpieczniejsza wersja od C++14 — sprawdza też koniec drugiego zakresu
template < class InputIt1, class InputIt2 >
std::pair < InputIt1, InputIt2 > mismatch( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );
template < class InputIt1, class InputIt2, class BinaryPred >
std::pair < InputIt1, InputIt2 > mismatch( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPred p );
Zwraca pozycje z obu zakresów, w których elementy pierwszy raz się różnią.
Co dokładnie zwraca, gdy nie znaleziono rozbieżności? W wersji klasycznej (bez
last2
) — jeśli WSZYSTKO się zgadza na całej długości
first1 .. last1
, para to
( last1, first2 + distance( first1, last1 ) )
. W wersji bezpiecznej (z
last2
, od C++14) porównywanie zatrzymuje się na krótszym z obu zakresów — jeśli ten krótszy się wyczerpie bez rozbieżności, zwrócona para wskazuje jego koniec i odpowiadającą pozycję w drugim zakresie (różna długość obu zakresów sama w sobie liczy się więc jako "mismatch" w tym sensie, że iteracja się zatrzymuje, mimo że porównane fragmenty były identyczne).

std::equal (też w dokumentacji: » Język C++» standard C++» Algorytmyequal funkcja)
Złożoność: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym
C/C++
template < class InputIt1, class InputIt2 >
bool equal( InputIt1 first1, InputIt1 last1, InputIt2 first2 );
template < class InputIt1, class InputIt2, class BinaryPred >
bool equal( InputIt1 first1, InputIt1 last1, InputIt2 first2, BinaryPred p );

// bezpieczniejsza wersja od C++14 (ten sam papier co dla mismatch)
template < class InputIt1, class InputIt2 >
bool equal( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );
template < class InputIt1, class InputIt2, class BinaryPred >
bool equal( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPred p );
Sprawdza, czy dwa zakresy są sobie równe element po elemencie.
Wersja klasyczna (bez
last2
) zakłada, że drugi zakres ma co najmniej tyle elementów co
first1 .. last1
 — jeśli nie, to UB, stąd bezpieczniejsza wersja z C++14. To nie to samo, co
ranges::equal
 ani "po prostu"
operator ==
 na kontenerach — choć np.
std::vector
 ma własny
operator ==
, który pod spodem korzysta z tej samej idei.

std::search (też w dokumentacji: » Język C++» standard C++» Algorytmysearch funkcja)
Złożoność: O(N·S) w najgorszym przypadku (to gwarancja standardu dla wersji bez searchera, nie dowiedziona optymalna granica — stąd searchery niżej: KMP/Boyer-Moore potrafią O(N+S)), gdzie S = długość szukanego podciągu, N = długość zakresu źródłowego
C/C++
template < class ForwardIt1, class ForwardIt2 >
ForwardIt1 search( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, ForwardIt2 last2 );

template < class ForwardIt1, class ForwardIt2, class BinaryPred >
ForwardIt1 search( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, ForwardIt2 last2, BinaryPred pred );
Wyszukuje podciąg wyznaczony przez
first2, last2
 w zakresie źródłowym
first1, last1
. Zwraca pozycję jego początku, lub
last1
, gdy nie znaleziono.
Od C++17
search
 ma też przeciążenie przyjmujące obiekt
Searcher
 (nagłówek
< functional >
) zamiast pary iteratorów:
std::default_searcher
 (opakowuje klasyczny
search
),
std::boyer_moore_searcher
 i
std::boyer_moore_horspool_searcher
 — implementują klasyczne algorytmy wyszukiwania wzorca w tekście i przy długich haystackach/needlach potrafią być zauważalnie szybsze, kosztem wymogu
RandomAccessIterator
 i przygotowania wewnętrznych tablic pomocniczych:
auto it = std::search( text.begin(), text.end(), std::boyer_moore_searcher( pattern.begin(), pattern.end() ) );

std::search_n
Złożoność: O(N) (ściśle: Θ(N) — patrz dymek niżej, dlaczego NIE O(N·count))
C/C++
template < class ForwardIt, class Size, class T >
ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T & value );

template < class ForwardIt, class Size, class T, class BinaryPred >
ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T & value, BinaryPred pred );
Szuka pierwszego wystąpienia
count
 kolejnych (sąsiadujących) elementów równych
value
. Zwraca iterator na początek znalezionej podsekwencji, lub
last
, gdy nie znaleziono; dla
count <= 0
 zwraca
first
. Przydatne np. do szukania "trzech kolejnych zer" w sekwencji.
Złożoność to O(N), NIE O(N·count) — mimo że naiwna implementacja (dla każdej pozycji sprawdź kolejne count elementów) sugerowałaby to drugie. Standard gwarantuje co najwyżej N porównań, bo dobra implementacja nie zaczyna sprawdzania od nowa po nieudanym dopasowaniu, tylko przeskakuje od razu za miejsce, gdzie seria się urwała.

Algorytmy modyfikujące

Omówione wyżej algorytmy niemodyfikujące odpowiadały na pytania „czy", „ile" i „gdzie" — żaden z nich nie zmienia zawartości zakresu (a przynajmniej nie taki jest jego zamysł, o czym była mowa przy
for_each
). Poniżej zestaw algorytmów, które albo modyfikują zakres źródłowy w miejscu, albo tworzą zmodyfikowaną kopię pod innym adresem. Grupuję je tak, jak w zestawieniu na początku artykułu.

Podział danych (partitioning)
"Partycjonowanie" to przegrupowanie zakresu tak, by elementy spełniające predykat znalazły się przed elementami go niespełniającymi (bez pełnego sortowania) — to dokładnie to, co robi krok "partition" algorytmu quicksort, i to jest chyba najlepsza analogia do zapamiętania tej grupy.

algorytm standard opis
partition( f, l, p1 )->it
C++98 Przegrupuj — spełniające p1 przed niespełniającymi. Zwraca punkt podziału
stable_partition( f, l, p1 )->it
C++98 Jak partition, zachowując względną kolejność w obu grupach
partition_copy( f, l, dst_true, dst_false, p1 )->pair < it, it >
C++11 Kopiuje do dwóch osobnych miejsc wg p1
is_partitioned( f, l, p1 )->bool
C++11 Czy zakres jest już podzielony wg p1
partition_point( f, l, p1 )->it
C++11 Punkt podziału już podzielonego zakresu (szukanie binarne)

std::partition / std::stable_partition
Złożoność: O(N) (ściśle: Θ(N) — standard wymaga dokładnie N zastosowań predykatu)
C/C++
template < class ForwardIt, class UnaryPred >
ForwardIt partition( ForwardIt first, ForwardIt last, UnaryPred p );

template < class BidirIt, class UnaryPred >
BidirIt stable_partition( BidirIt first, BidirIt last, UnaryPred p );
Przenoszą elementy spełniające
p
 na początek zakresu, resztę na koniec. Zwracają iterator na pierwszy element drugiej grupy (punkt podziału). Różnica:
partition
 nie gwarantuje zachowania względnej kolejności elementów w obrębie każdej z grup (i wymaga tylko
ForwardIterator
),
stable_partition
 tę kolejność zachowuje (kosztem wymogu
BidirectionalIterator
 i potencjalnie gorszej złożoności, gdy brakuje dodatkowej pamięci).
Analogicznie do
remove
/
unique
partition
 nie usuwa niczego z kontenera, tylko przestawia elementy. Jeśli zamiast pełnego podziału potrzebujesz tylko usunąć elementy niespełniające warunku, prościej użyć idiomu erase-remove z
remove_if
 (patrz sekcja "Usuwanie"). Złożoność
stable_partition
: dokładnie N zastosowań predykatu przy wystarczającej dodatkowej pamięci, w przeciwnym razie O(N log N) zamian (ale wciąż O(N) zastosowań predykatu) — sama implementacja może np. rekurencyjnie dzielić zakres na pół.

std::partition_copy (C++11)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt1, class OutputIt2, class UnaryPred >
std::pair < OutputIt1, OutputIt2 >
partition_copy( InputIt first, InputIt last,
OutputIt1 d_first_true, OutputIt2 d_first_false, UnaryPred p );
Nie modyfikuje źródła (wystarczy
InputIterator
) — kopiuje elementy spełniające
p
 do
d_first_true
, resztę do
d_first_false
. Zwraca parę iteratorów końcowych obu miejsc docelowych. Zakresy docelowe nie mogą nachodzić na zakres źródłowy ani na siebie nawzajem.

std::is_partitioned / std::partition_point (C++11)
Złożoność:
is_partitioned
: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym /
partition_point
: Θ(log N) — liczba kroków szukania binarnego zależy tylko od N, nie od danych
C/C++
template < class InputIt, class UnaryPred >
bool is_partitioned( InputIt first, InputIt last, UnaryPred p );

template < class ForwardIt, class UnaryPred >
ForwardIt partition_point( ForwardIt first, ForwardIt last, UnaryPred p );
is_partitioned
sprawdza (liniowo, w jednym przejściu), czy w zakresie wszystkie elementy spełniające
p
 poprzedzają te niespełniające.
partition_point
 zakłada, że zakres JEST już tak podzielony (to założenie, nie coś co sprawdza — niespełnienie to UB) i szuka punktu podziału szukaniem binarnym, analogicznie do
lower_bound
 poniżej — stąd O(log N) porównań, choć samych "kroków" iteratora może być O(N) przy iteratorach nie będących
RandomAccessIterator
.

Sortowanie
Sortowanie jest operacja o kosztownej złożoności obliczeniowej, dlatego staramy się go unikać. Jeśli jednak chcemy sięgnąć po sortowanie to zastanówmy się czy potrzebujemy zawsze pełnego sortowania? Solidne porównanie doboru wystarczająco dobrego sortowania w książce STL w praktyce. 50 sposobów efektywnego wykorzystania - Scott Meyers w rozdziale 31.
algorytm standard opis
sort( f, l, c2 ? )
C++98 Sortuj cały zakres — [b]O(N log N)[/b] gwarantowane od C++11
stable_sort( f, l, c2 ? )
C++98 Sortuj zachowując kolejność elementów równych
partial_sort( f, mid, l, c2 ? )
C++98 Posortuj tylko pierwsze (mid-f) elementów
partial_sort_copy( f, l, df, dl, c2 ? )->it
C++98 partial_sort z wynikiem w osobnym miejscu
nth_element( f, nth, l, c2 ? )
C++98 Quickselect — nth trafia na docelową pozycję
is_sorted( f, l, c2 ? )->bool
C++11 Czy zakres jest posortowany
is_sorted_until( f, l, c2 ? )->it
C++11 Pierwszy element psujący posortowanie

std::sort (też w dokumentacji: » Język C++» standard C++» Algorytmysort szablon funkcji)
Złożoność: O(N log N) w najgorszym przypadku — i ściśle Θ(N log N): to zarazem znane dolne ograniczenie (Ω(N log N)) dla DOWOLNEGO sortowania opartego na porównaniach, więc lepiej być nie może
C/C++
template < class RandomIt > void sort( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void sort( RandomIt first, RandomIt last, Compare comp );
Wymaga
RandomAccessIterator
 (stąd nie zadziała bezpośrednio na
std::list
 — tam trzeba użyć metody składowej
list::sort()
). NIE jest stabilny — elementy "równe" wg komparatora mogą zmienić względną kolejność.
Komparator musi spełniać strict weak ordering — w szczególności
comp( a, a )
 musi być fałszywe. Najczęstszy błąd zaczynających: komparator
<=
 zamiast
<
 dla sortowania rosnącego — to złamanie strict weak ordering i UB (w praktyce bywa segfault, pętla nieskończona, albo błędny wynik, w zależności od implementacji).
Przed C++11 standard gwarantował złożoności "w przybliżeniu N log N" tylko dla przeciętnego przypadku, dopuszczając implementacje czystego quicksortu (podatne na O(N²) w pesymistycznym przypadku, np. dla już posortowanych danych ze złym wyborem pivota). Od C++11 wymagane jest O(N log N) w najgorszym przypadku, co w praktyce wymusza introsort (quicksort z przełączeniem na heapsort po przekroczeniu limitu głębokości rekursji) — tak działają np. implementacje GCC/libstdc++ i Clang/libc++.

std::stable_sort
Złożoność: O(N log N) przy wystarczającej dodatkowej pamięci (ściśle: Θ(N log N), z tego samego powodu co
sort
 wyżej) — bez dodatkowej pamięci O(N log²N) (to gwarancja standardu, nie potwierdzona jako optymalna dolna granica, więc zostaje przy samym O)
C/C++
template < class RandomIt > void stable_sort( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void stable_sort( RandomIt first, RandomIt last, Compare comp );
Jak
sort
, ale zachowuje względną kolejność elementów "równych" wg komparatora — kluczowe przy sortowaniu wielokluczowym (np. najpierw sortujesz po nazwisku, potem stabilnie po dziale — w efekcie w obrębie działu kolejność alfabetyczna nazwisk przetrwa). Implementacja typowo korzysta z merge sort, stąd potrzeba dodatkowej pamięci dla gwarancji O(N log N); bez niej degraduje do O(N log²N).

std::partial_sort / std::partial_sort_copy
Złożoność: O(N log M), M = liczba sortowanych elementów
C/C++
template < class RandomIt > void partial_sort( RandomIt first, RandomIt middle, RandomIt last );
template < class RandomIt, class Compare > void partial_sort( RandomIt first, RandomIt middle, RandomIt last, Compare comp );

template < class InputIt, class RandomIt >
RandomIt partial_sort_copy( InputIt first, InputIt last, RandomIt d_first, RandomIt d_last );
template < class InputIt, class RandomIt, class Compare >
RandomIt partial_sort_copy( InputIt first, InputIt last, RandomIt d_first, RandomIt d_last, Compare comp );
partial_sort
sortuje tylko fragment od
first
 do
middle
 (wyłącznie), tak by zawierał M najmniejszych elementów całego zakresu we właściwej kolejności — reszta (od
middle
 do
last
) pozostaje w nieokreślonej kolejności. Klasyczne zastosowanie: "top-K najlepszych wyników" bez sortowania całości.
partial_sort_copy
 robi to samo, ale nie modyfikuje źródła (wystarczy
InputIterator
) i pisze do osobnego zakresu docelowego — liczba faktycznie posortowanych elementów to
min( last - first, d_last - d_first )
, zwracany iterator wskazuje na koniec zapisanego fragmentu.

std::nth_element
Złożoność: O(N) przeciętnie (average case — to jest kluczowe zastrzeżenie: standard NIE gwarantuje O(N) w najgorszym przypadku, więc Θ(N) tu nie przypisuję; przy złośliwie dobranych danych i naiwnym wyborze piwota implementacja mogłaby teoretycznie działać wolniej)
C/C++
template < class RandomIt > void nth_element( RandomIt first, RandomIt nth, RandomIt last );
template < class RandomIt, class Compare > void nth_element( RandomIt first, RandomIt nth, RandomIt last, Compare comp );
Po wywołaniu element na pozycji
nth
 to dokładnie ten element, który znalazłby się tam po pełnym posortowaniu — wszystkie elementy przed nim są
<= * nth
, wszystkie po nim
>= * nth
 (ale BEZ dalszego uporządkowania w obrębie tych dwóch grup). To Quickselect — średnio liniowe, znacznie szybsze niż pełne sortowanie, gdy potrzebujesz np. tylko mediany albo k-tego percentyla.

std::is_sorted / std::is_sorted_until (C++11)
Złożoność: O(N) w najgorszym przypadku (Θ(N) — pełne posortowanie wymaga sprawdzenia wszystkich par), Θ(1) w najlepszym (naruszenie zaraz na początku)
C/C++
template < class ForwardIt > bool is_sorted( ForwardIt first, ForwardIt last );
template < class ForwardIt, class Compare > bool is_sorted( ForwardIt first, ForwardIt last, Compare comp );

template < class ForwardIt > ForwardIt is_sorted_until( ForwardIt first, ForwardIt last );
template < class ForwardIt, class Compare > ForwardIt is_sorted_until( ForwardIt first, ForwardIt last, Compare comp );
is_sorted
zwraca
bool
;
is_sorted_until
 zwraca iterator na pierwszy element psujący porządek (lub
last
, gdy cały zakres jest posortowany) — przydatne przy diagnozowaniu "gdzie dokładnie" dane przestają być posortowane.

Przeszukiwania binarne
Cztery poniższe algorytmy wymagają, by zakres był już posortowany (a precyzyjniej: partycjonowany względem szukanej wartości — w praktyce niemal zawsze oznacza to posortowany) — nieposortowany zakres to niezdefiniowane zachowanie, nie błąd czasu kompilacji ani wyjątek.
Należy upewnić się, że zakres jest posortowany tym samym sposobem co użyty algorytm wyszukiwania binarnego. Domyślnie jest używany
std::less
 ale może być posortowane np. kolejnością malejącą, wtedy należy pamiętać aby przekazać właściwy algorytm.

algorytm standard opis
lower_bound( f, l, value, c2 ? )->it
C++98 Pierwsza pozycja, gdzie można wstawić value zachowując porządek (pierwszy element >= value)
upper_bound( f, l, value, c2 ? )->it
C++98 Jak wyżej, ale pierwszy element > value
equal_range( f, l, value, c2 ? )->pair < it, it >
C++98 Para {lower_bound, upper_bound} — cały podzakres równy value
binary_search( f, l, value, c2 ? )->bool
C++98 Czy value istnieje w zakresie

std::lower_bound / std::upper_bound
Złożoność: O(log N) porównań (ściśle: Θ(log N) — liczba kroków szukania binarnego zależy tylko od N)
C/C++
template < class ForwardIt, class T >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T & value );
template < class ForwardIt, class T, class Compare >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T & value, Compare comp );

template < class ForwardIt, class T >
ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T & value );
template < class ForwardIt, class T, class Compare >
ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T & value, Compare comp );
lower_bound
zwraca iterator na pierwszy element NIE mniejszy od
value
 (czyli
>= value
);
upper_bound
 — na pierwszy element ściśle większy od
value
. Różnica tych dwóch iteratorów to liczba elementów równych
value
 (zero, gdy value nie występuje). To budulec do wstawiania z zachowaniem sortowania:
v.insert( std::lower_bound( v.begin(), v.end(), x ), x )
.
Złożoność O(log N) porównań dotyczy samego algorytmu, ale liczba przesunięć iteratora zależy od jego kategorii: dla
RandomAccessIterator
 (np.
vector
) całość jest rzeczywiście O(log N). Dla zwykłego
ForwardIterator
 (np. ręcznie pisana lista jednokierunkowa) przesunięcie iteratora do połowy zakresu samo w sobie kosztuje O(N) — więc realny koszt całkowity to O(N), mimo że porównań jest tylko O(log N). To ta sama pułapka, co przy
std::list
:
std::list
 nie ma
RandomAccessIterator
, więc
lower_bound
 na nim traci sens praktyczny — lepiej użyć posortowanego kontenera asocjacyjnego.

std::equal_range / std::binary_search (binary_search też w dokumentacji: » Język C++» standard C++» Algorytmybinary_search szablon funkcji)
Złożoność: O(log N) porównań (ściśle: Θ(log N), z tego samego powodu co
lower_bound
/
upper_bound
)
C/C++
template < class ForwardIt, class T >
std::pair < ForwardIt, ForwardIt > equal_range( ForwardIt first, ForwardIt last, const T & value );
template < class ForwardIt, class T, class Compare >
std::pair < ForwardIt, ForwardIt > equal_range( ForwardIt first, ForwardIt last, const T & value, Compare comp );

template < class ForwardIt, class T >
bool binary_search( ForwardIt first, ForwardIt last, const T & value );
template < class ForwardIt, class T, class Compare >
bool binary_search( ForwardIt first, ForwardIt last, const T & value, Compare comp );
equal_range
to po prostu para
{ lower_bound( ... ), upper_bound( ... ) }
 (choć zwykle liczona wydajniej niż dwa osobne wywołania).
binary_search
 zwraca tylko
bool
 — jeśli potrzebujesz też pozycji, użyj
lower_bound
 i porównaj wynik z
value
 (tak jest wydajniej niż wołać oba).

Operacje na zbiorach
Wszystkie poniższe wymagają posortowanych zakresów wejściowych (tym samym komparatorem, którym potem sortowany jest — i będzie — wynik) i traktują je jak zbiory/multizbiory. Zakres wyjściowy nie może pokrywać się z żadnym z wejściowych.
Mimo nazwy sugerującej "zbiory", żaden z tych algorytmów nie wymaga
std::set
 — działają na dowolnym posortowanym zakresie, także na
std::vector
. Odwrotnie: chcąc użyć ich na dwóch
std::set
, wystarczy podać ich iteratory —
std::set
 jest z definicji posortowany.

algorytm standard opis
includes( f1, l1, f2, l2, c2 ? )->bool
C++98 Czy zakres 2 jest podzbiorem (multizbioru) zakresu 1
set_union( f1, l1, f2, l2, dst, c2 ? )->it
C++98 Suma zbiorów do dst
set_intersection( f1, l1, f2, l2, dst, c2 ? )->it
C++98 Część wspólna do dst
set_difference( f1, l1, f2, l2, dst, c2 ? )->it
C++98 Elementy z zakresu 1 nieobecne w zakresie 2
set_symmetric_difference( f1, l1, f2, l2, dst, c2 ? )->it
C++98 Elementy obecne w dokładnie jednym z zakresów

std::includes
Złożoność: O(N1+N2) w najgorszym przypadku (ściśle: Θ(N1+N2) — algorytm typu "merge", zawsze przegląda oba zakresy liniowo)
C/C++
template < class InputIt1, class InputIt2 >
bool includes( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );

template < class InputIt1, class InputIt2, class Compare >
bool includes( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Compare comp );
Sprawdza, czy każdy element zakresu 2 występuje w zakresie 1 z co najmniej taką samą krotnością (czyli zakres 2 jako multizbiór zawiera się w zakresie 1). Oba zakresy muszą być posortowane tym samym porządkiem.

std::set_union / std::set_intersection
Złożoność: O(N1+N2) (ściśle: Θ(N1+N2) — jak wyżej, stały koszt niezależny od zawartości)
C/C++
template < class InputIt1, class InputIt2, class OutputIt >
OutputIt set_union( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
template < class InputIt1, class InputIt2, class OutputIt, class Compare >
OutputIt set_union( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );

template < class InputIt1, class InputIt2, class OutputIt >
OutputIt set_intersection( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
template < class InputIt1, class InputIt2, class OutputIt, class Compare >
OutputIt set_intersection( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );
Dla elementu występującego
m
 razy w zakresie 1 i
n
 razy w zakresie 2:
set_union
 zapisze
max( m, n )
 kopii,
set_intersection
 —
min( m, n )
 kopii. Wynik jest posortowany.

std::set_difference / std::set_symmetric_difference
Złożoność: O(N1+N2) (ściśle: Θ(N1+N2), jak wyżej)
C/C++
template < class InputIt1, class InputIt2, class OutputIt >
OutputIt set_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
template < class InputIt1, class InputIt2, class OutputIt, class Compare >
OutputIt set_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );

template < class InputIt1, class InputIt2, class OutputIt >
OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );
template < class InputIt1, class InputIt2, class OutputIt, class Compare >
OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );
set_difference
:
max( m - n, 0 )
 kopii elementu (to, co zostaje z zakresu 1 po "odjęciu" zakresu 2) — niesymetryczne, kolejność argumentów ma znaczenie.
set_symmetric_difference
:
| m - n |
 kopii — elementy, które nie są częścią wspólną (to, co dałoby XOR na zbiorach).

Operacje na kopcu (stercie, ang. heap)
Kopiec binarny (nie mylić ze stosem/LIFO!) to struktura umożliwiająca O(log N) wstawianie i O(log N) pobranie największego (lub najmniejszego, wg komparatora) elementu — to właśnie na tych pięciu/sześciu algorytmach oparty jest std::priority_queue. Reprezentacja to zwykły zakres o dostępie swobodnym (np.
vector
), interpretowany jako drzewo binarne wg konwencji "rodzic na indeksie i, dzieci na 2i+1 i 2i+2".

algorytm standard opis
make_heap( f, l, c2 ? )
C++98 Zbuduj kopiec z zakresu — [b]O(N)[/b]
push_heap( f, l, c2 ? )
C++98 Dołącz ostatni element do istniejącego kopca
pop_heap( f, l, c2 ? )
C++98 Przenieś szczyt kopca na koniec zakresu
sort_heap( f, l, c2 ? )
C++98 Posortuj kopiec (heapsort) — po tym to już nie kopiec
is_heap( f, l, c2 ? )->bool
C++11 Czy zakres spełnia własność kopca
is_heap_until( f, l, c2 ? )->it
C++11 Pierwsza pozycja psująca własność kopca

std::make_heap
Złożoność: O(N) (ściśle: Θ(N) — algorytm Floyda ma z góry ustaloną liczbę kroków zależną tylko od N)
C/C++
template < class RandomIt > void make_heap( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void make_heap( RandomIt first, RandomIt last, Compare comp );
Przekształca dowolny zakres w kopiec max (lub min, z odpowiednim
comp
) w miejscu. Mimo że budowanie kopca "od zera" wygląda na O(N log N) (N wstawień razy O(log N)), sprytny algorytm Floyda robi to w O(N) — to klasyczna, trochę nieoczywista sztuczka algorytmiczna warta zapamiętania.

std::push_heap / std::pop_heap
Złożoność: O(log N) w najgorszym przypadku (Θ(log N) — element może przesiąknąć aż do korzenia/liścia), Θ(1) w najlepszym (element trafia na miejsce od razu)
C/C++
template < class RandomIt > void push_heap( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void push_heap( RandomIt first, RandomIt last, Compare comp );

template < class RandomIt > void pop_heap( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void pop_heap( RandomIt first, RandomIt last, Compare comp );
Typowy wzorzec użycia z
vector
: żeby dodać element, najpierw
v.push_back( x )
 (element trafia na koniec, jeszcze poza kopcem), potem
push_heap( v.begin(), v.end() )
 (włącza go do kopca). Żeby zdjąć maksimum:
pop_heap( v.begin(), v.end() )
 (przenosi maksimum na koniec zakresu i przywraca własność kopca dla reszty), potem dopiero
v.pop_back()
 (fizycznie usuwa z kontenera). Same
push_heap
/
pop_heap
NIE zmieniają rozmiaru kontenera — to typowy błąd dla początkujących, którzy zapominają o drugim kroku (
push_back
/
pop_back
).

std::sort_heap / std::is_heap / std::is_heap_until (C++11)
Złożoność:
sort_heap
: O(N log N) (ściśle: Θ(N log N)) /
is_heap
,
is_heap_until
: O(N) w najgorszym przypadku (Θ(N)), Θ(1) w najlepszym
C/C++
template < class RandomIt > void sort_heap( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > void sort_heap( RandomIt first, RandomIt last, Compare comp );

template < class RandomIt > bool is_heap( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > bool is_heap( RandomIt first, RandomIt last, Compare comp );

template < class RandomIt > RandomIt is_heap_until( RandomIt first, RandomIt last );
template < class RandomIt, class Compare > RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );
sort_heap
to w istocie heapsort — powtarza
pop_heap
 aż do wyczerpania zakresu; wynikowy zakres jest posortowany, ale przestaje być kopcem.
is_heap
/
is_heap_until
działają analogicznie do
is_sorted
/
is_sorted_until
.

Min / max
algorytm standard opis
min( a, b, c2 ? )->const T &
C++98 Mniejsza z dwóch wartości
max( a, b, c2 ? )->const T &
C++98 Większa z dwóch wartości
minmax( a, b, c2 ? )->pair < const T &, const T & >
C++11 Para {min,max} — jednym przebiegiem
min_element( f, l, c2 ? )->it
C++98 Iterator na (pierwsze) minimum zakresu
max_element( f, l, c2 ? )->it
C++98 Iterator na (pierwsze) maksimum zakresu
minmax_element( f, l, c2 ? )->pair < it, it >
C++11 Para iteratorów {min,max} zakresu
clamp( v, lo, hi, c2 ? )->const T &
C++17 Obetnij v do przedziału od lo do hi

std::min / std::max / std::minmax (min/max też w dokumentacji: » Język C++» standard C++» Algorytmymin funkcja / » Język C++» standard C++» Algorytmymax szablon funkcji)
Złożoność: O(1) dla dwóch wartości (ściśle: Θ(1))
C/C++
template < class T > constexpr const T & min( const T & a, const T & b );
template < class T > constexpr const T & max( const T & a, const T & b );
template < class T > constexpr std::pair < const T &, const T & > minmax( const T & a, const T & b );
// + przeciążenia z Compare, oraz z std::initializer_list<T> (min/max od C++11, minmax od C++11)
Działają na WARTOŚCIACH, nie na zakresach (stąd brak par iteratorów) — łatwo je pomylić z
min_element
/
max_element
poniżej.
minmax( a, b )
 jest wydajniejszy niż osobne wywołanie
min
 i
max
 — potrzebuje najwyżej jednego porównania zamiast dwóch.
Przeciążenie z
std::initializer_list < T >
 pozwala pisać
std::max( { a, b, c, d } )
 zamiast zagnieżdżonych
std::max( std::max( a, b ), std::max( c, d ) )
. Zwracają
const T &
 — referencję do jednego z argumentów, co przy tymczasowych obiektach bywa źródłem subtelnego dangling reference (np.
const auto & r = std::max( compute1(), compute2() );
 jest bezpieczne tylko do końca pełnego wyrażenia — życiowo trwałe przedłużenie referencji do tymczasowego obiektu nie zadziała tu tak, jak można by się spodziewać, bo referencja zwrócona przez
max
 nie jest tym samym obiektem, do którego przypisujesz).

std::min_element / std::max_element / std::minmax_element (min_element/max_element też w dokumentacji: » Język C++» standard C++» Algorytmymin_element funkcja / » Język C++» standard C++» Algorytmymax_element funkcja)
Złożoność:
min_element
/
max_element
: O(N) (ściśle: Θ(N), dokładnie N−1 porównań — trzeba zobaczyć każdy element, by być pewnym) /
minmax_element
: co najwyżej 3N/2 porównań (ściśle: Θ(N) — to znany, dowiedziony optymalny wynik dla jednoczesnego szukania min i max)
C/C++
template < class ForwardIt > ForwardIt min_element( ForwardIt first, ForwardIt last );
template < class ForwardIt, class Compare > ForwardIt min_element( ForwardIt first, ForwardIt last, Compare comp );

template < class ForwardIt > ForwardIt max_element( ForwardIt first, ForwardIt last );
template < class ForwardIt, class Compare > ForwardIt max_element( ForwardIt first, ForwardIt last, Compare comp );

template < class ForwardIt > std::pair < ForwardIt, ForwardIt > minmax_element( ForwardIt first, ForwardIt last );
template < class ForwardIt, class Compare > std::pair < ForwardIt, ForwardIt > minmax_element( ForwardIt first, ForwardIt last, Compare comp );
Tym razem działają na ZAKRESACH i zwracają iterator (nie wartość) — dzięki temu można np. dowiedzieć się, na której pozycji jest minimum, albo je potem zmodyfikować. Przy duplikatach
min_element
 zwraca pierwsze wystąpienie minimum.
Subtelna niespójność, łatwa do przeoczenia:
minmax_element
 zwraca iterator na pierwsze minimum, ale na ostatnie maksimum (gdy są duplikaty) — inaczej niż wywołanie
max_element
 osobno, które zwróciłoby pierwsze maksimum! To celowy wybór standardu, umożliwiający implementację w maks. 3N/2 porównaniach (zamiast 2N przy dwóch osobnych przebiegach) — ale jeśli Twój kod zakłada "pierwsze wystąpienie" dla obu, to pułapka.

std::clamp (C++17)
Złożoność: O(1) (ściśle: Θ(1))
C/C++
template < class T > constexpr const T & clamp( const T & v, const T & lo, const T & hi );
template < class T, class Compare > constexpr const T & clamp( const T & v, const T & lo, const T & hi, Compare comp );
Zwraca
lo
 gdy
v < lo
,
hi
 gdy
hi < v
, w przeciwnym razie
v
. Warunek wstępny:
lo <= hi
 (w przeciwnym razie UB) — najczęstszy błąd to odwrócenie kolejności argumentów.

Kopiowanie i przenoszenie
algorytm standard opis
copy( f, l, dst )->it
C++98 Skopiuj zakres do dst
copy_if( f, l, dst, p1 )->it
C++11 Skopiuj tylko elementy spełniające p1
copy_n( f, n, dst )->it
C++11 Skopiuj pierwsze n elementów
copy_backward( f, l, dst_last )->it
C++98 Kopiuj od końca — bezpieczne przy nakładających się zakresach
move( f, l, dst )->it
C++11 Przenieś (move) elementy do dst
move_backward( f, l, dst_last )->it
C++11 Jak move, ale od końca

std::copy / std::copy_if / std::copy_n (copy też w dokumentacji: » Język C++» standard C++» Algorytmycopy funkcja)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt d_first );

template < class InputIt, class OutputIt, class UnaryPred > // C++11
OutputIt copy_if( InputIt first, InputIt last, OutputIt d_first, UnaryPred pred );

template < class InputIt, class Size, class OutputIt > // C++11
OutputIt copy_n( InputIt first, Size count, OutputIt result );
Zwracają iterator na koniec zapisanego fragmentu w
dst
.
copy_n
 przy
count <= 0
 nic nie robi i zwraca
result
 bez zmian — nie sprawdza, czy w źródle rzeczywiście jest
count
 elementów (to odpowiedzialność wołającego).

std::copy_backward (też w dokumentacji: » Język C++» standard C++» Algorytmycopy_backward funkcja)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class BidirIt1, class BidirIt2 >
BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
Kopiuje elementy zaczynając od końca zakresu (
*( last - 1 )
trafia na
*( d_last - 1 )
, itd.) — dzięki temu, w odróżnieniu od zwykłego
copy
, jest bezpieczne, gdy zakres docelowy NAKŁADA się na źródłowy, o ile
d_last
 nie wypada między
first
 a
last
 (włącznie z
first
). To dokładnie ta sztuczka, której implementacje kontenerów używają np. przy
insert()
 w środku wektora — trzeba "przesunąć" ogon w prawo, żeby zrobić miejsce, a robienie tego od przodu nadpisałoby dane, zanim zdążysz je przeczytać.

std::move (algorytm) / std::move_backward (C++11)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt >
OutputIt move( InputIt first, InputIt last, OutputIt d_first );

template < class BidirIt1, class BidirIt2 >
BidirIt2 move_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
Odpowiedniki
copy
/
copy_backward
, które zamiast kopiować — przenoszą (każdy element jest efektywnie "wynoszony" przez move-assignment, po czym w źródle pozostaje w poprawnym, ale nieokreślonym stanie).
Częste źródło zamieszania dla uczących się: to NIE jest ten sam
std::move
, co
std::move
 z
< utility >
 (rzutowanie na referencję r-value,
static_cast < T && >
). To dwie zupełnie różne funkcje o tej samej nazwie w tej samej przestrzeni nazw — przeciążone wg liczby/typu argumentów (ta z
< algorithm >
 bierze trzy argumenty i operuje na zakresie, ta z
< utility >
 — jeden argument i tylko zmienia "kategorię wartości" wyrażenia, nic nie przenosząc same z siebie). W praktyce kompilator zawsze wybierze właściwe przeciążenie po liczbie argumentów, ale przy czytaniu cudzego kodu warto pamiętać, że to dwa różne byty.

Podmiana elementów (swap)
algorytm standard opis
swap( a, b )
C++98 (nagłówek <utility>) Zamień miejscami dwie wartości
iter_swap( it1, it2 )
C++98 Zamień wartości pod dwoma iteratorami
swap_ranges( f1, l1, f2 )->it
C++98 Zamień miejscami dwa zakresy element po elemencie

std::swap / std::iter_swap / std::swap_ranges (swap/iter_swap też w dokumentacji: » Język C++» standard C++» Algorytmyswap szablon funkcji / » Język C++» standard C++» Algorytmyiter_swap funkcja)
Złożoność:
swap
/
iter_swap
: O(1) (ściśle: Θ(1)) /
swap_ranges
: O(N) (ściśle: Θ(N))
C/C++
template < class T > void swap( T & a, T & b ); // <utility>, constexpr od C++20

template < class ForwardIt1, class ForwardIt2 >
void iter_swap( ForwardIt1 a, ForwardIt2 b );

template < class ForwardIt1, class ForwardIt2 >
ForwardIt2 swap_ranges( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2 );
iter_swap
przyjmuje iteratory (niekoniecznie tego samego typu — mogą to być iteratory dwóch różnych kontenerów, o ile przechowują ten sam typ wartości) i pod spodem woła
swap
 na wskazywanych wartościach.
swap_ranges
 to po prostu
iter_swap
 powtórzony dla całego zakresu.
Choć historycznie
std::swap
 bywał wymieniany razem z algorytmami z
< algorithm >
, od C++11 formalnie mieszka w
< utility >
. Warto też pamiętać o idiomie "swap via ADL" (argument-dependent lookup) — pisząc generyczny kod wołaj
using std::swap; swap( a, b );
 zamiast wprost
std::swap( a, b )
, żeby pozwolić kompilatorowi wybrać bardziej wyspecjalizowaną, zwykle wydajniejszą wersję
swap
 danej klasy, jeśli taka istnieje (tzw. swap idiom, kluczowy np. przy implementacji silnej gwarancji wyjątkowej).

Generowanie i wypełnianie
algorytm standard opis
fill( f, l, value )
C++98 Wypełnij cały zakres wartością value
fill_n( f, n, value )->it
C++98 Wypełnij n elementów
generate( f, l, gen )
C++98 Wypełnij wynikami kolejnych wywołań gen()
generate_n( f, n, gen )->it
C++98 Jak wyżej, dla n elementów

std::fill / std::fill_n (też w dokumentacji: » Język C++» standard C++» Algorytmyfill funkcja / » Język C++» standard C++» Algorytmyfill_n funkcja)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class ForwardIt, class T > void fill( ForwardIt first, ForwardIt last, const T & value );
template < class OutputIt, class Size, class T > OutputIt fill_n( OutputIt first, Size count, const T & value );
Proste przypisanie tej samej wartości do każdego elementu zakresu (lub pierwszych
count
 elementów).

std::generate / std::generate_n
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class ForwardIt, class Generator > void generate( ForwardIt first, ForwardIt last, Generator g );
template < class OutputIt, class Size, class Generator > OutputIt generate_n( OutputIt first, Size count, Generator g );
g
jest wołane BEZ argumentów dla każdej pozycji (w odróżnieniu od
transform
, gdzie funkcja dostaje elementy wejściowe) — typowe zastosowanie to generator liczb losowych albo licznik ze stanem w lambdzie (np.
int n = 0; generate( v.begin(), v.end(),[ & ] { return n++; } );
, choć do tego konkretnego przypadku lepiej pasuje
std::iota
 z
< numeric >
, patrz niżej).
Od C++26 dochodzi wyspecjalizowany
std::ranges::generate_random( zakres, generator )
 (nagłówek
< random >
) — wypełnia zakres liczbami losowymi bez ręcznego pisania lambdy wołającej generator; implementacja może to zoptymalizować (np. wektoryzacja/generowanie "hurtowe"), czego nie gwarantuje ręczne
generate
 z lambdą.

Transformacja i zastępowanie
algorytm standard opis
transform( f, l, dst, g1 )->it
C++98 Przekształć elementy funkcją g1 do dst
transform( f1, l1, f2, dst, g2 )->it
C++98 Jak wyżej, ale g2 bierze po jednym elemencie z dwóch zakresów
replace( f, l, old, new )
C++98 Zamień wszystkie old na new
replace_if( f, l, p1, new )
C++98 Zamień spełniające p1 na new
replace_copy( f, l, dst, old, new )->it
C++98 Jak replace, wynik w dst
replace_copy_if( f, l, dst, p1, new )->it
C++98 Jak replace_if, wynik w dst

std::transform
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt, class UnaryOp >
OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first, UnaryOp unary_op );

template < class InputIt1, class InputIt2, class OutputIt, class BinaryOp >
OutputIt transform( InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOp binary_op );
Wersja jednoargumentowa przekształca każdy element zakresu funkcją
unary_op
; wersja dwuargumentowa bierze po jednym elemencie z dwóch zakresów naraz (np. do dodawania dwóch wektorów element po elemencie).
d_first
 może być równe
first1
 (transformacja w miejscu) — to bezpieczne i częste.
To najbardziej ogólny z algorytmów modyfikujących —
replace
,
fill
 czy nawet
copy
 dałoby się (mniej czytelnie) zapisać przez
transform
 z odpowiednią lambdą. Warto jednak używać najbardziej wyspecjalizowanego algorytmu pasującego do zadania — to lepiej oddaje intencję (patrz argumenty na początku artykułu) i bywa, że wyspecjalizowana wersja ma dodatkowe optymalizacje (np.
fill
 dla typów trywialnych może zejść do
memset
).

std::replace / std::replace_if (też w dokumentacji: » Język C++» standard C++» Algorytmyreplace funkcja / » Język C++» standard C++» Algorytmyreplace_if funkcja)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class ForwardIt, class T >
void replace( ForwardIt first, ForwardIt last, const T & old_value, const T & new_value );

template < class ForwardIt, class UnaryPred, class T >
void replace_if( ForwardIt first, ForwardIt last, UnaryPred p, const T & new_value );
Modyfikują zakres w miejscu — każde wystąpienie
old_value
 (lub każdy element spełniający
p
) zostaje nadpisane przez
new_value
.

std::replace_copy / std::replace_copy_if
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt, class T >
OutputIt replace_copy( InputIt first, InputIt last, OutputIt d_first, const T & old_value, const T & new_value );

template < class InputIt, class OutputIt, class UnaryPred, class T >
OutputIt replace_copy_if( InputIt first, InputIt last, OutputIt d_first, UnaryPred p, const T & new_value );
Nieniszczące odpowiedniki powyższych — źródło zostaje bez zmian, wynik (z podmienionymi wartościami) trafia do
d_first
. To ten sam wzorzec "wersja w miejscu / wersja _copy", który zobaczysz jeszcze przy
remove
 i
unique
 poniżej.

Usuwanie i unikalność
algorytm standard opis
remove( f, l, value )->it
C++98 Przesuń elementy różne od value na początek — WYMAGA erase!
remove_if( f, l, p1 )->it
C++98 Jak wyżej, dla predykatu
remove_copy( f, l, dst, value )->it
C++98 Skopiuj elementy różne od value (nie modyfikuje źródła)
remove_copy_if( f, l, dst, p1 )->it
C++98 Jak wyżej, dla predykatu
unique( f, l, c2 ? )->it
C++98 Usuń SĄSIADUJĄCE duplikaty — WYMAGA erase i (zwykle) wcześniejszego sortowania!
unique_copy( f, l, dst, c2 ? )->it
C++98 Jak wyżej, nie modyfikuje źródła

std::remove / std::remove_if
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class ForwardIt, class T >
ForwardIt remove( ForwardIt first, ForwardIt last, const T & value );

template < class ForwardIt, class UnaryPred >
ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPred p );
To prawdopodobnie najbardziej mylący algorytm w całej bibliotece dla początkujących: NIE usuwa niczego z kontenera i nie zmienia jego rozmiaru!
remove
 przesuwa elementy różne od
value
 na początek zakresu (zachowując ich względną kolejność) i zwraca iterator na nowy "koniec" — reszta zakresu (między nowym końcem a starym
last
) zawiera elementy w nieokreślonym stanie (część to "usunięte" wartości, część to duplikaty przesuniętych). Dlatego niemal zawsze widuje się to w parze z metodą
erase
 kontenera — tzw. idiom erase-remove:
v.erase( std::remove( v.begin(), v.end(), 42 ), v.end() );

Dopiero
erase
 faktycznie zmniejsza kontener. Sam
remove
 bez następującego
erase
 to niemal zawsze błąd logiczny w kodzie (kompiluje się, ale nie robi tego, czego autor prawdopodobnie chciał).

std::remove_copy / std::remove_copy_if
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt, class T >
OutputIt remove_copy( InputIt first, InputIt last, OutputIt d_first, const T & value );

template < class InputIt, class OutputIt, class UnaryPred >
OutputIt remove_copy_if( InputIt first, InputIt last, OutputIt d_first, UnaryPred p );
Nieniszczący wariant — źródło zostaje bez zmian, do
d_first
 trafiają tylko elementy, które PRZESZŁYBY przez
remove
 (czyli różne od
value
 / niespełniające
p
). Tu nie ma pułapki z poprzedniego punktu —
d_first
 to zwykły iterator wyjściowy, więc "usuwanie" jest tu dosłowne (po prostu nie kopiujemy niechcianych elementów).

std::unique / std::unique_copy
Złożoność: O(N) (ściśle: Θ(N) — dokładnie N−1 porównań)
C/C++
template < class ForwardIt > ForwardIt unique( ForwardIt first, ForwardIt last );
template < class ForwardIt, class BinaryPred > ForwardIt unique( ForwardIt first, ForwardIt last, BinaryPred p );

template < class InputIt, class OutputIt > OutputIt unique_copy( InputIt first, InputIt last, OutputIt d_first );
template < class InputIt, class OutputIt, class BinaryPred > OutputIt unique_copy( InputIt first, InputIt last, OutputIt d_first, BinaryPred p );
Usuwa (a precyzyjniej — tak jak
remove
, tylko PRZESUWA i zwraca nowy koniec, więc znów potrzeba
erase
) kolejne, SĄSIADUJĄCE ze sobą duplikaty, zostawiając pierwszy element z każdej serii.
unique
 usuwa tylko sąsiadujące duplikaty —
{ 1, 2, 1, 1, 2 }
 da w wyniku
{ 1, 2, 1, 2 }
 (jedynka i dwójka z początku i końca NIE są sąsiadujące, więc zostają obie). Jeśli zamiarem jest "zostaw tylko unikalne wartości w całym zakresie", zakres trzeba NAJPIERW posortować (
std::sort
) — dopiero wtedy wszystkie duplikaty danej wartości sąsiadują ze sobą. Stąd klasyczny idiom "usuń duplikaty z wektora":
C/C++
std::sort( v.begin(), v.end() );
v.erase( std::unique( v.begin(), v.end() ), v.end() );
Wersja oparta na zakresach ma tu dodatkową pułapkę odwrotną:
std::ranges::unique
 zwraca nie iterator, a
subrange
 (patrz też ograniczenia ranges w dalszej części artykułu), więc idiom trzeba zapisać jako
v.erase( std::ranges::unique( v ).begin(), v.end() );
 albo przez nazwaną zmienną:
auto to_erase = std::ranges::unique( v ); v.erase( to_erase.begin(), v.end() );

Zmiana kolejności
algorytm standard opis
reverse( f, l )
C++98 Odwróć kolejność elementów w miejscu
reverse_copy( f, l, dst )->it
C++98 Jak wyżej, wynik w dst
rotate( f, n_first, l )->it
C++98 Obróć — n_first staje się nowym początkiem
rotate_copy( f, n_first, l, dst )->it
C++98 Jak wyżej, wynik w dst
shift_left( f, l, n )->it
C++20 Przesuń elementy o n w lewo (zwalniając miejsce na końcu)
shift_right( f, l, n )->it
C++20 Przesuń elementy o n w prawo (zwalniając miejsce na początku)
shuffle( f, l, urng )
C++11 Losowe przetasowanie z generatorem liczb losowych

std::reverse / std::reverse_copy (też w dokumentacji: » Język C++» standard C++» Algorytmyreverse funkcja / » Język C++» standard C++» Algorytmyreverse_copy funkcja)
Złożoność: O(N) (ściśle: Θ(N) — dokładnie N/2 zamian)
C/C++
template < class BidirIt > void reverse( BidirIt first, BidirIt last );
template < class BidirIt, class OutputIt > OutputIt reverse_copy( BidirIt first, BidirIt last, OutputIt d_first );
reverse
odwraca kolejność elementów w miejscu — zamienia
* first
 z
*( last - 1 )
, potem
*( first + 1 )
 z
*( last - 2 )
, i tak dalej, aż pozycje się miną lub zrównają w środku zakresu (stąd wymóg
BidirectionalIterator
 — trzeba umieć iść też "do tyłu").
reverse_copy
 nie rusza źródła — zapisuje odwróconą kolejność elementów pod
d_first
 i, jak większość algorytmów z przyrostkiem
_copy
, zwraca iterator na koniec zapisanego (odwróconego) zakresu w miejscu docelowym, czyli
d_first +( last - first )
.

std::rotate / std::rotate_copy
Złożoność: O(N) (ściśle: Θ(N) — od C++11 gwarantowane dokładnie N zamian)
C/C++
template < class ForwardIt > ForwardIt rotate( ForwardIt first, ForwardIt n_first, ForwardIt last );
template < class ForwardIt, class OutputIt >
OutputIt rotate_copy( ForwardIt first, ForwardIt n_first, ForwardIt last, OutputIt d_first );
Element wskazywany przez
n_first
 staje się nowym pierwszym elementem zakresu, dotychczasowy
first .. n_first - 1
 "zawija się" na koniec. Zwraca iterator odpowiadający pozycji, na którą trafił dawny
first
 (czyli
first +( last - n_first )
). Działa już na zwykłym
ForwardIterator
 (nie potrzeba nawet iteratora dwukierunkowego) — od C++11 gwarantowane jest dokładnie N zamian.

std::shift_left / std::shift_right (C++20)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class ForwardIt >
ForwardIt shift_left( ForwardIt first, ForwardIt last,
typename std::iterator_traits < ForwardIt >::difference_type n );

template < class ForwardIt >
ForwardIt shift_right( ForwardIt first, ForwardIt last,
typename std::iterator_traits < ForwardIt >::difference_type n );
Przesuwają elementy o
n
 pozycji (odpowiednio w lewo/w prawo) za pomocą move-przypisań — miejsce zwolnione po przesunięciu zawiera elementy w nieokreślonym (ale poprawnym) stanie. Gdy
n <= 0
 lub
n >= last - first
, zakres pozostaje bez zmian; w pozostałych przypadkach
shift_left
 zwraca
first + n
 (nowy koniec "przesuniętej" treści), a
shift_right
 —
first + n
 jako nowy początek.
To w istocie "rotate bez zawijania" — o ile
rotate
 przenosi wypchnięte elementy na drugi koniec,
shift_left
/
shift_right
po prostu je porzuca (zostawiając nieokreśloną wartość na zwolnionym miejscu). Dodane w C++20 głównie z myślą o algorytmach operujących na "oknach" danych oraz o przenoszalnych, ale niekopiowalnych typach, gdzie
rotate
 bywał niepotrzebnie kosztowny.

std::shuffle (C++11)
Złożoność: O(N) (ściśle: Θ(N)Fisher-Yates zawsze wykonuje dokładnie N−1 zamian)
C/C++
template < class RandomIt, class URBG >
void shuffle( RandomIt first, RandomIt last, URBG && g );
Losowo tasuje zakres, korzystając z przekazanego generatora liczb losowych spełniającego wymogi
UniformRandomBitGenerator
 (np.
std::mt19937
). Zastąpił usunięty w C++17
std::random_shuffle
 (który albo korzystał z globalnego, słabej jakości generatora, albo z domyślnie skonstruowanego — trudno było o powtarzalność i dobrą jakość losowości). Wymaga
RandomAccessIterator
 (algorytm Fishera-Yatesa potrzebuje swobodnego dostępu).

Próbkowanie
std::sample (C++17)
Złożoność: O(N)
C/C++
template < class PopulationIt, class SampleIt, class Distance, class URBG >
SampleIt sample( PopulationIt first, PopulationIt last, SampleIt out, Distance n, URBG && g );
Losuje
n
 elementów bez powtórzeń z zakresu od
first
 do
last
 (a dokładniej:
min( n, last - first )
, gdy zakres jest krótszy niż
n
) i zapisuje je do
out
, zachowując ich względną kolejność z zakresu źródłowego. Różni się od
shuffle
 tym, że nie tasuje całego zakresu, tylko wybiera podzbiór — klasyczne zastosowanie: reprezentatywna próbka z dużego zbioru danych bez konieczności jego kopiowania/tasowania w całości. Dla iteratorów wejściowych innych niż
RandomAccessIterator
 używany jest algorytm zbiornikowy (reservoir sampling) — dzięki temu działa nawet, gdy nie znamy z góry rozmiaru populacji (np. strumień danych).
PopulationIt
 wymaga jedynie
InputIterator
 — to najluźniejsza z kategorii, celowo, żeby
sample
 dało się użyć nawet na jednorazowym, jednokierunkowym strumieniu danych. Ta swoboda ma jednak cenę: gdy
PopulationIt
 NIE jest przynajmniej
ForwardIterator
 (czyli zakres źródłowy da się przejść tylko raz), standard wymaga dodatkowo, by
SampleIt
 (iterator wyjściowy) był
RandomAccessIterator
 — dopiero to pozwala algorytmowi zbiornikowemu nadpisywać już wybrane próbki pod losowymi indeksami w miejscu docelowym. Gdy
PopulationIt
 jest przynajmniej
ForwardIterator
 (zakres można przejść wielokrotnie),
SampleIt
 wystarczy zwykły
OutputIterator
 — implementacja może wtedy przejść zakres dwukrotnie (raz, by poznać jego rozmiar, drugi raz, by wybrać elementy) i zapisywać wynik sekwencyjnie. Nazwy
PopulationIt
/
SampleIt
są zgodne z nazewnictwem samego standardu (tam pełne
PopulationIterator
/
SampleIterator
) — w odróżnieniu od
ForwardIt
/
InputIt
gdzie nazwa od razu mówi o minimalnej kategorii, tutaj po prostu trzeba to wiedzieć (stąd ten akapit zamiast zmiany nazwy).

Permutacje
Permutacja zakresu to dowolne inne ustawienie (przestawienie) tych samych elementów w innej kolejności — zakres N-elementowy ma dokładnie N! permutacji. Permutacje można uporządkować liniowo leksykograficznie, czyli tak, jak porządkuje się słowa w słowniku: porównując kolejne elementy, aż do pierwszej różnicy (dokładnie to samo porównanie, którego formalnie dokonuje
lexicographical_compare
, opisany w dalszej części artykułu). Poniższe trzy algorytmy operują właśnie na tym liniowym porządku.
algorytm standard opis
next_permutation( f, l, c2 ? )->bool
C++98 Przekształć w następną permutację leksykograficzną
prev_permutation( f, l, c2 ? )->bool
C++98 Jak wyżej, permutacja poprzednia
is_permutation( f1, l1, f2, c2 ? )->bool
C++11 Czy zakres 1 jest permutacją zakresu 2

std::next_permutation / std::prev_permutation
Złożoność: O(N) w najgorszym przypadku (Θ(N) — np. przejście z ostatniej permutacji na pierwszą wymaga odwrócenia całego zakresu), Θ(1) w najlepszym (wystarczy zamienić dwa ostatnie elementy)
C/C++
template < class BidirIt > bool next_permutation( BidirIt first, BidirIt last );
template < class BidirIt, class Compare > bool next_permutation( BidirIt first, BidirIt last, Compare comp );

template < class BidirIt > bool prev_permutation( BidirIt first, BidirIt last );
template < class BidirIt, class Compare > bool prev_permutation( BidirIt first, BidirIt last, Compare comp );
Przekształcają zakres w miejsce w kolejną (lub poprzednią) permutację w porządku leksykograficznym i zwracają
true
 — a gdy podany zakres jest już permutacją ostatnią (odpowiednio pierwszą), "zawijają" go do permutacji pierwszej (ostatniej) i zwracają
false
. Dzięki temu klasyczna pętla "wypisz wszystkie permutacje" wygląda tak:
C/C++
std::sort( v.begin(), v.end() ); // start od permutacji najmniejszej leksykograficznie
do {
   
// ... użyj v ...
} while( std::next_permutation( v.begin(), v.end() ) );
Żeby przejść przez WSZYSTKIE permutacje, zakres musi zaczynać się od najmniejszej leksykograficznie (czyli posortowanej rosnąco) — w przeciwnym razie pętla przejdzie tylko przez "resztę" cyklu, pomijając permutacje leksykograficznie mniejsze od startowej. To częsty błąd przy pisaniu takiego kodu bez wcześniejszego
sort
.

std::is_permutation (C++11)
Złożoność: O(N²) w ogólności, O(N) gdy elementy są haszowalne (zależnie od implementacji i typu — nie precyzuję tu Θ)
C/C++
template < class ForwardIt1, class ForwardIt2 >
bool is_permutation( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2 );

template < class ForwardIt1, class ForwardIt2, class BinaryPred >
bool is_permutation( ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, BinaryPred p );
Sprawdza, czy oba zakresy zawierają te same elementy z tymi samymi krotnościami, niekoniecznie w tej samej kolejności (czyli: czy jeden jest permutacją drugiego). W ogólnym przypadku wymaga to porównań rzędu O(N²) (dla dowolnego
ForwardIterator
 i dowolnego predykatu równości), ale implementacje mogą przyspieszyć dla typów spełniających dodatkowe wymagania.

Porównywanie leksykograficzne
algorytm standard opis
lexicographical_compare( f1, l1, f2, l2, c2 ? )->bool
C++98 Czy zakres 1 jest leksykograficznie mniejszy od zakresu 2
lexicographical_compare_three_way( f1, l1, f2, l2, c2 ? )->auto
C++20 Trójstronne porównanie leksykograficzne (operator <=>)

std::lexicographical_compare
Złożoność: O(min(N1,N2)) w najgorszym przypadku (Θ(min(N1,N2))), Θ(1) w najlepszym
C/C++
template < class InputIt1, class InputIt2 >
bool lexicographical_compare( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );

template < class InputIt1, class InputIt2, class Compare >
bool lexicographical_compare( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Compare comp );
Porównuje dwa zakresy tak, jak porównuje się słowa w słowniku: element po elemencie, aż do pierwszej różnicy (a jeśli jeden zakres jest prefiksem drugiego — krótszy jest "mniejszy"). Zwraca
true
, gdy pierwszy zakres jest leksykograficznie mniejszy od drugiego. To właśnie ten algorytm stoi pod spodem
operator <
 dla
std::vector
,
std::string
 i innych kontenerów sekwencyjnych. Wersja z
comp
 pozwala podać własne kryterium porównania pojedynczych elementów (np. porównanie ignorujące wielkość liter) zamiast domyślnego
operator <
.

std::lexicographical_compare_three_way (C++20)
Złożoność: O(min(N1,N2)) w najgorszym przypadku (Θ(min(N1,N2))), Θ(1) w najlepszym
C/C++
template < class InputIt1, class InputIt2, class Cmp >
auto lexicographical_compare_three_way( InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2, Cmp comp );

template < class InputIt1, class InputIt2 >
auto lexicographical_compare_three_way( InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2 );
// bez comp — jak wyżej, ale z domyślnym std::compare_three_way{} (czyli operatorem <=>)
Odpowiednik powyższego dla operatora trójstronnego porównania
<=>
 (spaceship operator) — zamiast
bool
 zwraca kategorię porządkowania (np.
std::strong_ordering
), jednym przebiegiem rozstrzygając "mniejszy / równy / większy", zamiast — jak dawniej bywało — wołać
lexicographical_compare
 dwukrotnie (raz dla
<
, raz dla
>
) żeby dowiedzieć się wszystkich trzech możliwości.

Algorytmy numeryczne (nagłówek <numeric>)
Osobny nagłówek, ale koncepcyjnie to dalej algorytmy uogólnione — operują na zakresach przez iteratory tak samo, jak wszystko powyżej.

algorytm standard opis
iota( f, l, value )
C++11 Wypełnij rosnącą sekwencją value, value+1, value+2, ...
accumulate( f, l, init, g2 ? )->T
C++98 Fold w GWARANTOWANEJ kolejności lewo-do-prawa
reduce( f, l, init ?, g2 ? )->T
C++17 Jak accumulate, ale kolejność NIEOKREŚLONA (bezpieczne dla par.)
inner_product( f1, l1, f2, init, g2 ?, g2 ? )->T
C++98 Iloczyn skalarny dwóch zakresów
transform_reduce( f1, l1, f2, init, ... )->T
C++17 Map-reduce jednym wywołaniem, kolejność nieokreślona
partial_sum( f, l, dst, g2 ? )->it
C++98 Suma częściowa (prefix-sum) — i-ty = suma(0..i)
adjacent_difference( f, l, dst, g2 ? )->it
C++98 Różnice sąsiednich elementów (odwrotność partial_sum)
inclusive_scan( f, l, dst, g2 ?, init ? )->it
C++17 Jak partial_sum, kolejność nieokreślona (par.)
exclusive_scan( f, l, dst, init, g2 ? )->it
C++17 Jak wyżej, ale i-ty wynik BEZ i-tego elementu
transform_inclusive_scan( ... )->it
C++17 transform + inclusive_scan
transform_exclusive_scan( ... )->it
C++17 transform + exclusive_scan

std::iota (C++11)
Złożoność: O(N) (ściśle: Θ(N))
template < class ForwardIt, class T > void iota( ForwardIt first, ForwardIt last, T value );

Wypełnia zakres kolejnymi wartościami zaczynając od
value
, zwiększając je przez
++value
 po każdym zapisie. Najczęstsze zastosowanie: szybkie wygenerowanie wektora indeksów
{ 0, 1, 2, ..., N - 1 }
.

std::accumulate / std::reduce (C++17)
Złożoność: O(N) (ściśle: Θ(N) — zawsze N kombinacji, bez skrótów)
C/C++
template < class InputIt, class T > T accumulate( InputIt first, InputIt last, T init );
template < class InputIt, class T, class BinaryOp > T accumulate( InputIt first, InputIt last, T init, BinaryOp op );

template < class InputIt, class T > T reduce( InputIt first, InputIt last, T init );
template < class InputIt, class T, class BinaryOp > T reduce( InputIt first, InputIt last, T init, BinaryOp op );
accumulate
to klasyczny fold: domyślnie sumuje (
init = init + * it
dla kolejnych
it
), z
op
 pozwala zastąpić dodawanie dowolną operacją — GWARANTUJE kolejność lewo-do-prawa, więc bezpiecznie użyjesz go np. do konkatenacji stringów albo mnożenia macierzy (gdzie kolejność ma znaczenie).
reduce
 robi to samo co
accumulate
, ale świadomie REZYGNUJE z gwarancji kolejności — elementy mogą być łączone w dowolnej kolejności i dowolnym grupowaniu. To brzmi jak wada, ale jest zamierzoną cechą: dzięki temu
reduce
 może być bezpiecznie zrównoleglony (i ma przeciążenia z execution policy), o ile Twoja operacja
op
 jest łączna i przemienna (dla zwykłego dodawania liczb całkowitych — tak, dla dodawania
float
/
double
— technicznie nie do końca, ze względu na błędy zaokrągleń, choć w praktyce różnice bywają pomijalne). Zasada: gdy kolejność ma znaczenie —
accumulate
; gdy zależy Ci na wydajności przy dużych zakresach i operacja jest łączna —
reduce
.

std::inner_product / std::transform_reduce (C++17)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt1, class InputIt2, class T >
T inner_product( InputIt1 first1, InputIt1 last1, InputIt2 first2, T init );
// + przeciążenie z dwoma operacjami binarnymi (domyślnie: sumowanie i mnożenie)

template < class InputIt1, class InputIt2, class T >
T transform_reduce( InputIt1 first1, InputIt1 last1, InputIt2 first2, T init );
// + przeciążenia z własnymi operacjami redukcji/transformacji, także dla jednego zakresu
inner_product
domyślnie liczy iloczyn skalarny:
init
 + Σ
( first1[ k ] * first2[ k ] )
, z gwarantowaną kolejnością lewo-do-prawa (jak
accumulate
).
transform_reduce
 to jego "nieuporządkowany", równoległo-przyjazny odpowiednik (jak
reduce
 jest dla
accumulate
) — dodatkowo ma wersję jednozakresową, łączącą "zmapuj funkcją, potem zredukuj" w jednym wywołaniu, bez tworzenia tymczasowego zakresu pośredniego.

std::partial_sum / std::adjacent_difference
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt > OutputIt partial_sum( InputIt first, InputIt last, OutputIt d_first );
template < class InputIt, class OutputIt > OutputIt adjacent_difference( InputIt first, InputIt last, OutputIt d_first );
partial_sum
:
k
-ty element wyniku to suma
first[ 0 ] + ... + first[ k ]
 (suma częściowa/prefiksowa; z
BinaryOp
 — dowolna inna operacja zamiast dodawania).
adjacent_difference
 robi w pewnym sensie operację odwrotną:
d_first[ 0 ] = first[ 0 ]
, a dla
k > 0
:
d_first[ k ] = first[ k ] - first[ k - 1 ]
. Zastosowanie
adjacent_difference
 po
partial_sum
 (z tymi samymi operacjami odwrotnymi) odtwarza oryginalny zakres.

std::inclusive_scan / std::exclusive_scan (C++17)
Złożoność: O(N) (ściśle: Θ(N))
C/C++
template < class InputIt, class OutputIt > OutputIt inclusive_scan( InputIt first, InputIt last, OutputIt d_first );

template < class InputIt, class OutputIt, class T >
OutputIt exclusive_scan( InputIt first, InputIt last, OutputIt d_first, T init );
Nieuporządkowane (równoległo-przyjazne) odpowiedniki
partial_sum
 — wymagają operacji łącznej z tego samego powodu, co
reduce
.
Różnica między nimi to dokładnie jeden element przesunięcia, stąd nazwy:
inclusive_scan
 —
k
-ty wynik WŁĄCZA
k
-ty element wejścia (jak
partial_sum
:
d[ k ] = x[ 0 ] + ... + x[ k ]
);
exclusive_scan
 —
k
-ty wynik WYŁĄCZA
k
-ty element (liczy tylko
x[ 0 ] + ... + x[ k - 1 ]
, startując od jawnie podanego
init
). Dlatego
exclusive_scan
 WYMAGA parametru
init
 (musi mieć od czego zacząć sumę pustą), a
inclusive_scan
 ma
init
 opcjonalny (bez niego pierwszy element wyniku to po prostu pierwszy element wejścia). Ten sam podział "inclusive/exclusive" znajdziesz w wielu innych bibliotekach programowania równoległego (np. CUDA Thrust, Intel TBB).

std::transform_inclusive_scan / std::transform_exclusive_scan (C++17)
Złożoność: O(N) (ściśle: Θ(N))
Łączą
transform_reduce
 (zmapuj, potem zredukuj) z ideą skanu prefiksowego: najpierw stosują podaną transformację jednoargumentową do każdego elementu, potem liczą
inclusive_scan
/
exclusive_scan
na wynikach — bez materializowania zakresu pośredniego. Sygnatury są rozbudowane (kilka przeciążeń różniących się kolejnością/obecnością
init
 i operacji), więc po dokładne parametry warto zajrzeć wprost do dokumentacji.

W
< numeric >
 mieszkają też cztery funkcje, które formalnie NIE są algorytmami uogólnionymi w powyższym sensie — działają na pojedynczych wartościach, nie na zakresach:
std::gcd( a, b )
/
std::lcm( a, b )
(największy wspólny dzielnik / najmniejsza wspólna wielokrotność, obie C++17) oraz
std::midpoint( a, b )
/
std::lerp( a, b, t )
(bezpieczny arytmetycznie punkt środkowy i interpolacja liniowa, obie C++20, nagłówek
< numeric >
 dla
midpoint
 i
< cmath >
 dla
lerp
). Warto o nich wiedzieć, bo często pojawiają się w tym samym kontekście, mimo że nie pasują do żadnej z kategorii z tego artykułu.

Funkcje z języka C
std::qsort / std::bsearch
Złożoność: zwykle O(N log N) / O(log N), ale standard tego NIE gwarantuje — stąd żadnego Θ tu nie podaję
C/C++
void qsort( void * ptr, std::size_t count, std::size_t size,
int( * comp )( const void *, const void * ) );

void * bsearch( const void * key, const void * ptr, std::size_t count, std::size_t size,
int( * comp )( const void *, const void * ) );
Odziedziczone wprost z C (nagłówek
< cstdlib >
) — działają na surowej pamięci (
void *
), z ręcznie podanym rozmiarem pojedynczego elementu i wskaźnikiem na funkcję porównującą (zwracającą
int
: ujemny/zero/dodatni, analogicznie do C-owego
strcmp
).
W kodzie C++ praktycznie nie ma powodu po nie sięgać:
std::sort
/
std::binary_search
są w pełni typowo bezpieczne (żadnego rzutowania na
void *
), zwykle szybsze (komparator w postaci lambdy/funktora łatwo się inline'uje, wywołanie przez wskaźnik na funkcję zwykle nie), a dodatkowo standard C++ w ogóle nie gwarantuje żadnej konkretnej złożoności dla
qsort
/
bsearch
(w przeciwieństwie do
std::sort
, gdzie O(N log N) w najgorszym przypadku to formalny wymóg od C++11) — tylko poprawność wyniku przy odpowiednio posortowanych/nieposortowanych danych. Te dwie funkcje mają sens głównie przy interfejsowaniu z istniejącym kodem C.

Scalanie (merge)
algorytm standard opis
merge( f1, l1, f2, l2, dst )->it
C++98 Scal dwa posortowane zakresy do dst (wynik posortowany)
inplace_merge( f, mid, l )
C++98 Scal dwie posortowane połowy TEGO SAMEGO zakresu w miejscu

std::merge / std::inplace_merge (inplace_merge też w dokumentacji: » Język C++» standard C++» Algorytmyinplace_merge szablon funkcji)
Złożoność:
merge
: O(N1+N2) (ściśle: Θ(N1+N2) — trzeba przepisać wszystkie elementy) /
inplace_merge
: O(N log N), O(N) przy wystarczającej dodatkowej pamięci (ściśle: Θ(N) w tym drugim przypadku)
C/C++
template < class InputIt1, class InputIt2, class OutputIt >
OutputIt merge( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first );

template < class BidirIt >
void inplace_merge( BidirIt first, BidirIt middle, BidirIt last );
merge
scala dwa JUŻ posortowane zakresy źródłowe do trzeciego, docelowego miejsca — jest stabilny (przy remisie pierwszeństwo ma element z pierwszego zakresu).
inplace_merge
 robi dokładnie to samo, ale dla dwóch sąsiadujących, posortowanych podzakresów TEGO SAMEGO zakresu (od
first
 do
middle
 oraz od
middle
 do
last
), scalając je w miejscu w jeden posortowany zakres.
To dosłownie krok "merge" algorytmu merge sort — mając funkcję sortującą małe zakresy (albo rekurencyjnie dzieląc zakres na połowy aż do pojedynczych elementów, z definicji posortowanych),
inplace_merge
 pozwala złożyć z tego pełny merge sort bez samodzielnego pisania logiki scalania:
C/C++
void merge_sort( auto first, auto last ) {
   
if( last - first <= 1 ) return;
   
   
auto middle = first +( last - first ) / 2;
   
merge_sort( first, middle );
   
merge_sort( middle, last );
   
std::inplace_merge( first, middle, last );
}

Operacje na pamięci nieinicjalizowanej
W odróżnieniu od scalania (wyżej) — po te algorytmy w typowym kodzie aplikacyjnym raczej nie sięgniesz. To narzędzia dla osób piszących WŁASNE kontenery. Są potrzebne, bo zwykłe
copy
/
fill
zakładają, że pod docelowym adresem już ISTNIEJE żywy obiekt (bo wykonują przypisanie,
operator =
) — użycie ich na surowej, niezainicjalizowanej pamięci (np. świeżo zaalokowanej przez
std::allocator
, zanim jakikolwiek konstruktor tam zadziałał) to niezdefiniowane zachowanie. Algorytmy z tej grupy zamiast przypisania używają placement-new (konstruują obiekt "od zera" pod wskazanym adresem).

algorytm standard opis
uninitialized_copy( f, l, dst )->it
C++98 Kopiuj-konstruuj w surową (niezainicjalizowaną) pamięć
uninitialized_fill( f, l, value )
C++98 Jak wyżej, wypełniając jedną wartością
uninitialized_move( f, l, dst )->it
C++17 Przenieś-konstruuj w surową pamięć
uninitialized_default_construct( f, l )
C++17 Domyślnie skonstruuj w surowej pamięci (bez zerowania skalarów!)
uninitialized_value_construct( f, l )
C++17 Jak wyżej, ale z inicjalizacją wartością (zerowanie skalarów)
destroy( f, l ) / destroy_n / destroy_at
C++17 Wywołaj destruktor bez zwalniania pamięci
construct_at( p, args ... )->T *
C++20 Placement-new pojedynczego obiektu, przyjazny constexpr

Specjalizowane algorytmy z
< memory >

Złożoność: O(N) (ściśle: Θ(N) — poza
construct_at
: O(1), ściśle Θ(1))
C/C++
template < class InputIt, class NoThrowForwardIt >
NoThrowForwardIt uninitialized_copy( InputIt first, InputIt last, NoThrowForwardIt d_first );

template < class ForwardIt > void uninitialized_default_construct( ForwardIt first, ForwardIt last ); // C++17
template < class ForwardIt > void uninitialized_value_construct( ForwardIt first, ForwardIt last ); // C++17
template < class ForwardIt > void destroy( ForwardIt first, ForwardIt last ); // C++17
template < class T, class ... Args > constexpr T * construct_at( T * p, Args && ... args ); // C++20
Grupę dopełniają warianty
_n
 oraz przenoszące
uninitialized_move
/
uninitialized_move_n
(C++17).
destroy *
/
construct_at
pozwalają explicite wywołać destruktor/konstruktor bez zwalniania czy alokowania pamięci — to właśnie tymi cegiełkami zbudowane są od środka
std::vector
 i podobne kontenery.
uninitialized_default_construct
 a
uninitialized_value_construct
 różnią się dokładnie tak, jak różni się
T x;
 od
T x { };
 dla typów skalarnych: pierwsza zostawia niezainicjalizowaną (nieokreśloną) wartość, druga — zeruje. Dla typów klasowych z jawnym konstruktorem różnicy zwykle nie ma (oba wołają ten sam konstruktor domyślny), ale dla
int
,
float
 czy wskaźników różnica jest realna i łatwa do przeoczenia.
construct_at
 (C++20) zyskał na znaczeniu, bo w przeciwieństwie do zwykłego placement-new (który przed C++20 nie mógł być użyty w kontekście
constexpr
) jest przyjazny dla stałowyrażeniowego kodu — to jeden z elementów, które w ogóle umożliwiły powstanie
constexpr
 kontenerów w nowszych standardach. Od C++26 cała ta grupa (łącznie z
uninitialized_copy
/
fill
i pochodnymi) zyskuje pełne wsparcie
constexpr
.

Co nowego dla algorytmów: C++20, C++23, C++26

Powyżej, przy okazji poszczególnych algorytmów, zaznaczałem standard, w którym każdy z nich się pojawił. Poniżej krótkie, celowo zwięzłe zestawienie nowości z ostatnich trzech standardów, które NIE mają swojego klasycznego odpowiednika opisanego wyżej (bo są dostępne tylko jako
std::ranges::
/
std::views::
) albo dotyczą wszystkich algorytmów naraz. Pełny, dogłębny opis mechanizmu zakresów (w tym wszystkich nowych widoków/views) zostawiam oddzielnemu artykułowi — tu tylko tyle, ile potrzeba, by zorientować się "co nowego" pod kątem samych algorytmów.

C++20
Największa zmiana to oczywiście całe
std::ranges::
/
std::views::
(zakresowe wersje niemal wszystkich algorytmów z tego artykułu, wraz z projekcjami — możliwością posortowania np. po polu struktury bez pisania lambdy) — opisane w artykule o zakresach. Z rzeczy klasycznych (nie-zakresowych), o których już była mowa wyżej:
shift_left
/
shift_right
,
lexicographical_compare_three_way
 oraz polityka wykonania
std::execution::unseq
.

C++23
  • std::ranges::contains
    /
    contains_subrange
     — czy zakres zawiera daną wartość / podzakres (bez klasycznego odpowiednika — najbliżej klasyce jest
    std::find
     + porównanie z
    last
    ).
  • std::ranges::starts_with
    /
    ends_with
     — czy zakres zaczyna/kończy się danym podzakresem.
  • std::ranges::fold_left
    /
    fold_left_first
    /
    fold_right
    /
    fold_right_last
    — zakresowy odpowiednik
    accumulate
     (z gwarantowaną kolejnością — brak "nieuporządkowanego" odpowiednika
    reduce
     w wersji zakresowej).
  • std::ranges::find_last
    /
    find_last_if
     /
    find_last_if_not
     — odpowiedniki
    find
    /
    find_if
    /
    find_if_not
    szukające OSTATNIEGO, nie pierwszego, dopasowania (bez klasycznego odpowiednika, poza ręcznym użyciem odwróconych iteratorów).
  • std::ranges::to < Container >()
    — konwersja dowolnego zakresu (w tym "leniwego" widoku) na konkretny kontener, np.
    auto v = r | std::views::filter( pred ) | std::ranges::to < std::vector >();
  • Nowe widoki — m.in.
    views::zip
    ,
    views::adjacent
    ,
    views::chunk
    ,
    views::slide
    ,
    views::join_with
    ,
    views::stride
    ,
    views::cartesian_product
     — szczegóły w artykule o zakresach.

C++26
  • Równoległe algorytmy zakresowe  — dotąd (C++17) polityki wykonania (
    std::execution::par
    i pozostałe, patrz wyżej) działały tylko na algorytmach klasycznych,
    std::ranges::
     (od C++20) ich nie miało. C++26 to nadrabia — algorytmy zakresowe też przyjmują teraz politykę wykonania jako pierwszy argument, np.
    std::ranges::sort( std::execution::par, v );
    . Wszystkie zastrzeżenia z sekcji o pułapkach execution policy (wyżej) dotyczą tego również.
  • std::views::concat
    — łączy dowolną liczbę zakresów (niekoniecznie tego samego typu) w jeden widok, np.
    views::concat( v1, v2, v3 )
     iteruje kolejno po wszystkich trzech bez fizycznego kopiowania czegokolwiek do nowego kontenera.
  • std::views::indices
    views::indices( n )
     generuje
    { 0, 1, ..., n - 1 }
     z typem elementu dopasowanym do typu
    n
    . Rozwiązuje irytujący problem
    views::iota( 0, r.size() )
    , które nie kompiluje się wprost (bo
    int
     i
    size_t
     to różne typy) — teraz wystarczy
    views::indices( r.size() )
    .
  • std::views::cache_latest
    — buforuje ostatnio wyliczoną wartość widoku, żeby przy łańcuchach typu
    filter | transform
     nie przeliczać tej samej transformacji za każdym razem, gdy jest ona odczytywana więcej niż raz na jedną iterację (typowy koszt "leniwych" widoków, o którym jest mowa w artykule o zakresach).
  • std::views::as_input
    — wymusza potraktowanie dowolnego zakresu jako
    input_range
     (jednorazowego, jednokierunkowego), co bywa potrzebne, gdy jakiś algorytm/adapter celowo ma działać na najmniej wymagającej kategorii zakresu.
  • std::ranges::generate_random
    (nagłówek
    < random >
    ) — opisane już przy
    generate
     wyżej.
  • std::ranges::reserve_hint
    /
    approximately_sized_range
     — dla zakresów, których dokładnego rozmiaru nie da się tanio policzyć (np. niektóre widoki), ale da się podać jego przybliżenie — przydatne np. przy
    reserve()
     na docelowym kontenerze przed
    ranges::to
    .
  • Constexpr
    stable_sort
     — dołącza do
    sort
    , który constexpr był już wcześniej; podobnie cała grupa specjalizowanych algorytmów z
    < memory >
     (opisana wyżej) staje się w pełni constexpr.
  • Inicjalizacja listą dla algorytmów — drobna, ale wygodna zmiana: więcej miejsc pozwala teraz podać literał w nawiasach klamrowych tam, gdzie wcześniej trzeba było jawnie podawać typ.
Wszystkie powyższe C++26 to na dzień pisania tego akapitu (2026 r.) świeżutkie nowości — żaden mainstreamowy kompilator nie ma jeszcze pełnego wsparcia dla wszystkich z nich naraz. Zanim zaczniesz z nich korzystać produkcyjnie, sprawdź tabelę wsparcia kompilatorów na cppreference.

Dodatek: usunięte algorytmy

Biblioteka standardowa bardzo dba o kompatybilność wsteczną — usunięcia zdarzają się rzadko. Z całego
< algorithm >
/
< numeric >
do tej pory usunięto tak naprawdę jeden algorytm:
std::random_shuffle
 — oznaczony jako przestarzały (deprecated) w C++14, usunięty w C++17. Powód usunięcia: jego wersja bez jawnie podanego generatora opierała się zwykle na
std::rand
 i globalnym stanie losowości słabej jakości — trudno było o powtarzalność i dobrą jakość losowania. Zastępuje go opisany wyżej
std::shuffle
, wymagający jawnego podania generatora spełniającego
UniformRandomBitGenerator
.
W tej samej fali porządków (deprecation w C++11, usunięcie w C++17) zniknęły też pokrewne, choć formalnie nie algorytmiczne, pomocnicze obiekty funkcyjne z
< functional >
, którymi dawniej owijało się funkcje przekazywane do algorytmów:
std::ptr_fun
,
std::mem_fun
/
std::mem_fun_ref
,
std::bind1st
/
std::bind2nd
oraz bazowe klasy
std::unary_function
/
std::binary_function
. Lambdy (a od C++11 też
std::bind
) całkowicie je zastąpiły — patrz uwaga o
ptr_fun
 przy
for_each
 na początku artykułu.

Podsumowanie

Po przeczytaniu tego artykułu masz w ręku kompletną ściągę po algorytmach standardowych C++ — z tabelkami cheatsheet, do których możesz wracać bez czytania całości od nowa. Razem to 105 algorytmów (nie licząc wariantów
std::ranges::
, które doczekały się osobnego artykułu) — od
for_each
 z C++98 po
generate_random
 z C++26.

Ważniejsza od samej listy jest jednak umiejętność, którą przy okazji nabyłeś: wiesz już, jak czytać taką dokumentację i jak się uczyć kolejnych algorytmów samodzielnie, na co zwracać uwagę. Znasz kategorie (niemodyfikujące, podział danych, sortowanie, operacje na zbiorach...), wiesz, co mówi sygnatura, złożoność i wymagana kategoria iteratora, i wiesz, gdzie sprawdzić resztę — cppreference.com aktualizuje się wraz z każdym nowym standardem, więc gdy pojawi się C++29 (czy jak go tam ostatecznie nazwą), ten sam sposób czytania dokumentacji zadziała bez żadnych zmian.

Warto też pamiętać, że biblioteka standardowa to nie koniec świata algorytmów w C++. Boost.Algorithm to swego rodzaju "poczekalnia" do standardu — sporo algorytmów, które nigdy (albo jeszcze) nie trafiły do
< algorithm >
, a bywają przydatne; podobnie jak wiele innych bibliotek Boost w przeszłości, część z nich może z czasem stać się podstawą kolejnych propozycji do standardu. Osobną, wartą uwagi kategorią są algorytmy tekstowe — o pracy z tekstem przy użyciu dostępnych narzędzi w C++ napisałem osobny artykuł.

Autor artykułu

Artykuł powstał we IX 2026, jest aktualny ze standardem C++26.
Autor Zakres zmian Afiliacja
Bazior Grzegorz Utworzenie artykułu Pracownik AGH w Krakowie