Algoritmi

Algoritmi djeluju na spremnike kroz iteratore.

  • Algoritmi nikada ne izvršavaju operacije koje nude spremnici i ne mijenjaju veličinu spremnika.

  • Svi su algoritmi definirani u zaglavlju <algorithm> osim nekoliko numeričkih koji su definirani u zaglavlju <numeric> te algoritama u <memory> zaglavlju.

  • Algoritam se može pojaviti u više verzija i tada dobiva sufiks:

    • Sufiks _if. Ova verzija uzima predikat dok osnovna verzija uzima vrijednost. Na primjer, find() i find_if().

    • Sufiks _copy. Ova verzija kopira ulazni niz, dok osnovna radi na njemu. Na primjer, reverse() i reverse_copy() .

    • Sufiks _n. Ova verzija umjesto end iteratora uzima broj elemenata s kojima treba raditi.

  • Algoritmi koji imaju ulazan i izlazan niz pretpostavljaju da izlazan niz ima isti broj elemenata kao i ulazan i stoga je izlazni niz zadan samo svojim početnim iteratorom.

Popis algoritama (1)

Algoritmi su podijeljeni u nekoliko grupa:

  • Nemodificirajući algoritmi

  • Modificirajući algoritmi

  • Operacije particioniranja

  • Operacije sortiranja

  • Operacije na sortiranim kolekcijama

  • Skupovne operacije

  • Opearcije na hrpi

  • Minimum/maksimum operacije

  • Opearacije uspoređivanja

  • Permutacije

  • Numeričke operacije

  • Operacije na neinicijaliziranoj memoriji

Osnovna literatura za algoritme je https://en.cppreference.com

Popis algoritama (2)

Nemodificirajući algoritmi

Definirani su u zaglavlju <algorithm>.

  • all_of, any_of, none_of provjera predikata

  • for_each, for_each_n (C++17) primjena funkcije na elemente

  • count, count_if broj elemenata koji zadovoljavaju kriterij

  • mismatch prvo mjesto razlikovanja

  • find, find_if, find_if_not nalaženje elementa

  • find_end nalaženje posljednjeg niza elemenata

  • find_first_of nalaženje bilo koje elementa iz skupa

  • adjacent_find nalaženje prva dva jednaka elementa

  • search, search_n potraga za nizom elemenata

Popis algoritama (3)

Modificirajući algoritmi

Definirani su u zaglavlju <algorithm>.

  • copy, copy_if, copy_n, copy_backward kopiranje elemenata

  • move, move_backward micanje elemenata

  • fill, fill_n kopiranje zadane vrijednosti

  • transform primjeni funkciju na kolekciju

  • generate, generate_n sukcesivni funkcijski pozivi

  • remove, remove_if, remove_copy, remove_copy_if uklanjanje elemenata

  • replace, replace_if, replace_copy , replace_copy_if zamjena elemenata

  • swap, swap_ranges, iter_swap međusobna zamjena elemenata

  • reverse, reverse_copy inverzija kolekcije

  • rotate, rotate_copy rotacija elemenata kolekcije

  • shift_left, shift_right (C++20) pomak elemenata

  • shuffle slučajno preuređivanje

  • sample (C++17) slučajni izbor elemenata

  • unique, unique_copy ukloni konsekutivne duplikate

Popis algoritama (4)

Definirani su u zaglavlju <algorithm>.

Operacije particioniranja

  • is_partitioned ispitivanja particioniranosti

  • partition, partition_copy, stable_partition particioniranje

  • partition_point lociranje razdijelne točke

Operacije sortiranja

  • is_sorted provjera sortiranosti

  • is_sorted_until najveći sortirani raspon

  • sort, stable_sort sortiranje

  • partial_sort, partial_sort_copy, nth_element parcijalno sortiranje

Operacije na sortiranim kolekcijama

  • lower_bound prvi element koji nije manji od zadanog

  • upper_bound prvi element veći od zadanog

  • binary_search binarno pretraživanje

  • equal_range raspon ekvivalentnih elemenata

  • merge, inplace_merge spajanje sortiranih kolekcija

Popis algoritama (5)

Definirani su u zaglavlju <algorithm>.

Skupovne operacije

  • includes inkluzija

  • set_difference skupovna razlika

  • set_intersection presjek

  • set_symmetric_difference simetrična razliak

  • set_union unija

Opearcije na hrpi

  • is_heap je li max hrpa

  • is_heap_until najveći raspon koji je max hrpa

  • make_heap učini max hrpom

  • push_heap dodaj element u max hrpu

  • pop_heap ukloni element iz max hrpe

  • sort_heap pretvorimax hrpu u rastući niz

Minimum/maksimum operacije

  • max , max_element maksimum

  • min , min_element minimum

  • minmax , minmax_element minimum i maksimum

  • clamp (C++17) odsjecanje između dva praga

Opearacije uspoređivanja

  • equal jednakost

  • lexicographical_compare leksikografsko uspoređivanje

  • compare_3way, lexicographical_compare_3way (C++20) trostruko uspoređivanje

Permutacije

  • is_permutation je li permutacija nečega

  • next_permutation sljedeća permutacija

  • prev_permutation prethodna permutacija

Popis algoritama (6)

Numeričke operacije

Definirani su u zaglavlju <numeric>.

  • iota ispuni suksesivnim inkrementima

  • accumulate, reduce (C++17) sumiraj

  • inner_product skalarni produkt

  • adjacent_difference diferencije elemenata

  • partial_sum, exclusive_scan (C++17), inclusive_scan (C++17) parcijalne sume

  • transform_reduce (C++17) primjena funkcije i zatim redukcija

  • transform_exclusive_scan (C++17) primjena funkcije i exclusive_scan

  • transform_inclusive_scan (C++17) primjena funkcije i inclusive_scan

Operacije na neinicijaliziranoj memoriji

Definirani su u zaglavlju <memory>.

  • uninitialized_copy , uninitialized_copy_n kopiranje

  • uninitialized_fill , uninitialized_fill_n ispunjavanje

  • uninitialized_move (C++17), uninitialized_move_n (C++17) premještanje

  • uninitialized_default_construct (C++17), uninitialized_default_construct_n (C++17) defaultna konstrukcija

  • uninitialized_value_construct (C++17), uninitialized_value_construct_n (C++17) vrijednosna inicijalizacija

  • destroy_at (C++17), destroy (C++17), destroy_n (C++17) destrukcija

Kopiranje

Ovi algoritmi kopiraju ulazni niz u izlazni niz. Ulazni niz je dan kao raspon elemenata, a izlazni niz je dan samo iteratorom koji pokazuje na prvi element niza.

it = copy(srcBeg, srcEnd, destBeg)
it = copy_if(srcBeg, srcEnd, destBeg, op)
it = copy_n (srcBeg, num, destBeg)
it = copy_backward (srcBeg, srcEnd, destEnd)
  • Algoritmi copy i copy_if uzimaju raspon elemenata koje kopiraju [srcBeg,srcEnd) i kopiraju ih u spremnik čiji je početni iterator destBeg.

  • Algoritam copy_n uzima iterator na prvi element ulaznog niza i broj elemenata koje treba kopirati.

  • Algoritmi copy, copy_if i copy_n iteriraju u pozitivnom smjeru pri kopiranju (od srcBeg prema srcEnd) dok algoritam copy_backward iterira u negativnom smjeru (od srcEnd prema srcBeg). Stoga copy_backward uzima end-iterator izlaznog niza.

  • Algoritam copy_if kopira element samo ako predikat op vrati true.

  • Svi algoritmi vraćaju iterator na mjesto iza zadnjeg kopiranog elementa u izlaznom nizu.

  • Algoritmi pretpostavljaju da je izlazni niz dovoljno velik da primi sve elemente.

  • Složenost: linearna

list<int> lst{1,2,3,4,5};
vector<int> vec(5);

copy(lst.begin(), lst.end(), vec.begin()); // Kopiraj listu u vektor

fill(vec.begin(), vec.end(), 0);
// Kopiraj samo neparne brojeve iz liste u vektor
copy_if(lst.begin(), lst.end(), vec.begin(),
                                [](int x){ return x % 2; });

// Pisanje preko istog spremnika.
vector<int> vec1{1,2,3,4,5,6,7,8,9};
copy_n(next(vec1.begin(),4),4,vec1.begin()); // 5,6,7,8,5,6,7,8,9,

vector<char> vec2(10,'.');
vec2.push_back('a');
vec2.push_back('b');
vec2.push_back('c');
print(vec2);
// Insertiraj točkice s ulaza na kraj spremnika
vec2.insert(vec2.end(), 10, '.'); // ..........abc..........
// Za kopiranje na kraj istog spremnika koristimo copy_backward
copy_backward(vec2.begin()+10, vec2.begin()+13, vec2.end());
// ..........abc.......abc

for_each i transform

for_each

Algoritam for_each djeluje na kolekciji zadanoj sa dva iteratora i na svakom elementu kolekcije poziva funkciju koja je treći argument algoritma. Signatura algoritma je sljedeća:

UnaryProc for_each (InputIterator beg, InputIterator end, UnaryProc op)

gdje je UnaryProc tip funkcije (funkcijskog objekta, lambda izraza) koji se poziva na svakom elementu kolekcije. Povratna vrijednost je kopija objekta op. Povratna vrijednost od op se ignorira.

Algoritam for_each može mijenjati elemente spremnika ako op uzima argument poreferenci. Ako op ima unutarnje stanje, to stanje će biti dohvatljivo kroz povratnu vrijednost algoritma.

void f(int& n) { n = n*n; }
// ...
std::vector<int> vek{0,1,2,3,4,5,6,7,8,9};

std::for_each(vek.begin(), vek.end(), f);
// sada je vek = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

int noParni = 0;
std::for_each(vek.begin(), vek.end(), [&noParni](int x){
         if(!(x % 2))
             noParni++;
     });
std::cout << noParni << std::endl; // 5

transform

Algoritam transform prolazi kroz ulaznu kolekciju, na svakom elementu kolekcije poziva funkciju op i rezultat ispisuje u izlaznu kolekciju. Vraća iterator koji pokazuje iza zadnjeg transformiranog elementa u izlaznoj kolekciji. Signatura algoritma je sljedeća:

OutputIterator transform(InputIterator srcBeg, InputIterator srcEnd, OutputIterator destBeg, UnaryFunc op)

Na primjer,

int f(int n) { return n*n; }
// ...
std::vector<int> vek{0,1,2,3,4,5,6,7,8,9};
std::transform(vek.begin(), vek.end(), vek.begin(), f);
// vek = 0 1 4 9 16 25 36 49 64 81

Algoritam ima verziju koja uzima dva ulazna niza i funkciju sa dva argumenta koja ulazne nizove transformira u izlazni. Na primjer,

int f2(int n, int m) { return n-m; }
// ...
std::random_device rd; // zaglavlje <random>
std::mt19937 g(rd());
std::vector<int> vek1 = vek, vek2(vek.size());
std::shuffle(vek1.begin(), vek1.end(), g);  // slučajna permutacija
std::transform(vek.begin(), vek.end(), vek1.begin(), vek2.begin(), f2);
// vek2 = vek - vek1

Nalaženje elementa

count

Algoritam count() vraća broj pojavljivanja vrijednosti u nizu: count_if vraća broj elemenata na kojima je predikat istinit.

n = count(beg, end, value)
n = count_if(beg, end, op)

Na primjer,

int A[] = { 2, 0, 4, 6, 0, 3, 1, -7 };
const int N = sizeof(A) / sizeof(int);

std::cout << "Broj nula: "
          << std::count(A, A + N, 0) << std::endl;

std::cout << "Broj brojeva > 2: "
          << std::count_if(A, A + N, [](int x){ return x>2; }) << std::endl;

find

Ovi algoritmi vraćaju iterator na prvi nađeni element ili end iterator. Osnovna verzija traži vrijednost, a druge dvije traže prvi element na kojem je predikat istinit (find_if) ili neistinit (find_if_not).

it = find (beg, end, value)
it = find_if(beg, end, op)
it = find_if_not(beg, end, op)

Na primjer,

std::vector<int> vec{1,4,1,9,4,5,5,7,4};
auto it = find(vec.begin(), vec.end(), 4);
assert(*it == 4);
assert(std::distance(vec.begin(), it) == 1);

it = find_if_not(vec.begin(), vec.end(), [](int x){ return x<7;});
assert(*it == 9);
assert(std::distance(vec.begin(), it) == 3);

search

Algoritam search traži niz elemenata u kolekciji. Kolekcija koja se pretražuje je dana s prvim parom iteratora (beg i end), a niz elemenata koji tražimo je zadan s drugim parom iteratora (beg1 i end1). Za uspoređivanje elemenata se u prvoj verziji algoritma koristi operator ==, a u drugoj binarni predikat op: op(elem,searchElem) vraća "istinu" ako su elementi isti, a u suprotnom laž.

it = search(beg, end, beg1, end1)
it = search(beg, end, beg1, end1, op)
std::list<int> li{1,2,3,4,5,1,2,3,4,5};
std::array<int,3>  niz{3,4,5};

auto it1 = std::search(li.begin(), li.end(), niz.begin(), niz.end());
if(it1 != li.end()){
  it1++;  // Nađi drugu grupu "niz"
  auto it2 =  std::search(it1, li.end(), niz.begin(), niz.end());
  assert(*it2 == 3);
  assert(std::distance(li.begin(),it2) == 7);
}

Algoritam search traži prvi podniz u nizu i vraća iterator na prvu poziciju nađenog podniza ili end iterator.

Algoritam koji traži zadnji podniz u nizu naziva se find_end (umjesto search_end, što bi bilo konzistentno).

Generiranje vrijednosti

fill

Ovaj algoritam inicijalizira svaki element kolekcije sa zadanom vrijednošću. Ne vraća ništa.

#include <cmath>

std::vector<double> vec(10);
std::fill(vec.begin(), vec.end(), M_PI);

generate

Ovaj algoritam inicijalizira svaki element kolekcije pozivom funkcije op() koja ne uzima argumente.

#include <cstdlib>  // za rand

std::vector<int> vec(5);
std::generate(vec.begin(), vec.end(), rand); // 1804289383,846930886,1681692777,1714636915,1957747793

iota

Algoritam iota generira niz sikcesivnih vrijednosti (svaka sljedeća je veća za 1) polazeći od dane vrijednosti.

#include <numeric>  // za iota

std::vector<float> vec(5);
std::iota(vec.begin(), vec.end(), 15);// 15,16,17,18,19

Zamjena vrijednosti

Algoritam replace svako pojavljivanje stare vrijednsti zamijenjuje s novom. Verzija replace_if zamijenjuje svaki element na kojem unarni predikat op vrati istinu s novom vrijednošću. Verzija _copy ovog algoritma kombinira copy i replace i smješta transformirane elemente u izlaznu kolekciju.

replace(beg, end, oldValue, newValue)
replace_if(beg, end, op, newValue)
replace_copy(beg, end, destBeg, oldValue, newValue)
replace_copy_if(beg, end, destBeg, op, newValue)

Na primjer,

std::vector<char> vec{'a','b','c','d','e','f'};
std::replace(vec.begin(), vec.end(), 'a', 'x');  // x,b,c,d,e,f

std::vector<char> vec1;
std::replace_copy(vec.begin(), vec.end(), std::back_inserter(vec1), 'e', 'x');
 // x,b,c,d,x,f

Brisanje elemenata

Algoritmi ne mogu brisati elemente spremnika te stoga samo prebacuje elemente određene za brisanje na kraj spremnika i vraća novi end iterator spremnika (iterator koji pokazuje na prvi eliminirani element). Pri tome algoritam ne garantira da će elementi određeni za brisanje biti prebačeni na kraj spremnika. U većini implementacija oni će biti jednostavno prebrisani.

remove

Algoritam remove briše sve sve elemente jednake zadanom iz kolekcije. Na primjer,

Nakon prolaza remove algoritma potrebno je zvati funkciju spremnika erase kako bi se izvršilo stvarno brisanje elemenata.

std::vector<int> ivecc{1,2,3,4,5,6,7,8,9,0};
auto newend = std::remove(ivecc.begin(), ivecc.end(), 1); // samo premještanje elemenata
ivecc.erase(newend, ivecc.end());    // stvarno brisanje elemenata

Verzija remove_if briše one elemente na kojima predikat vrati istinu.

Verzija remove_copy kombinira copy i remove. Ulazni niz ostaje isti, a na izlazni se kopiraju samo oni elementi koji nisu izbrisani. Na primjer,

std::vector<char> vec{'a','b','c','a','d','e','f'};

std::remove_copy(vec.begin(), vec.end(), std::ostream_iterator<char>(std::cout, " "), 'a');  // b,c,d,e,f
  • remove algoritam se ne koristi sa listom koja ima (efikasniju) remove metodu. S listom se ne koristi niti sort algoritam jer ona ima sort metodu koja je efikasnija.

  • remove algoritam se ne koristi sa asocijativnim spremnicima koji imaju verziju erase metode koja radi istu operaciju. Asocijativni spremnici map, set, multimap i multiset se ne sortiraju jer su u njima po konstrukciji elementi sortirani.

unique

Algoritam unique eliminira sve kosekutivne duplikate u spremiku (otkrivene operatorom ==).

Metoda std::sort služi sortiranju spremika (pomoću operatora <) i pozivamo ju prije std::unique() kako bismo osigurali da su duplikati konsekutivni:

// Izbacivanje duplikata
std::vector<std::string> lista_imena;
// Ubacimo neka imena
lista_imena.push_back("Ante");
lista_imena.push_back("Lovre");
lista_imena.push_back("Karmela");
lista_imena.push_back("Ante");
lista_imena.push_back("Lovre");
// Sortirajmo listu kako bi se ista imena našla jedna do drugih
std::sort(lista_imena.begin(), lista_imena.end());
// "brisanje" duplikata
auto it_unique = std::unique(lista_imena.begin(), lista_imena.end());
// stvarno brisanje
lista_imena.erase(it_unique, lista_imena.end()); // Ante Karmela Lovre

Kao i kod remove algoritma, postoje i unique_if i unique_copy algoritmi.

Algoritmi na sortiranim rasponima

Na spremnicima čiji su elementi sortirani za pretraživanja možemo koristiti algoritme:

Asocijativni spremnici map, multimap, set i multiset imaju implementirane metode lower_bound, upper_bound i equal_range. Nesortirani asocijativni spremnici unordered_map, itd. imaju implementiranu metodu equal_range.

  • lower_bound za dani element vraća iterator na prvu poziciju koja je veća ili jednaka od danog elementa. Kada tražene pozicije nema vraća se end iterator.

  • upper_bound za dani element vraća iterator na prvu poziciju koja je strogo veća od danog elementa. Kada tražene pozicije nema vraća se end iterator.

  • equal_range za dani element vraća par iteratora. Prvi je onaj koji vraća lower_bound, a drugi je onaj koji vraća upper_bound. Ako su vraćeni iteratori jednaki onda element nije nađen. Ako je vraćeni raspon neprazan, onda on sadrži sve elemente ekvivalentne traženom elementu.

  • binary_search ispituje dali je zadani element u rasponu metodom binarnog pretraživanja (i stoga raspon mora biti sortiran).

Primjer:

std::default_random_engine r_engine;
r_engine.seed( std::time(nullptr) );
std::uniform_int_distribution<unsigned int> dist(0, 7);

std::vector<unsigned int> vec(20);
std::generate(vec.begin(), vec.end(), [&dist, &r_engine]() { return dist(r_engine); } );
// npr.  3,7,3,4,7,4,1,6,2,6,6,7,4,0,2,5,1,6,2,2

std::sort(vec.begin(), vec.end());
// 0,1,1,2,2,2,2,3,3,4,4,4,5,6,6,6,6,7,7,7

auto its = std::equal_range(vec.begin(), vec.end(), 4);
for(auto it = its.first; it != its.second; ++it)
    std::cout << "vec[" << std::distance(vec.begin(), it) << "]=" << *it << ",";
std::cout << std::endl; // vec[9]=4,vec[10]=4,vec[11]=4