Last modified: September 25, 2024

This article is written in: 🇵🇱

STL (Standard Template Library)

STL to biblioteka szablonów w języku C++, która dostarcza gotowych do użycia implementacji wielu funkcji, algorytmów i struktur danych. Najważniejsze komponenty biblioteki STL to:

Kolekcje w STL

Kolekcje w STL to zbiory implementacji struktur danych wraz z funkcjami operującymi na tych strukturach.

STL (Standard Template Library)

STL to biblioteka szablonów w języku C++, która dostarcza gotowych do użycia implementacji wielu funkcji, algorytmów i struktur danych. Najważniejsze komponenty biblioteki STL to:

Dodanie kolumny "Złożoność czasowa" do każdej tabeli może pomóc lepiej zrozumieć wydajność różnych operacji. Oto zaktualizowane tabele:

Kolekcje w STL

Kolekcje w STL to zbiory implementacji struktur danych wraz z funkcjami operującymi na tych strukturach.

vector

vector to dynamiczna tablica, która może zmieniać swój rozmiar. Jest jednym z najczęściej używanych kontenerów w STL.

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
push_back(value) Dodaje element na końcu vec.push_back(10); Amortyzowane O(1)
pop_back() Usuwa element z końca vec.pop_back(); O(1)
size() Zwraca liczbę elementów auto s = vec.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (vec.empty()) {} O(1)
clear() Usuwa wszystkie elementy vec.clear(); O(n)
insert(pos, value) Wstawia element na podanej pozycji vec.insert(vec.begin() + 1, 20); O(n)
erase(pos) Usuwa element z podanej pozycji vec.erase(vec.begin() + 1); O(n)
at(index) Zwraca referencję do elementu na podanym indeksie int val = vec.at(2); O(1)
operator[] Zwraca referencję do elementu na podanym indeksie int val = vec[2]; O(1)
front() Zwraca referencję do pierwszego elementu int val = vec.front(); O(1)
back() Zwraca referencję do ostatniego elementu int val = vec.back(); O(1)
begin() Zwraca iterator na początek auto it = vec.begin(); O(1)
end() Zwraca iterator na koniec auto it = vec.end(); O(1)
reserve(n) Rezerwuje miejsce na n elementów vec.reserve(100); O(n)
resize(n) Zmienia rozmiar wektora vec.resize(10); O(n)

Przykład:

std::vector<int> vec = {1, 2, 3};
vec.push_back(4);
vec.insert(vec.begin() + 1, 10);
for(auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}

list

list to dwukierunkowa lista wiązana, która umożliwia szybkie wstawianie i usuwanie elementów.

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
push_back(value) Dodaje element na końcu lst.push_back(10); O(1)
push_front(value) Dodaje element na początku lst.push_front(5); O(1)
pop_back() Usuwa element z końca lst.pop_back(); O(1)
pop_front() Usuwa element z początku lst.pop_front(); O(1)
size() Zwraca liczbę elementów auto s = lst.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (lst.empty()) {} O(1)
clear() Usuwa wszystkie elementy lst.clear(); O(n)
insert(pos, value) Wstawia element na podanej pozycji lst.insert(++lst.begin(), 20); O(1)
erase(pos) Usuwa element z podanej pozycji lst.erase(++lst.begin()); O(1)
front() Zwraca referencję do pierwszego elementu int val = lst.front(); O(1)
back() Zwraca referencję do ostatniego elementu int val = lst.back(); O(1)
begin() Zwraca iterator na początek auto it = lst.begin(); O(1)
end() Zwraca iterator na koniec auto it = lst.end(); O(1)

Przykład:

std::list<int> lst = {1, 2, 3};
lst.push_back(4);
lst.push_front(0);
lst.insert(++lst.begin(), 10);
for(auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}

map

map to kontener asocjacyjny, który przechowuje pary klucz-wartość w uporządkowanej formie, wykorzystując drzewo czerwono-czarne.

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
insert({key, value}) Wstawia parę klucz-wartość mp.insert({1, "one"}); O(log n)
erase(key) Usuwa element o podanym kluczu mp.erase(1); O(log n)
find(key) Zwraca iterator na element o podanym kluczu auto it = mp.find(1); O(log n)
size() Zwraca liczbę elementów auto s = mp.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (mp.empty()) {} O(1)
clear() Usuwa wszystkie elementy mp.clear(); O(n)
operator[] Zwraca referencję do wartości o podanym kluczu std::string val = mp[1]; O(log n)
begin() Zwraca iterator na początek auto it = mp.begin(); O(1)
end() Zwraca iterator na koniec auto it = mp.end(); O(1)

Przykład:

std::map<int, std::string=""> mp;
mp.insert({1, "one"});
mp[2] = "two";
mp.erase(1);
for(auto it = mp.begin(); it != mp.end(); ++it) {
std::cout << it->first << ": " << it->second << " ";
}

unordered_map

unordered_map to kontener asocjacyjny, który przechowuje pary klucz-wartość w nieuporządkowanej formie, wykorzystując tablicę mieszającą (hash table).

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
insert({key, value}) Wstawia parę klucz-wartość ump.insert({1, "one"}); O(1) amortyzowane
erase(key) Usuwa element o podanym kluczu ump.erase(1); O(1) amortyzowane
find(key) Zwraca iterator na element o podanym kluczu auto it = ump.find(1); O(1) amortyzowane
size() Zwraca liczbę elementów auto s = ump.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (ump.empty()) {} O(1)
clear() Usuwa wszystkie elementy ump.clear(); O(n)
operator[] Zwraca referencję do wartości o podanym kluczu std::string val = ump[1]; O(1) amortyzowane
begin() Zwraca iterator na początek auto it = ump.begin(); O(1)
end() Zwraca iterator na koniec auto it = ump.end(); O(1)

Przykład:

std::unordered_map<int, std::string=""> ump;
ump.insert({1, "one"});
ump[2] = "two";
ump.erase(1);
for(auto it = ump.begin(); it != ump.end(); ++it) {
std::cout << it->first << ": " << it->second << " ";
}

set

set to kontener, który przechowuje unikalne elementy w uporządkowanej formie, wykorzystując drzewo czerwono-czarne.

Operacje:

Oto poprawiona tabela:

Metoda Opis Przykład użycia Złożoność czasowa
insert(value) Wstawia element st.insert(10); O(log n)
erase(value) Usuwa element st.erase(10); O(log n)
find(value) Zwraca iterator na element auto it = st.find(10); O(log n)
size() Zwraca liczbę elementów auto s = st.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (st.empty()) {} O(1)
clear() Usuwa wszystkie elementy st.clear(); O(n)
begin() Zwraca iterator na początek auto it = st.begin(); O(1)
end() Zwraca iterator na koniec auto it = st.end(); O(1)

Przykład:

std::set<int> st;
st.insert(10);
st.insert(20);
st.erase(10);
for(auto it = st.begin(); it != st.end(); ++it) {
std::cout << *it << " ";
}

unordered_set

unordered_set to kontener, który przechowuje unikalne elementy w nieuporządkowanej formie, wykorzystując tablicę mieszającą (hash table).

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
insert(value) Wstawia element ust.insert(10); O(1) amortyzowane
erase(value) Usuwa element ust.erase(10); O(1) amortyzowane
find(value) Zwraca iterator na element auto it = ust.find(10); O(1) amortyzowane
size() Zwraca liczbę elementów auto s = ust.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (ust.empty()) {} O(1)
clear() Usuwa wszystkie elementy ust.clear(); O(n)
begin() Zwraca iterator na początek auto it = ust.begin(); O(1)
end() Zwraca iterator na koniec auto it = ust.end(); O(1)

Przykład:

std::unordered_set<int> ust;
ust.insert(10);
ust.insert(20);
ust.erase(10);
for(auto it = ust.begin(); it != ust.end(); ++it) {
std::cout << *it << " ";
}

priority_queue

priority_queue to kontener, który implementuje kopiec binarny i pozwala na szybkie wyciąganie największego (lub najmniejszego) elementu.

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
push(value) Dodaje element pq.push(10); O(log n)
pop() Usuwa największy element pq.pop(); O(log n)
top() Zwraca referencję do największego elementu int val = pq.top(); O(1)
size() Zwraca liczbę elementów auto s = pq.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (pq.empty()) {} O(1)

Przykład:

std::priority_queue<int> pq;
pq.push(10);
pq.push(20);
pq.push(5);
std::cout << pq.top() << " "; // wyświetli 20
pq.pop();
std::cout << pq.top() << " "; // wyświetli 10

queue

queue to kontener, który implementuje kolejkę FIFO (First In, First Out).

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
push(value) Dodaje element q.push(10); O(1)
pop() Usuwa element z początku q.pop(); O(1)
front() Zwraca referencję do pierwszego elementu int val = q.front(); O(1)
back() Zwraca referencję do ostatniego elementu int val = q.back(); O(1)
size() Zwraca liczbę elementów auto s = q.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (q.empty()) {} O(1)

Przykład:

std::queue<int> q;
q.push(10);
q.push(20);
std::cout << q.front() << " "; // wyświetli 10
q.pop();
std::cout << q.front() << " "; // wyświetli 20

stack

stack to kontener, który implementuje stos LIFO (Last In, First Out).

Operacje:

Metoda Opis Przykład użycia Złożoność czasowa
push(value) Dodaje element st.push(10); O(1)
pop() Usuwa element z końca st.pop(); O(1)
top() Zwraca referencję do ostatniego elementu int val = st.top(); O(1)
size() Zwraca liczbę elementów auto s = st.size(); O(1)
empty() Sprawdza, czy kontener jest pusty if (st.empty()) {} O(1)

Przykład:

std::stack<int> st;
st.push(10);
st.push(20);
std::cout << st.top() << " "; // wyświetli 20
st.pop();
std::cout << st.top() << " "; // wyświetli 10

Iteratory

Iteratory w języku C++ to obiekty umożliwiające sekwencyjny dostęp do elementów kontenerów. Dzięki nim możliwe jest jednolite przeglądanie zawartości różnych typów kolekcji, niezależnie od ich wewnętrznej implementacji. Iteratory pełnią podobną rolę do wskaźników, ale są bardziej elastyczne i bezpieczne w użyciu.

Rodzaje iteratorów

Typ iteratora Opis
Input Iterator Służy do odczytu danych z kontenera.
Output Iterator Służy do zapisu danych do kontenera.
Forward Iterator Łączy możliwości iteratorów input i output, może przemieszczać się tylko do przodu.
Bidirectional Iterator Umożliwia przemieszczanie się zarówno do przodu, jak i do tyłu.
Random Access Iterator Oferuje wszystkie możliwości Bidirectional Iterator oraz umożliwia dostęp do dowolnego elementu kontenera w stałym czasie.

Podstawowe operacje na iteratorach

Przykład użycia iteratora

Poniższy przykład ilustruje operacje na wektorze std::vector typu std::string. Najpierw tworzy i inicjalizuje wektor trzema elementami: "ala", "ma", "kota", a następnie wyświetla jego zawartość za pomocą iteratora. Następnie dodaje nowy element "nie" przed drugim elementem wektora, czyli przed "ma". Po tym usuwa pierwszy element wektora, którym jest "ala". Na koniec, program ponownie wyświetla zmodyfikowaną zawartość wektora.

#include <iostream>
#include <vector>

int main() {
    std::vector<std::string> v = {"ala", "ma", "kota"};

    // Wyświetlenie zawartości wektora przy użyciu iteratora
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << std::endl;
    }

    // Dodanie nowego elementu przed "ma"
    auto it = v.begin() + 1;
    v.insert(it, "nie");

    // Usunięcie słowa "ala"
    it = v.begin();
    v.erase(it);

    std::cout << "\nPo modyfikacjach:\n";
    for (const auto& word : v) {
        std::cout << word << std::endl;
    }

    return 0;
}

Warto pamiętać, że po niektórych operacjach, takich jak insert czy erase, używane wcześniej iteratory mogą stać się nieaktualne i ich dalsze użycie może prowadzić do niezdefiniowanego zachowania programu.

Algorytmy w Standardowej Bibliotece C++ (STL)

Biblioteka algorithm w standardowej bibliotece C++ (STL) dostarcza bogaty zestaw funkcji szablonowych, które służą do manipulacji i analizy danych przechowywanych w kontenerach. Algorytmy te są zaprojektowane w sposób generyczny, co oznacza, że mogą działać z różnymi typami danych i kontenerami, pod warunkiem spełnienia określonych wymagań. W tej sekcji przyjrzymy się kilku kluczowym algorytmom, ich implementacji, zastosowaniu oraz analizie matematycznej wydajności.

Wprowadzenie do Algorytmów STL

Algorytmy w STL są zaimplementowane jako funkcje szablonowe, co pozwala na ich użycie z różnymi typami danych i kontenerami. Działają one na zakresach określonych przez iteratory, co zapewnia elastyczność i niezależność od konkretnego typu kontenera. Dzięki temu możliwe jest pisanie kodu, który jest jednocześnie uniwersalny i wydajny.

Klasyfikacja Algorytmów

Algorytmy STL można podzielić na kilka kategorii:

Kategoria algorytmów Przykłady
Algorytmy modyfikujące std::copy, std::fill, std::transform
Algorytmy przeszukujące std::find, std::count, std::search
Algorytmy sortujące i porządkujące std::sort, std::stable_sort, std::partial_sort
Algorytmy numeryczne std::accumulate, std::inner_product, std::partial_sum
Algorytmy usuwające std::remove, std::unique, std::remove_if

std::sort()

Funkcja std::sort() służy do sortowania elementów w określonym zakresie. Jest to jeden z najczęściej używanych algorytmów w STL. Wykorzystuje algorytm sortowania introspektywnego (introsort), który łączy zalety sortowania szybkiego (quick sort), sortowania kopcowego (heap sort) i sortowania przez wstawianie (insertion sort).

Prototyp:

template< class RandomIt >
void sort( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );

Wymagania:

Przykład Użycia:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> wektor{8, 3, 5, 1, 2, 4, 6, 7};
    auto kopia = wektor;

    // Sortowanie pierwszych trzech elementów
    std::sort(wektor.begin(), wektor.begin() + 3);

    // Sortowanie całego wektora
    std::sort(kopia.begin(), kopia.end());

    // Wyświetlanie wyników
    std::cout << "Wektor po częściowym sortowaniu: ";
    for (const auto& val : wektor) {
        std::cout << val << " ";
    }
    std::cout << "\n";

    std::cout << "Wektor po pełnym sortowaniu: ";
    for (const auto& val : kopia) {
        std::cout << val << " ";
    }

    return 0;
}

Analiza Matematyczna:

Dostosowywanie Kryterium Sortowania:

Możemy dostarczyć własną funkcję porównującą:

std::sort(wektor.begin(), wektor.end(), [](int a, int b) {
    return a > b; // Sortowanie malejące
});

Uwagi:

std::find()

Algorytm std::find() przeszukuje zakres w poszukiwaniu pierwszego wystąpienia określonej wartości. Zwraca iterator wskazujący na znaleziony element lub na koniec zakresu, jeśli element nie został znaleziony.

Prototyp:

template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );

Przykład Użycia:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> wektor{8, 3, 5, 1, 2, 4, 6, 7};

    auto it = std::find(wektor.begin(), wektor.end(), 3);

    if (it != wektor.end())
        std::cout << "Znaleziono element o wartości 3 na pozycji "
                  << std::distance(wektor.begin(), it) << "\n";
    else
        std::cout << "Nie znaleziono elementu o wartości 3\n";

    return 0;
}

Analiza Matematyczna:

Uwagi:

std::for_each()

Funkcja std::for_each() stosuje podaną funkcję lub funktor do każdego elementu w zakresie. Jest to alternatywa dla pętli for i pozwala na bardziej funkcyjne podejście do iteracji.

Prototyp:

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );

Przykład Użycia:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> wektor{8, 3, 5, 1, 2, 4, 6, 7};

    // Modyfikacja każdego elementu przez jego podwojenie
    std::for_each(wektor.begin(), wektor.end(), [](int &x) { x *= 2; });

    // Wyświetlanie wyników
    std::cout << "Wektor po modyfikacji: ";
    for (const auto& val : wektor) {
        std::cout << val << " ";
    }

    return 0;
}

Analiza Matematyczna:

Uwagi:

std::count_if()

Algorytm std::count_if() zlicza liczbę elementów w zakresie, dla których predykat jest spełniony.

Prototyp:

template< class InputIt, class UnaryPredicate >
typename iterator_traits<inputit>::difference_type
count_if( InputIt first, InputIt last, UnaryPredicate p );

Przykład Użycia:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> wektor{8, 3, 5, 1, 2, 4, 6, 7};

    // Zliczanie parzystych elementów
    int parzyste = std::count_if(wektor.begin(), wektor.end(), [](int x) { return x % 2 == 0; });

    std::cout << "Liczba parzystych elementów: " << parzyste << "\n";

    return 0;
}

Analiza Matematyczna:

Uwagi:

Inne Ważne Algorytmy

std::accumulate()

Służy do obliczenia sumy wartości w zakresie, z opcjonalnym początkowym akumulatorem i funkcją operacji.

Prototyp:

template< class InputIt, class T >
T accumulate( InputIt first, InputIt last, T init );

template< class InputIt, class T, class BinaryOperation >
T accumulate( InputIt first, InputIt last, T init, BinaryOperation op );

Przykład:

int suma = std::accumulate(wektor.begin(), wektor.end(), 0);

std::transform()

Stosuje funkcję do każdego elementu w zakresie i zapisuje wynik w innym zakresie.

Prototyp:

template< class InputIt, class OutputIt, class UnaryOperation >
OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op );

Przykład:

std::vector<int> wyniki(wektor.size());
std::transform(wektor.begin(), wektor.end(), wyniki.begin(), [](int x) { return x * x; });

Kompatybilność z Iteratorami

Algorytmy STL są zaprojektowane tak, aby działać z różnymi kategoriami iteratorów:

Kategoria Przykłady Algorytmów
Iteratory wejściowe std::find, std::count_if
Iteratory wyjściowe std::copy, std::fill
Iteratory jedno kierunkowe std::for_each, std::remove
Iteratory dwukierunkowe std::reverse, std::rotate
Iteratory losowego dostępu std::sort, std::nth_element

Zastosowanie Algorytmów w Praktyce

Algorytmy STL pozwalają na pisanie kodu o wysokim poziomie abstrakcji, co zwiększa czytelność i redukuje ryzyko błędów. Dzięki generyczności i wykorzystaniu szablonów, algorytmy te są niezwykle elastyczne i mogą być stosowane w szerokim zakresie zastosowań.

Przykład: Analiza Danych Finansowych

Załóżmy, że mamy wektor reprezentujący dzienne zmiany cen akcji i chcemy przeprowadzić analizę:

std::vector<double> zmianyCen = { -0.5, 1.2, 0.3, -0.7, 0.8, -1.0, 0.6 };

// Obliczenie sumarycznej zmiany cen
double sumaZmian = std::accumulate(zmianyCen.begin(), zmianyCen.end(), 0.0);

// Zliczenie dni ze wzrostem cen
int dniWzrostu = std::count_if(zmianyCen.begin(), zmianyCen.end(), [](double x) { return x > 0; });

// Znalezienie największego spadku
auto it = std::min_element(zmianyCen.begin(), zmianyCen.end());

// Wyświetlenie wyników
std::cout << "Sumaryczna zmiana cen: " << sumaZmian << "\n";
std::cout << "Liczba dni ze wzrostem cen: " << dniWzrostu << "\n";
std::cout << "Największy spadek: " << *it << "\n";

Optymalizacja za Pomocą Algorytmów

Korzystanie z algorytmów STL może prowadzić do bardziej wydajnego kodu, ponieważ są one zazwyczaj dobrze zoptymalizowane i wykorzystują najlepsze praktyki implementacyjne. Ponadto, kompilatory mogą dokonywać dodatkowych optymalizacji, gdy używane są standardowe algorytmy.

Przykład: Porównanie z Pętlą for

Rozważmy zliczanie elementów spełniających określony warunek:

Tradycyjna pętla for:

int licznik = 0;
for (size_t i = 0; i < wektor.size(); ++i) {
  if (wektor[i] % 2 == 0) {
      ++licznik;
  }
}

Użycie std::count_if():

int licznik = std::count_if(wektor.begin(), wektor.end(), [](int x) { return x % 2 == 0; });

Korzystanie z std::count_if() nie tylko skraca kod, ale również zwiększa jego czytelność i potencjalnie wydajność.

Spis Treści

    STL (Standard Template Library)
    1. Kolekcje w STL
    2. STL (Standard Template Library)
    3. Kolekcje w STL
      1. vector
      2. list
      3. map
      4. unordered_map
      5. set
      6. unordered_set
      7. priority_queue
      8. queue
      9. stack
    4. Iteratory
      1. Rodzaje iteratorów
      2. Podstawowe operacje na iteratorach
      3. Przykład użycia iteratora
    5. Algorytmy w Standardowej Bibliotece C++ (STL)
      1. Wprowadzenie do Algorytmów STL
      2. Klasyfikacja Algorytmów
      3. std::sort()
      4. std::find()
      5. std::for_each()
      6. std::count_if()
      7. Inne Ważne Algorytmy
      8. Kompatybilność z Iteratorami
      9. Zastosowanie Algorytmów w Praktyce
      10. Optymalizacja za Pomocą Algorytmów