Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Prednáška 22: Rozdiel medzi revíziami
(→Oznamy) |
(→Oznamy) |
||
(3 medziľahlé úpravy od rovnakého používateľa nie sú zobrazené.) | |||
Riadok 8: | Riadok 8: | ||
** Na testovači pribudnú tréningové príklady na skúšku. Za niektoré budete môcť získať bonusový bod, ak ich vyriešite do 16.1. (ako tréning sa dajú riešiť aj neskôr). V utorok na cvičeniach pribudne ešte jeden tréningový príklad za 4 body. Ak prídete na cvičenia a odovzdáte na konci aspoň rozumne rozrobenú verziu programu, získate jeden bonusový bod, aj keď ho nestihnete dokončiť. | ** Na testovači pribudnú tréningové príklady na skúšku. Za niektoré budete môcť získať bonusový bod, ak ich vyriešite do 16.1. (ako tréning sa dajú riešiť aj neskôr). V utorok na cvičeniach pribudne ešte jeden tréningový príklad za 4 body. Ak prídete na cvičenia a odovzdáte na konci aspoň rozumne rozrobenú verziu programu, získate jeden bonusový bod, aj keď ho nestihnete dokončiť. | ||
* V piatok 20.12. od 13:10 predtermín skúšky, piatkové cvičenia nebudú. | * V piatok 20.12. od 13:10 predtermín skúšky, piatkové cvičenia nebudú. | ||
+ | |||
+ | Odporúčame si aspoň raz vyskúšať prácu v Linuxe na školských počítačoch, budete to potrebovať na skúške ([[Zimný_semester,_softvér|návod]]). | ||
== Prefixové stromy == | == Prefixové stromy == | ||
Riadok 31: | Riadok 33: | ||
struct node { | struct node { | ||
− | // pole | + | // pole smerníkov na deti |
− | node *children[alphSize]; | + | node * children[alphSize]; |
− | // | + | // prislúcha uzol k slovu z množiny? |
bool isWord; | bool isWord; | ||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Samotný prefixový strom | + | Samotný prefixový strom potrebuje smerník na svoj koreň: |
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
struct trie { | struct trie { | ||
− | node *root; | + | node * root; |
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 50: | Riadok 52: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | void init(trie &t) { | + | void init(trie & t) { |
t.root = NULL; | t.root = NULL; | ||
} | } | ||
Riadok 58: | Riadok 60: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | void destroySubtree(node *root) { | + | void destroySubtree(node * root) { |
if (root != NULL) { | if (root != NULL) { | ||
for (int i = 0; i < alphSize; i++) { | for (int i = 0; i < alphSize; i++) { | ||
Riadok 71: | Riadok 73: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | void destroy(trie &t) { | + | void destroy(trie & t) { |
destroySubtree(t.root); | destroySubtree(t.root); | ||
} | } | ||
Riadok 84: | Riadok 86: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | bool contains(trie &t, const char *word) { | + | bool contains(trie & t, const char * word) { |
− | node *v = t.root; | + | node * v = t.root; |
if (v == NULL) { | if (v == NULL) { | ||
return false; | return false; | ||
Riadok 106: | Riadok 108: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | node *createNode(bool isWord) { | + | node * createNode(bool isWord) { |
− | node *v = new node; | + | node * v = new node; |
for (int i = 0; i < alphSize; i++) { | for (int i = 0; i < alphSize; i++) { | ||
v->children[i] = NULL; | v->children[i] = NULL; | ||
Riadok 122: | Riadok 124: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | void add(trie &t, const char *word) { | + | void add(trie & t, const char * word) { |
if (t.root == NULL) { | if (t.root == NULL) { | ||
t.root = createNode(false); | t.root = createNode(false); | ||
} | } | ||
− | node *v = t.root; | + | node * v = t.root; |
for (int i = 0; word[i] != 0; i++) { | for (int i = 0; word[i] != 0; i++) { | ||
int c = word[i] - 'a'; | int c = word[i] - 'a'; | ||
Riadok 151: | Riadok 153: | ||
* V prípade, že po vykonaní jednej z predchádzajúcich dvoch operácií nemá uzol <tt>root</tt> žiadne dieťa a súčasne má <tt>root->isWord</tt> hodnotu <tt>false</tt>, uvoľní pamäť alokovanú pre uzol <tt>root</tt> a informáciu o jeho zmazaní vráti na výstupe. | * V prípade, že po vykonaní jednej z predchádzajúcich dvoch operácií nemá uzol <tt>root</tt> žiadne dieťa a súčasne má <tt>root->isWord</tt> hodnotu <tt>false</tt>, uvoľní pamäť alokovanú pre uzol <tt>root</tt> a informáciu o jeho zmazaní vráti na výstupe. | ||
− | Cvičenie: hoci mazanie neprehľadáva celý strom, iba jednu cestu z koreňa smerom dolu, naprogramovali sme | + | Cvičenie: hoci mazanie neprehľadáva celý strom, iba jednu cestu z koreňa smerom dolu, naprogramovali sme ho rekurzívne. Na aký problén by sme narazili, ak by sme ju chceli naprogramovať cyklom? Pomohli by nám smerníky na rodiča v uzloch stromu? |
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | bool removeFromSubtree(node *root, const char *word, int index) { | + | bool removeFromSubtree(node * root, |
+ | const char * word, int index) { | ||
assert(root != NULL); | assert(root != NULL); | ||
if (word[index] == 0) { | if (word[index] == 0) { | ||
Riadok 161: | Riadok 164: | ||
} else { | } else { | ||
int c = word[index] - 'a'; | int c = word[index] - 'a'; | ||
− | bool deleted = removeFromSubtree(root->children[c], word, index + 1); | + | bool deleted = removeFromSubtree(root->children[c], |
+ | word, index + 1); | ||
if (deleted) { | if (deleted) { | ||
root->children[c] = NULL; | root->children[c] = NULL; | ||
Riadok 184: | Riadok 188: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | void remove(trie &t, const char *word) { | + | void remove(trie & t, const char * word) { |
− | // | + | // zavoláme rekurziu pre koreň stromu |
bool rootRemoved = removeFromSubtree(t.root, word, 0); | bool rootRemoved = removeFromSubtree(t.root, word, 0); | ||
− | // ak bol | + | // ak bol koreň odstránený, nastavíme t.root na NULL |
if (rootRemoved) { | if (rootRemoved) { | ||
t.root = NULL; | t.root = NULL; | ||
Riadok 232: | Riadok 236: | ||
} | } | ||
if (root->isWord) { | if (root->isWord) { | ||
− | str[index] = 0; | + | // ukončíme a vypíšeme reťazec |
+ | str[index] = 0; | ||
printf("%s\n", str); | printf("%s\n", str); | ||
} | } | ||
Riadok 430: | Riadok 435: | ||
char D[100] = {'p', 'e', 's', 0}; | char D[100] = {'p', 'e', 's', 0}; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * funkcie strlen, strcpy, strcmp, strcat | + | * funkcie <tt>strlen</tt>, <tt>strcpy</tt>, <tt>strcmp</tt>, <tt>strcat</tt> |
'''Súbory, spracovanie vstupu''' | '''Súbory, spracovanie vstupu''' | ||
− | * cin, cout alebo printf, scanf | + | * <tt>cin</tt>, <tt>cout</tt> alebo <tt>printf</tt>, <tt>scanf</tt> (nekombinovať) |
− | * fopen, fclose, feof | + | * <tt>fopen</tt>, <tt>fclose</tt>, <tt>feof</tt> |
− | * fprintf, fscanf | + | * <tt>fprintf</tt>, <tt>fscanf</tt> |
− | * getc, putc, ungetc, fgets, fputs | + | * <tt>getc</tt>, <tt>putc</tt>, <tt>ungetc</tt>, <tt>fgets</tt>, <tt>fputs</tt> |
* spracovanie súboru po znakoch, po riadkoch, po číslach alebo slovách | * spracovanie súboru po znakoch, po riadkoch, po číslach alebo slovách | ||
+ | |||
'''Smerníky, dynamicky alokovaná pamäť, dvojrozmerné polia''' | '''Smerníky, dynamicky alokovaná pamäť, dvojrozmerné polia''' | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
int i; // „klasická“ celočíselná premenná | int i; // „klasická“ celočíselná premenná | ||
− | int *p; | + | int * p; // smerník na celočíselnú premennú |
− | p = &i; // | + | p = &i; // správne |
− | p = &(i + 3); // zle i+3 nie je | + | p = &(i + 3); // zle, i+3 nie je premenná |
− | p = &15; // zle | + | p = &15; // zle, konštanta nemá adresu |
− | i = *p; | + | i = * p; // správne ak p bol inicializovaný |
int * cislo = new int; // alokovanie jednej premennej | int * cislo = new int; // alokovanie jednej premennej | ||
Riadok 455: | Riadok 461: | ||
int a[4]; | int a[4]; | ||
− | int *b = a; // a,b su teraz takmer | + | int *b = a; // a,b su teraz takmer rovnocenné premenné |
− | int * | + | int *b = new int[n]; // alokovanie 1D poľa danej dĺžky |
.. | .. | ||
− | delete[] | + | delete[] b; |
int **a; // alokovanie 2D matice | int **a; // alokovanie 2D matice | ||
Riadok 472: | Riadok 478: | ||
Abstraktný dátový typ '''dynamické pole''' (rastúce pole) | Abstraktný dátový typ '''dynamické pole''' (rastúce pole) | ||
− | * operácie init, add, get, set, length | + | * operácie <tt>init</tt>, <tt>add</tt>, <tt>get</tt>, <tt>set</tt>, <tt>length</tt> |
Abstraktný dátový typ '''dynamická množina''' (set) | Abstraktný dátový typ '''dynamická množina''' (set) | ||
− | * operácie init, contains, add, remove | + | * operácie <tt>init</tt>, <tt>contains</tt>, <tt>add</tt>, <tt>remove</tt> |
* implementácie pomocou | * implementácie pomocou | ||
** neutriedeného poľa | ** neutriedeného poľa | ||
Riadok 485: | Riadok 491: | ||
Abstraktné dátové typy '''rad a zásobník''' | Abstraktné dátové typy '''rad a zásobník''' | ||
− | * operácie pre rad (frontu, queue): init, isEmpty, enqueue, dequeue, peek | + | * operácie pre rad (frontu, queue): <tt>init</tt>, <tt>isEmpty</tt>, <tt>enqueue</tt>, <tt>dequeue</tt>, <tt>peek</tt> |
− | * operácie pre zásobník (stack): init, isEmpty, push, pop | + | * operácie pre zásobník (stack): <tt>init</tt>, <tt>isEmpty</tt>, <tt>push</tt>, <tt>pop</tt> |
* implementácie: v poli alebo v spájanom zozname | * implementácie: v poli alebo v spájanom zozname | ||
* využitie: ukladanie dát na spracovanie, odstránenie rekurzie | * využitie: ukladanie dát na spracovanie, odstránenie rekurzie | ||
Riadok 513: | Riadok 519: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
struct node { | struct node { | ||
− | /* | + | /* uzol stromu */ |
dataType data; | dataType data; | ||
− | node * left; /* | + | node * left; /* ľavé dieťa */ |
− | node * right; /* | + | node * right; /* pravé dieťa */ |
}; | }; | ||
− | node * createNode(dataType data, node *left, node *right) { | + | node * createNode(dataType data, node * left, node * right) { |
− | node *v = new node; | + | node * v = new node; |
v->data = data; | v->data = data; | ||
v->left = left; | v->left = left; | ||
Riadok 548: | Riadok 554: | ||
'''Hašovanie''' | '''Hašovanie''' | ||
* hašovacia tabuľka veľkosti ''m'' | * hašovacia tabuľka veľkosti ''m'' | ||
− | * kľúč ''k'' premietneme nejakou funkciou na index v poli | + | * kľúč ''k'' premietneme nejakou funkciou na index v poli {0,...,m-1} |
* každé políčko hašovacej tabuľky spájaný zoznam prvkov, ktoré sa tam zahašovali | * každé políčko hašovacej tabuľky spájaný zoznam prvkov, ktoré sa tam zahašovali | ||
* v ideálnom prípade sa prvky rozhodia pomerne rovnomerne, zoznamy krátke, rýchle hľadanie, vkladenie, mazanie | * v ideálnom prípade sa prvky rozhodia pomerne rovnomerne, zoznamy krátke, rýchle hľadanie, vkladenie, mazanie |
Aktuálna revízia z 08:01, 11. december 2024
Obsah
Oznamy
Plán prednášok a cvičení na zvyšok semestra:
- Dnes informácie k skúške a posledná ukážka stromov, večer 18:10 semestrálny test.
- V piatok cvičenia pre tých, čo nespravili v utorok rozcvičku.
- V pondelok 16.12. nepovinná prednáška o nepreberaných črtách jazykov C a C++ (táto nepovinná časť učiva nebude vyžadovaná na skúške, ale môžete ju použiť).
- V utorok 17.12. v rámci cvičení tréning na skúšku.
- Na testovači pribudnú tréningové príklady na skúšku. Za niektoré budete môcť získať bonusový bod, ak ich vyriešite do 16.1. (ako tréning sa dajú riešiť aj neskôr). V utorok na cvičeniach pribudne ešte jeden tréningový príklad za 4 body. Ak prídete na cvičenia a odovzdáte na konci aspoň rozumne rozrobenú verziu programu, získate jeden bonusový bod, aj keď ho nestihnete dokončiť.
- V piatok 20.12. od 13:10 predtermín skúšky, piatkové cvičenia nebudú.
Odporúčame si aspoň raz vyskúšať prácu v Linuxe na školských počítačoch, budete to potrebovať na skúške (návod).
Prefixové stromy
Prefixové stromy (niekde tiež lexikografické stromy; angl. trie zo slova retrieval) sú dátová štruktúra na uchovávanie množiny reťazcov. Ide o stromy, ktoré nemusia byť binárne:
- Uzol prefixového stromu má najviac toľko detí, koľko je znakov v uvažovanej abecede. Každé dieťa je označené iným znakom abecedy. Graficky si môžeme predstaviť tento znak prislúchajúci k hrane spájajúcej rodiča a dieťa.
- Koreň prefixového stromu zodpovedá prázdnemu reťazcu.
- Uzol v hĺbke k zodpovedá reťazcu dĺžky k, ktorý dostaneme prečítaním písmen na ceste z koreňa do daného uzla.
- Každý uzol prefixového stromu obsahuje logickú hodnotu vyjadrujúcu, či k nemu prislúchajúci reťazec patrí do množiny reprezentovanej týmto prefixovým stromom.
- V korektnom prefixovom strome všetky listy zodpovedajú reťazcom z reprezentovanej množiny.
- Vnútorné vrcholy môžu zodpovedať reťazcu z množiny alebo iba prefixu jedného alebo viacerých takých reťazcov.
Uzly prefixového stromu budeme reprezentovať štruktúrou node
- Uzol obsahuje obsahuje booleovskú premennú isWord, v ktorej je uložená informácia o tom, či reťazec prislúchajúci k danému uzlu patrí alebo nepatrí do reprezentovanej množiny a pole children smerníkov na jednotlivé deti daného uzla.
- Veľkosť alphSize tohto poľa je rovná veľkosti uvažovanej abecedy.
- V ukážkovom programe uvažujeme abecedu 'a'..'z'.
const int alphSize = 'z' - 'a' + 1;
struct node {
// pole smerníkov na deti
node * children[alphSize];
// prislúcha uzol k slovu z množiny?
bool isWord;
};
Samotný prefixový strom potrebuje smerník na svoj koreň:
struct trie {
node * root;
};
Inicializácia a likvidácia prefixového stromu
Nasledujúca funkcia inicializuje prázdny prefixový strom t:
void init(trie & t) {
t.root = NULL;
}
Uvoľnenie pamäte alokovanej pre podstrom zakorenený v uzle root realizujeme obdobne ako pri binárnych vyhľadávacích stromoch. Jediný rozdiel spočíva v potenciálne väčšom počte detí uzla root.
void destroySubtree(node * root) {
if (root != NULL) {
for (int i = 0; i < alphSize; i++) {
destroySubtree(root->children[i]);
}
delete root;
}
}
Nasledujúca funkcia potom zlikviduje celý prefixový strom t:
void destroy(trie & t) {
destroySubtree(t.root);
}
Hľadanie v prefixovom strome
Funkcia contains pre daný prefixový strom t a reťazec word zistí, či slovo word patrí do množiny reprezentovanej stromom t.
- Postupuje po písmenách reťazca word. Kým nedôjde na koniec slova, snaží sa ísť po hranách, ktoré zodpovedajú jednotlivým písmenám.
- V prípade, že v niektorom bode narazí na NULL, slovo word sa v strome nenachádza.
- V opačnom prípade toto slovo dočíta v nejakom uzle v. V takom prípade slovo word patrí do reprezentovanej množiny práve vtedy, keď v->isWord má hodnotu true.
bool contains(trie & t, const char * word) {
node * v = t.root;
if (v == NULL) {
return false;
}
for (int i = 0; word[i] != 0; i++) {
int c = word[i] - 'a';
assert(c >= 0 && c < alphSize);
v = v->children[c];
if (v == NULL) {
return false;
}
}
return v->isWord;
}
Vkladanie do prefixového stromu
Pri vkladaní reťazca do množiny reprezentovanej prefixovým stromom potrebujeme vytvárať nové uzly. Túto podúlohu realizuje funkcia createNode, ktorá vytvorí nový uzol s hodnotou isWord danou jej argumentom a so všetkými smerníkmi na deti nastavenými na NULL.
node * createNode(bool isWord) {
node * v = new node;
for (int i = 0; i < alphSize; i++) {
v->children[i] = NULL;
}
v->isWord = isWord;
return v;
}
Vloženie reťazca word do prefixového stromu t vykoná funkcia add, ktorá pracuje nasledovne:
- Začne v koreni stromu, odkiaľ postupuje nižšie smerom k listom.
- V každom uzle sa pozrie na ďalšie písmeno slova word. Ak danému uzlu chýba dieťa pre toto písmeno, vytvorí ho pomocou funkcie createNode. Následne sa presunie do tohto dieťaťa.
- Keď v nejakom uzle v príde na koniec slova word, nastaví hodnotu v->isWord na true.
void add(trie & t, const char * word) {
if (t.root == NULL) {
t.root = createNode(false);
}
node * v = t.root;
for (int i = 0; word[i] != 0; i++) {
int c = word[i] - 'a';
assert(c >= 0 && c < alphSize);
if (v->children[c] == NULL) {
v->children[c] = createNode(false);
}
v = v->children[c];
}
v->isWord = true;
}
Vymazanie slova z prefixového stromu
Vymazávanie slov z množiny reprezentovanej prefixovým stromom budeme realizovať prostredníctvom pomocnej rekurzívnej funkcie removeFromSubtree.
- Funkcia z podstromu zakorenenom v uzle root vymaže sufix reťazca word začínajúci na pozícii index.
- Funkcia vráti booleovskú hodnotu podľa toho, či sa pri tomto vymazaní sufixu z daného podstromu vymazal jeho koreň root.
- Ak sa slovo word v reprezentovanej množine nenachádza, funkcia removeFromSubtree vyhlási chybu pomocou funkcie assert.
Funkcia removeFromSubtree pracuje nasledovne:
- Ak je sufix reťazca word začínajúci na indexe index prázdny, nastaví hodnotu root->isWord na false.
- V opačnom prípade funkcia removeFromSubtree zavolá rekurzívne samú seba pre dieťa zodpovedajúce písmenu na pozícii index reťazca word. Ak toto volanie dané dieťa zmaže, prestaví smerník na toto dieťa na NULL.
- V prípade, že po vykonaní jednej z predchádzajúcich dvoch operácií nemá uzol root žiadne dieťa a súčasne má root->isWord hodnotu false, uvoľní pamäť alokovanú pre uzol root a informáciu o jeho zmazaní vráti na výstupe.
Cvičenie: hoci mazanie neprehľadáva celý strom, iba jednu cestu z koreňa smerom dolu, naprogramovali sme ho rekurzívne. Na aký problén by sme narazili, ak by sme ju chceli naprogramovať cyklom? Pomohli by nám smerníky na rodiča v uzloch stromu?
bool removeFromSubtree(node * root,
const char * word, int index) {
assert(root != NULL);
if (word[index] == 0) {
assert(root->isWord);
root->isWord = false;
} else {
int c = word[index] - 'a';
bool deleted = removeFromSubtree(root->children[c],
word, index + 1);
if (deleted) {
root->children[c] = NULL;
}
}
int numChildren = 0;
for (int i = 0; i < alphSize; i++) {
if (root->children[i] != NULL) {
numChildren++;
}
}
if (numChildren == 0 && !root->isWord) {
delete root;
return true;
} else {
return false;
}
}
Samotné odstránenie reťazca word z množiny reprezentovanej stromom t potom realizuje funkcia remove.
void remove(trie & t, const char * word) {
// zavoláme rekurziu pre koreň stromu
bool rootRemoved = removeFromSubtree(t.root, word, 0);
// ak bol koreň odstránený, nastavíme t.root na NULL
if (rootRemoved) {
t.root = NULL;
}
}
Výška prefixového stromu
Nasledujúca funkcia vypočíta výšku podstromu zakoreneného v uzle root:
int subtreeHeight(node *root) {
if (root == NULL) {
return -1;
}
int maxHeight = -1;
for (int i = 0; i < alphSize; i++) {
int height = subtreeHeight(root->children[i]);
if (height > maxHeight) {
maxHeight = height;
}
}
return maxHeight + 1;
}
Výšku samotného prefixového stromu t potom spočíta nasledujúca funkcia:
int height(trie &t) {
return subtreeHeight(t.root);
}
Vypisovanie slov reprezentovaných prefixovým stromom
Nasledujúca funkcia printSubtree prehľadáva podstrom zakorenený v uzle root a v reťazci str postupne generuje všetky slová z reprezentovanej množiny, ktoré zároveň vypisuje na konzolu. V parametri index dostane hĺbku aktuálneho vrcholu, t.j. pozíciu v reťazci, na ktorú pridáme ďalší znak.
void printSubtree(node *root, char *str, int index) {
if (root == NULL) {
return;
}
if (root->isWord) {
// ukončíme a vypíšeme reťazec
str[index] = 0;
printf("%s\n", str);
}
for (int i = 0; i < alphSize; i++) {
str[index] = 'a' + i;
printSubtree(root->children[i], str, index + 1);
}
}
Funkcia printAll vypisujúca všetky slová v množine reprezentovanej prefixovým stromom t najprv spočíta výšku stromu t, ktorá je rovná dĺžke najdlšieho reťazca tejto množiny. Následne dynamicky alokuje reťazec dostatočnej dĺžky na uchovanie každého slova množiny a zavolá funkciu printSubtree pre koreň stromu t.
void printAll(trie &t) {
int height = height(t);
if (height >= 0) {
char *str = new char[height + 1];
printSubtree(t.root, str, 0);
delete[] str;
}
}
V akom poradí budú slová vypísané?
Ukážka programu s prefixovým stromom, ADT slovník
Na vstupe máme text pozostávajúci zo slov s malými písmenami a pre každé slovo v texte chceme spočítať, koľkokrát sa tam nachádza.
- Jednotlivé slová uložíme pomocou prefixového stromu a v každom uzle si pamätáme namiesto hodnoty isWord počítadlo count, ktoré udáva, koľkokrát sme príslušné slovo videli na vstupe.
- Počítadlo má hodnotu nula pre prefixy vstupných slov, ktoré samé zatiaľ ako slovo na vstupe neboli.
- Namiesto funkcie treeInsert máme funkciu treeIncrement, ktorá dostane slovo a zvýši jeho počítadlo, pričom ak slovo zatiaľ v strome nebolo, tak ho pridá.
- Podobne by sme na tento účel vedeli upraviť aj implementáciu množiny pomocou binárneho vyhľadávacieho stromu, hašovacej tabuľky, poľa alebo zoznamu.
- Pozor, ak sú kľúče reťazce, na ich porovnanie musíme v týchto implementáciách použiť strcmp, nie ==, < a pod.
Abstraktný dátový typ, ktorý si okrem množiny kľúčov ku každému kľúču pamätá aj ďalšie dáta, sa zvykne nazývať slovník (angl. dictionary, map).
- Tu boli kľúče slová a ďalšie dáta počet výskytov.
- Iný príklad je zoznam kontaktov, kde kľúčom je meno osoby a pre dané meno chceme vrátiť kontaktné údaje danej osoby (emailová adresa, telefón a pod.)
#include <cstdio>
#include <cassert>
#include <cstring>
using namespace std;
const int alphSize = 'z' - 'a' + 1;
// uzol prefixoveho stromu
struct node {
// pole smernikov na deti
node *children[alphSize];
// pocet vyskytov slova prisluchajuceho uzlu
int count;
};
// cely prefixovy strom
struct trie {
node *root;
};
// inicializacia prazdneho stormu
void init(trie &t) {
t.root = NULL;
}
// mazanie podstromu s korenom root
void destroySubtree(node *root) {
if (root != NULL) {
for (int i = 0; i < alphSize; i++) {
destroySubtree(root->children[i]);
}
delete root;
}
}
// uvolnenie pamate celeho stromu
void destroy(trie &t) {
destroySubtree(t.root);
}
// vytvorenie noveo uzlu bez deti a s nula vyskytmi
node *createNode() {
node *v = new node;
for (int i = 0; i < alphSize; i++) {
v->children[i] = NULL;
}
v->count = 0;
return v;
}
// zvysenie pocitadla pre slovo word
// ak slovo este nie je v strome, je pridane
void increment(trie &t, const char *word) {
if (t.root == NULL) {
t.root = createNode();
}
node *v = t.root;
for (int i = 0; word[i] != 0; i++) {
int c = word[i] - 'a';
assert(c >= 0 && c < alphSize);
if (v->children[c] == NULL) {
v->children[c] = createNode();
}
v = v->children[c];
}
v->count++;
}
// vyska podstromu s korenom root
int subtreeHeight(node *root) {
if (root == NULL) {
return -1;
}
int maxHeight = -1;
for (int i = 0; i < alphSize; i++) {
int height = subtreeHeight(root->children[i]);
if (height > maxHeight) {
maxHeight = height;
}
}
return maxHeight + 1;
}
// vyska stromu. t.j. dlzka najdlsieho slova
int height(trie &t) {
return subtreeHeight(t.root);
}
// vypisanie slov v podstrome prefixoveho stromu
void printSubtree(node *root, char *str, int index) {
if (root == NULL) {
return;
}
if (root->count > 0) {
str[index] = 0; // ukoncenie retazca pred vypisom
printf("%s %d\n", str, root->count);
}
for (int i = 0; i < alphSize; i++) {
str[index] = 'a' + i;
printSubtree(root->children[i], str, index + 1);
}
}
// vypisanie slov prefixoveho stromu
void printAll(trie &t) {
int height = height(t);
if (height >= 0) {
char *str = new char[height + 1];
printSubtree(t.root, str, 0);
delete[] str;
}
}
int main() {
// inicializacia stromu
trie t;
init(t);
// postupne nacitavanie slov
char word[100];
while (true) {
int count = scanf("%99s", word);
if (count < 1) { // koniec vstupu
break;
}
// pridanie slova resp. zvysenie pocitadla
increment(t, word);
}
// vypis a uvolnenie pamate
printAll(t);
destroy(t);
}
Sylaby predmetu
Základy
Konštrukcie jazyka C
- premenné typov int, double, char, bool, konverzie medzi nimi
- podmienky (if, else, switch), cykly (for, while)
- funkcie (a parametre funkcií - odovzdávanie hodnotou, referenciou, smerníkom)
void f1(int x){} //hodnotou
void f2(int &x){} //referenciou
void f3(int* x){} //smerníkom
void f(int a[], int n){} //polia bez & (ostanú zmeny)
void kresli(Turtle &t){} //korytnačky, SVGdraw a pod. s &
Polia, reťazce (char[])
int A[4]={3, 6, 8, 10};
int B[4];
B[0]=3; B[1]=6; B[2]=8; B[3]=10;
char C[100] = "pes";
char D[100] = {'p', 'e', 's', 0};
- funkcie strlen, strcpy, strcmp, strcat
Súbory, spracovanie vstupu
- cin, cout alebo printf, scanf (nekombinovať)
- fopen, fclose, feof
- fprintf, fscanf
- getc, putc, ungetc, fgets, fputs
- spracovanie súboru po znakoch, po riadkoch, po číslach alebo slovách
Smerníky, dynamicky alokovaná pamäť, dvojrozmerné polia
int i; // „klasická“ celočíselná premenná
int * p; // smerník na celočíselnú premennú
p = &i; // správne
p = &(i + 3); // zle, i+3 nie je premenná
p = &15; // zle, konštanta nemá adresu
i = * p; // správne ak p bol inicializovaný
int * cislo = new int; // alokovanie jednej premennej
*cislo = 50;
..
delete cislo;
int a[4];
int *b = a; // a,b su teraz takmer rovnocenné premenné
int *b = new int[n]; // alokovanie 1D poľa danej dĺžky
..
delete[] b;
int **a; // alokovanie 2D matice
a = new int *[n];
for (int i = 0; i < n; i++) a[i] = new int[m];
..
for (int i = 0; i < n; i++) delete[] a[i];
delete[] a;
Abstraktné dátové typy
Abstraktný dátový typ dynamické pole (rastúce pole)
- operácie init, add, get, set, length
Abstraktný dátový typ dynamická množina (set)
- operácie init, contains, add, remove
- implementácie pomocou
- neutriedeného poľa
- utriedeného poľa
- spájaných zoznamov
- hašovacej tabuľky
- binárnych vyhľadávacích stromov
- prefixového stromu (ak kľúč je reťazec)
Abstraktné dátové typy rad a zásobník
- operácie pre rad (frontu, queue): init, isEmpty, enqueue, dequeue, peek
- operácie pre zásobník (stack): init, isEmpty, push, pop
- implementácie: v poli alebo v spájanom zozname
- využitie: ukladanie dát na spracovanie, odstránenie rekurzie
- kontrola zátvoriek a vyhodnocovanie výrazov pomocou zásobníka
Dátové štruktúry
Spájané zoznamy
struct node {
int data;
node* next;
};
struct linkedList {
node* first;
};
void insertFirst(linkedList &z, int d){
/* do zoznamu z vlozi na zaciatok novy prvok s datami d */
node* p = new node; // vytvoríme nový prvok
p->data = d; // naplníme dáta
p->next = z.first; // uzol ukazuje na doterajší začiatok
z.first = p; // tento prvok je novým začiatkom
}
Binárne stromy
struct node {
/* uzol stromu */
dataType data;
node * left; /* ľavé dieťa */
node * right; /* pravé dieťa */
};
node * createNode(dataType data, node * left, node * right) {
node * v = new node;
v->data = data;
v->left = left;
v->right = right;
return v;
}
- prehľadávanie inorder, preorder, postorder
- použitie na uloženie aritmetických výrazov
Binárne vyhľadávacie stromy
- vrcholy vľavo od koreňa menší kľúč, vpravo od koreňa väčší
- insert, find, remove v čase závisiacom od hĺbky stromu
Prefixové stromy
- ukladajú množinu reťazcov
- nie sú binárne: vrchol môže mať veľa detí
- insert, find, remove v čase závisiacom od dĺžky kľúča, ale nie od počtu kľúčov, ktoré už sú v strome
struct node { // uzol prefixoveho stromu
bool isWord; // je tento uzol koncom slova?
node* next[Abeceda]; // pole smernikov na deti
};
Hašovanie
- hašovacia tabuľka veľkosti m
- kľúč k premietneme nejakou funkciou na index v poli {0,...,m-1}
- každé políčko hašovacej tabuľky spájaný zoznam prvkov, ktoré sa tam zahašovali
- v ideálnom prípade sa prvky rozhodia pomerne rovnomerne, zoznamy krátke, rýchle hľadanie, vkladenie, mazanie
- v najhoršom prípade všetky prvky v jednom zozname, pomalé hľadanie a mazanie
int hash(int k, int m){ // veľmi jednoduchá hašovacia funkcia, v praxi väčšinou zložitejšie
return abs(k) % m;
}
struct node {
int data;
node* next;
};
struct set {
node** data;
int m;
};
Algoritmy
Rekurzia
- Rekurzívne funkcie
- Vykresľovanie fraktálov
- Prehľadávanie s návratom (backtracking)
- Vyfarbovanie
- Prehľadávanie stromov
Triedenia
- nerekurzívne: Bubblesort, Selectionsort, Insertsort
- rekurzívne: Mergesort, Quicksort
- súvisiace algoritmy: binárne vyhľadávanie
Matematické úlohy
- Euklidov algoritmus, Eratostenovo sito
- Práca s aritmetickými výrazmi: vyhodnocovanie postfixovej formy, prevod z infixovej do postfixovej, reprezentácia vo forme stromu