Code Submission Evaluation System Login

Algoritmit ongelmanratkaisussa 2019


Tasks | Statistics


CSES - Lisää tietorakenteita

Lisää tietorakenteita


C++:n standardikirjasto sisältää valmiita toteutuksia tutuista tietorakenteista, kuten Javan standardikirjasto. Saatavilla on joukkoja ja hakemistoja, jotka perustuvat puu- ja hajautusrakenteisiin.

C++:n rakenteiden nimet ja käyttötavat eroavat hieman Javasta, mutta periaate on sama. Erona on kuitenkin, että iteraattorit (jotka esitellään tässä tekstissä) ovat C++:ssa suuremmassa roolissa kuin Javassa.

Joukko

C++:n set-rakenne perustuu tasapainoiseen binäärihakupuuhun, ja sen operaatiot toimivat $O(\log n)$-ajassa.

Joukon käyttäminen vaatii, että koodin alussa on rivi #include <set>.

Seuraava koodi luo joukon, lisää siihen luvut 3, 7 ja 5 funktiolla insert ja hakee sitten alkioiden määrän funktiolla size.
set<int> s;
s.insert(3);
s.insert(7);
s.insert(5);
cout << s.size() << "\n"; // 3

Funktio count kertoo, onko joukossa tiettyä alkiota. Koska tietty alkio voi esiintyä vain kerran joukossa, funktio palauttaa aina 0 tai 1.
cout << s.count(5) << "\n"; // 1
cout << s.count(6) << "\n"; // 0

Funktio erase poistaa alkion joukosta:
s.insert(4);
cout << s.count(4) << "\n"; // 1
s.erase(4);
cout << s.count(4) << "\n"; // 0

Hakemisto

Hakemisto on taulukon yleistys, joka sisältää joukon avain-arvo-pareja. C++:n map-rakenne perustuu tasapainoiseen binäärihakupuuhun, ja sen operaatiot toimivat $O(\log n)$-ajassa.

Hakemiston käyttäminen vaatii, että koodin alussa on rivi #include <map>.

Esimerkiksi seuraava koodi luo hakemiston, jossa avaimet ovat merkkijonoja ja arvot ovat kokonaislukuja:
map<string,int> x;
x["apina"] = 1;
x["banaani"] = 2;
x["cembalo"] = 3;
cout << x["banaani"] << "\n"; // 2

Jos haettua avainta ei ole hakemistossa, sen oletusarvona on 0 tai tyhjä:
map<string,int> x;
cout << x["aybabtu"] << "\n"; // 0

Huomaa, että yllä oleva koodi myös lisää avaimen hakemistoon oletusarvolla.

Prioriteettijono

Prioriteettijonoon voi lisätä alkioita sekä hakea ja poistaa pienimmän tai suurimman alkion. C++:n priority_queue rakenne toteuttaa prioriteettijonon, jossa voi oletuksena hakea ja poistaa suurimman alkion.

Prioriteettijonon käyttäminen vaatii, että koodin alussa on rivi #include <queue>.

Seuraava koodi esittelee prioriteettijonon operaatioita:
priority_queue<int> q;
q.push(3);
q.push(7);
q.push(5);
cout << q.top() << "\n"; // 7
q.pop();
cout << q.top() << "\n"; // 5

Huomaa ero Javaan: Javassa prioriteettijono antaa oletuksena pienimmän alkion, mutta C++:ssä suurimman alkion.

Iteraattorit

Iteraattori on muuttuja, joka osoittaa tietorakenteen alkioon. Iteraattori begin osoittaa ensimmäiseen alkioon ja iteraattori end osoittaa viimeisen alkion jälkeiseen alkioon. Iteraattorin osoittamaan alkioon pääsee käsiksi *-merkinnällä, ja iteraattoria pystyy siirtämään operaatioilla ++ ja --.

Esimerkiksi seuraava koodi tulostaa kaikki joukon alkiot iteraattorin avulla. Koska joukko säilyttää järjestyksen, alkiot käydään läpi pienimmästä suurimpaan.
set<int> s = {2,3,5,7};
auto it = s.begin();
while (it != s.end()) {
    cout << *it << "\n";
    it++;
}

Tämä koodi tulostaa joukon pienimmän ja suurimman alkion:
set<int> s = {2,3,5,7};
auto it1 = s.begin();
auto it2 = s.end(); it2--;
cout << *it1 << " " << *it2 << "\n"; // 2 7

Funktio find antaa iteraattorin tiettyyn alkioon. Funktio lower_bound antaa iteraattorin pienimpään alkioon, joka on ainakin yhtä suuri kuin annettu alkio. Funktio upper_bound antaa iteraattorin pienimpään alkioon, joka on suurempi kuin annettu alkio. Jos alkiota ei ole olemassa, nämä funktiot antavat tuloksena iteraattorin end.

Seuraava koodi etsii iteraattoriin alkioon 5:
auto it = s.find(5);
if (it == s.end()) {
    // alkiota ei löytynyt
}

Seuraava koodi tulostaa pienimmän alkion, joka on ainakin yhtä suuri kuin 5, sekä pienimmän alkion, joka on suurempi kuin 5:
set<int> s = {2,3,5,7};
cout << *s.lower_bound(5) << "\n"; // 5
cout << *s.upper_bound(5) << "\n"; // 7

Toistot joukossa

Joukkoon set ei voi lisätä samaa alkiota monta kertaa:
set<int> s;
s.insert(5);
s.insert(5);
s.insert(5);
cout << s.count(5) << "\n"; // 1

Tämä on kuitenkin mahdollista joukossa multiset, joka on muuten kuin set, mutta sallii toistuvat alkiot:
multiset<int> s;
s.insert(5);
s.insert(5);
s.insert(5);
cout << s.count(5) << "\n"; // 3

Huomaa, että multiset-rakenteessa funktio count vie aikaa $O(\log n + k)$, missä $k$ on toistojen määrä.

Funktio erase poistaa alkion kaikki esiintymät joukosta:
multiset<int> s;
s.insert(5);
s.insert(5);
s.insert(5);
cout << s.count(5); << "\n"; // 3
s.erase(5);
cout << s.count(5); << "\n"; // 0

Vain yhden esiintymän pystyy kuitenkin poistamaan näin:
multiset<int> s;
s.insert(5);
s.insert(5);
s.insert(5);
cout << s.count(5); << "\n"; // 3
s.erase(s.find(5));
cout << s.count(5); << "\n"; // 2

Tällöin poistettavaksi annetaan alkion asemesta iteraattori alkioon.

Parit joukossa

Joskus on kätevää luoda joukko, jonka alkiot ovat pareja. Esimerkiksi seuraava koodi tekee näin:
set<pair<int,int>> s;
s.insert({3,5});
s.insert({6,1});
s.insert({3,4});

Tällöin parit ovat järjestyksessä joukossa ensisijaisesti ensimmäisen jäsenen ja toissijaisesti toisen jäsenen mukaan:
auto it = s.lower_bound({4,2});
cout << it->first << " " << it->second << "\n"; // 6 1

Hajautusrakenteet

C++ sisältää tasapainoiseen binäärihakupuuhun perustuvien rakenteiden rinnalla vastaavat hajautustauluun perustuvat rakenteet. Niissä on etuliitteenä unordered ja operaatiot vievät aikaa (yleensä) $O(1)$.

Esimerkiksi seuraava koodi luo hajautustauluun perustuvan joukon:
unordered_set<int> s;
s.insert(3);
s.insert(7);
s.insert(5);

Hajautustauluun perustuvat rakenteet ovat usein hieman nopeampia kuin binäärihakupuuhun perustuvat rakenteet. Niissä ei voi kuitenkaan käyttää funktioita, jotka vaativat alkioiden järjestystä (kuten lower_bound ja upper_bound).