Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Prednáška 22: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(46 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 2: Riadok 2:
  
 
Plán prednášok a cvičení na zvyšok semestra:
 
Plán prednášok a cvičení na zvyšok semestra:
* Dnes pokračujeme stromy.
+
* Dnes informácie k skúške a posledná ukážka stromov, večer 18:10 [[Zimný semester, semestrálny test|semestrálny test]].
* V utorok 14.12. v rámci cvičení tréning na skúšku.
+
* V piatok cvičenia pre tých, čo nespravili v utorok rozcvičku.
** Na testovači už sú tréningové príklady na skúšku. Za niektoré budete môcť získať bonusový bod, ak ich vyriešite do 12.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 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 stredu 15.12. ak treba dokončíme stromy, potom 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.
* V piatok 17.12. od 12:00 predtermín skúšky, doplnkové cvičenia nebudú
+
** 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ú.
  
== Binárne vyhľadávacie stromy ==
+
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 ==
  
===Opakovanie===
+
[[Image:trie2.png|thumb|right|Prefixový strom reprezentujúci množinu reťazcov <tt>a, aj, ale, aleba, alebo, cez, na, nad</tt>.]]
[[Image:P22-BST.png|200px|right|thumb|Príklad binárneho vyhľadávacieho stromu.]]
 
Binárny vyhľadávací strom (binary search tree) je dátová štruktúra určená na ukladanie dynamickej množiny prvkov.
 
* V binárnom vyhľadávacom strome má každý vrchol 0, 1 alebo 2 deti
 
* V každom vrchole máme položku s dátami (pre jednoduchosť typu <tt>int</tt>)
 
* Pre každý vrchol ''v'' stromu platí:
 
** Každý vrchol v ľavom podstrome ''v'' má hodnotu <tt>data</tt> menšiu ako vrchol ''v''
 
** Každý vrchol v pravom podstrome ''v'' má hodnotu <tt>data</tt> väčšiu ako vrchol ''v''
 
  
<syntaxhighlight lang="C++">
+
''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:
struct node {
+
* 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.  
    /* vrchol binárneho vyhľadávacieho stromu  */
+
* Koreň prefixového stromu zodpovedá prázdnemu reťazcu.
    int data;      /* hodnota */
 
    node * parent;  /* rodič vrchola, NULL v koreni */
 
    node * left;    /* ľavé dieťa, NULL ak neexistuje */
 
    node * right;  /* pravé dieťa, NULL ak neexistuje */
 
};
 
 
 
/* Samotná štruktúra binárneho vyhľadávacieho stromu (obal pre používateľa). */
 
struct binarySearchTree {
 
    node *root;  /* koreň stromu, NULL pre prázdny strom */
 
};
 
</syntaxhighlight>
 
 
 
 
 
Videli sme vyhľadávanie prvku v binárnom vyhľadávacom strome. Čas výpočtu je v najhoršom prípade úmerný výške stromu.
 
 
 
=== Vkladanie do binárneho vyhľadávacieho stromu ===
 
 
 
Nasledujúca funkcia <tt>insertNode</tt> vloží uzol <tt>*v</tt> na správne miesto podstromu zakoreneného v <tt>*root</tt> ako jeho list.
 
* Predpokladáme, že prvok v strome nie je.
 
* Putujeme po strome podobne ako pri vyhľadávaní prvku, až kým nenarazíme na nulový smerník.
 
** Na tomto mieste by mal byť nový prvok, takže ho tam pridáme ako nový list
 
** Uvádzame rekurzívnu verziu, dá sa aj cyklom, podobne ako pri hľadaní
 
* Funkcia <tt>bstInsert</tt> vytvorí uzol s daným kľúčom <tt>key</tt> a pomocou funkcie <tt>insertNode</tt> ho vloží do binárneho vyhľadávacieho stromu <tt>t</tt>.
 
 
 
<syntaxhighlight lang="C++">
 
/* Vloží uzol v na správne miesto podstromu zakoreneného v root */
 
void insertNode(node *root, node *v) {
 
    assert(root != NULL && v != NULL);
 
    if (v->data < root->data) {
 
        if (root->left == NULL) {
 
            root->left = v;
 
            v->parent = root;
 
        } else {
 
            insertNode(root->left, v);
 
        }
 
    } else {
 
        if (root->right == NULL) {
 
            root->right = v;
 
            v->parent = root;
 
        } else {
 
            insertNode(root->right, v);
 
        }
 
    }
 
}
 
 
 
/* Vloží do stromu t nový uzol s kľúčom key. */
 
void bstInsert(binarySearchTree &t, int key) {
 
    node *v = new node;
 
    v->data = key;
 
    v->left = NULL;
 
    v->right = NULL;
 
    v->parent = NULL;
 
    if (t.root == NULL) {
 
        t.root = v;
 
    } else {
 
        insertNode(t.root, v);
 
    }
 
}
 
</syntaxhighlight>
 
 
 
Čas vkladania je tiež v najhoršom prípade úmerný hĺbke stromu.
 
 
 
'''Cvičenia'''
 
* Napíšte nerekurzívny variant funkcie <tt>insertNode</tt>.
 
* Napíšte funkciu <tt>treeSort</tt>, ktorá z poľa celých čísel <tt>a</tt> pomocou volaní funkcie <tt>bstInsert</tt> vytvorí binárny vyhľadávací strom a následne pomocou prehľadávania tohto stromu v poradí ''inorder'' pole <tt>a</tt> utriedi.
 
* Ako bude vyzerať strom po nasledujúcej postupnosti operácií?
 
<pre>
 
    binarySearchTree t;
 
    bstInit(t);
 
    bstInsert(t, 2);
 
    bstInsert(t, 5);
 
    bstInsert(t, 3);
 
    bstInsert(t, 10);
 
    bstInsert(t, 7); 
 
</pre>
 
 
 
===Minimum a následník===
 
Uvedieme teraz dve funkcie, ktoré sa zídu pri mazaní prvku zo stromu, ale môžu sa zísť aj inokedy.
 
 
 
Prvá funkcia <tt>minNode</tt> nájde vo vyhľadávacom strome uzol, v ktorom je uložená najmenšia hodnota.
 
* Všetky prvky menšie ako koreň sú v ľavom podstrome, bude tam zrejme aj minimum.
 
* Tá istá úvaha platí pre koreň ľavého podstromu.
 
* Ideme teda doľava kým sa dá, posledný vrchol vrátime (list alebo vrchol, ktorý má iba pravé dieťa).
 
* Nie je treba teda prechádzať celý strom a nemusíme sa ani pozerať na položku data v uzloch.
 
* Dá sa napísať cyklom aj rekurzívne.
 
* Obalom pre používateľa bude funkcia <tt>bstMin</tt>, ktorá pomocou funkcie <tt>minNode</tt> nájde minimálny kľúč v danom binárnom vyhľadávacom strome <tt>t</tt>.
 
 
 
<syntaxhighlight lang="C++">
 
/* Vrati uzol s minimalnou hodnotou data v podstrome s korenom v. */
 
node *minNode(node *v) {
 
    assert(v != NULL);
 
    while (v->left != NULL) {
 
        v = v->left;
 
    }
 
    return v;
 
}
 
 
 
/* Vrati minimalny kluc uzla v strome t. */
 
int bstMin(binarySearchTree &t) {
 
    assert(t.root != NULL);
 
    return minNode(t.root)->data;
 
}
 
</syntaxhighlight>
 
 
 
''Cvičenia'':
 
* Napíšte rekurzívny variant funkcie <tt>minNode</tt>.
 
* Ako by bolo treba funkciu zmeniť, aby hľadala maximum?
 
 
 
=== Následník uzla ===
 
 
 
Funkcia <tt>successorNode</tt> nájde pre daný uzol <tt>v</tt> jeho ''následníka'' (angl. ''successor'') v binárnom vyhľadávacom strome, čiže uzol, ktorý vo vzostupnom poradí podľa kľúčov nasleduje bezprostredne za uzlom <tt>v</tt>. Je pritom založená na nasledujúcich pozorovaniach:
 
* Ak má uzol <tt>v</tt> pravého syna, následník uzla <tt>v</tt> musí byť v jeho pravom podstrome &ndash; konkrétne pôjde o minimálny uzol z tohto podstromu.
 
* V opačnom prípade môže byť následníkom uzla <tt>*v</tt> jeho otec (ak <tt>*v</tt> je jeho ľavý syn). Ak je <tt>*v</tt> pravým synom svojho otca, môže to byť aj jeho starý otec (ak je otec uzla <tt>*v</tt> ľavým synom tohto starého otca), atď. Vo všeobecnosti teda ide o najbližšieho predka uzla <tt>*v</tt> takého, že <tt>*v</tt> patrí do jeho ľavého podstromu.
 
* V strome existuje práve jeden uzol bez následníka (jeden spomedzi najväčších prvkov).
 
 
 
<syntaxhighlight lang="C++">
 
/* Vrati uzol, ktory vo vzostupnom poradi uzlov podla klucov nasleduje za *v. Ak taky uzol neexistuje, vrati NULL. */
 
node *successorNode(node *v) {
 
    assert(v != NULL);
 
    if (v->right != NULL) {
 
        return minNode(v->right);
 
    }
 
    while (v->parent != NULL && v == v->parent->right) {
 
        v = v->parent;
 
    }
 
    return v->parent;
 
}
 
</syntaxhighlight>
 
 
 
=== Mazanie z binárneho vyhľadávacieho stromu ===
 
 
 
Nasledujúca funkcia <tt>bstRemove</tt> zmaže z binárneho vyhľadávacieho stromu <tt>t</tt> práve jeden uzol s kľúčom <tt>key</tt> (ak sa taký uzol v strome vyskytuje). Pracuje tak, že najprv pomocou funkcie <tt>findNode</tt> nájde uzol <tt>*v</tt> s kľúčom <tt>key</tt>. V prípade úspechu zistí počet synov uzla <tt>*v</tt>. Ak totiž <tt>*v</tt> nemá žiadneho syna alebo má len jedného syna, možno ho zo stromu <tt>t</tt> zmazať jednoducho tak, že sa prípadný syn uzla <tt>*v</tt> stane synom otca uzla <tt>*v</tt>. V prípade, že má <tt>*v</tt> dvoch synov je však zrejmé, že jeho následník sa musí nachádzať v jeho neprázdnom pravom podstrome. Tento následník  <tt>*rm</tt> navyše nemôže mať ľavého syna. Odstránenie kľúča <tt>key</tt> je teda možné realizovať tak, že sa kľúč uzla <tt>*rm</tt> presunie do uzla <tt>*v</tt> a následne sa odstráni uzol <tt>*rm</tt> tak, ako je popísané vyššie.
 
 
 
<syntaxhighlight lang="C++">
 
/* Zmaze zo stromu t prave jeden uzol s klucom key (ak tam taky je). */
 
void bstRemove(binarySearchTree &t, int key) {
 
    node *v = findNode(t.root, key);                  // Najde uzol v s hodnotou, ktoru treba vymazat.
 
    if (v == NULL) {
 
        return;
 
    }
 
   
 
    node *rm;                                          // Najde uzol *rm stromu t, ktory sa napokon realne zmaze. 
 
    if (v->left == NULL || v->right == NULL) {       
 
        rm = v;
 
    } else  {
 
        rm = successorNode(v);
 
    }
 
 
 
    if (rm != v) {                                    // Ak rm != v, presunie kluc uzla *rm do uzla *v.
 
        v->data = rm->data;
 
    }
 
   
 
    node *child;                                      // Zmaze uzol *rm a uvolni pamat alokovanu pre tento uzol.
 
    if (rm->left != NULL) {
 
        child = rm->left;
 
    } else {
 
        child = rm->right;
 
    }                 
 
    if (child != NULL) {
 
        child->parent = rm->parent;
 
    }
 
    if (rm->parent == NULL) {
 
        t.root = child;
 
    } else if (rm == rm->parent->left) {
 
        rm->parent->left = child;   
 
    } else if (rm == rm->parent->right) {
 
        rm->parent->right = child;
 
    }
 
    delete rm;
 
}
 
</syntaxhighlight>
 
 
 
=== Zložitosť jednotlivých operácií ===
 
 
 
* Časová zložitosť operácií <tt>bstFind(t)</tt>, <tt>bstInsert(t)</tt> aj <tt>bstRemove(t)</tt> je úmerná hodnote ''height(<tt>t</tt>)'', čo je výška stromu <tt>t</tt>.
 
* Minule sme ukázali, že pre výšku ''h'' stromu s ''n'' vrcholmi je log<sub>2</sub>''(n+1)-1 &le; h &le; n-1''.
 
* Zložitosť uvedených operácií je teda v najhoršom prípade lineárna od počtu uzlov stromu (tento prípad nastane, ak prvky vkladáme od najmenšieho po najväčší alebo naopak).
 
* Dá sa však ukázať, že v priemernom prípade je ich zložitosť rádovo logaritmická od počtu uzlov.
 
* Na predmete ''Algoritmy a dátové štruktúry'' (druhý ročník) sa tieto tvrdenia dokazujú poriadne a preberajú sa tam aj varianty vyhľadávacích stromov, pre ktoré je zložitosť uvedených operácií logaritmická aj v najhoršom prípade.
 
 
 
 
 
* Časová zložitosť operácií <tt>bstFind(t)</tt>, <tt>bstInsert(t)</tt> aj <tt>bstRemove(t)</tt> je úmerná hodnote ''height(<tt>t</tt>)'', čo je výška stromu <tt>t</tt>.
 
* Predminule sme ukázali, že pre výšku ''h'' stromu s ''n'' vrcholmi je log<sub>2</sub>''(n+1)-1 &le; h &le; n-1''.
 
* Zložitosť uvedených operácií je teda v najhoršom prípade lineárna od počtu uzlov stromu (tento prípad nastane, ak prvky vkladáme od najmenšieho po najväčší alebo naopak).
 
* Dá sa však ukázať, že v priemernom prípade je ich zložitosť rádovo logaritmická od počtu uzlov.
 
* Na predmete ''Algoritmy a dátové štruktúry'' (druhý ročník) sa tieto tvrdenia dokazujú poriadne a preberajú sa tam aj varianty vyhľadávacích stromov, pre ktoré je zložitosť uvedených operácií logaritmická aj v najhoršom prípade.
 
 
 
== Lexikografické stromy ==
 
 
 
[[Image:trie2.png|thumb|right|Lexikografický strom reprezentujúci množinu reťazcov <tt>a, aj, ale, aleba, alebo, cez, na, nad</tt>.]]
 
 
 
''Lexikografické stromy'' (niekde tiež ''prefixové 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:
 
* Každý uzol lexikografického stromu má najviac toľko synov, koľko je písmen v uvažovanej abecede. Každý zo synov zodpovedá ''práve jednému'' písmenu abecedy a každému písmenu abecedy zodpovedá ''najviac jeden'' syn daného uzla. Graficky možno písmená zodpovedajúce jednotlivým synom znázorniť ohodnotením hrán spájajúcich dané uzly.  
 
* Koreň lexikografické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.
 
* 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 lexikografického stromu obsahuje informáciu (<tt>true</tt> alebo <tt>false</tt>) o tom, či k nemu prislúchajúci reťazec patrí do množiny reprezentovanej týmto lexikografickým stromom alebo nie.
+
* 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 lexikografickom strome všetky listy zodpovedajú reťazcom z reprezentovanej množiny; niektoré reťazce reprezentovanej množiny však môžu zodpovedať aj vnútorným vrcholom stromu. Každý uzol lexikografického stromu tak zodpovedá nejakému prefixu slova z ním reprezentovanej množiny &ndash; odtiaľ alternatívne pomenovanie ''prefixový strom''.  
+
* 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.  
  
Štruktúra <tt>node</tt> pre uzol lexikografického stromu tak obsahuje booleovskú premennú <tt>isWord</tt>, 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 <tt>children</tt> smerníkov na jednotlivých synov daného uzla. Veľkosť <tt>alphSize</tt> tohto poľa je rovná veľkosti uvažovanej abecedy &ndash; pre písmená malej anglickej abecedy je táto hodnota rovná <tt>'z' - 'a' + 1</tt>.
+
Uzly prefixového stromu budeme reprezentovať štruktúrou <tt>node</tt>  
 +
* Uzol obsahuje obsahuje booleovskú premennú <tt>isWord</tt>, 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 <tt>children</tt> smerníkov na jednotlivé deti daného uzla.  
 +
* Veľkosť <tt>alphSize</tt> tohto poľa je rovná veľkosti uvažovanej abecedy.
 +
* V ukážkovom programe uvažujeme abecedu 'a'..'z'.
  
Pri rôznych praktických aplikáciách môže štruktúra <tt>node</tt> obsahovať aj rozličné ďalšie informácie &ndash; pre každé slovo z reprezentovanej množiny si napríklad môžeme pamätať jeho frekvenciu výskytu, preklad do nejakého iného jazyka a podobne.
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 229: Riadok 33:
  
 
struct node {
 
struct node {
     node *children[alphSize]; // pole smernikov na deti
+
    // pole smerníkov na deti
     bool isWord;              // udava, ci uzol prislucha k slovu z reprezentovanej mnoziny
+
     node * children[alphSize];  
     // dalsie data (napriklad preklad slova, pocet vyskytov slova v texte, ...)
+
     // prislúcha uzol k slovu z množiny?
 +
     bool isWord;             
 
};
 
};
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Samotný lexikografický strom je potom daný iba smerníkom na svoj koreň:
+
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>
  
=== Inicializácia lexikografického stromu ===
+
=== Inicializácia a likvidácia prefixového stromu ===
  
Nasledujúca funkcia inicializuje prázdny lexikografický strom <tt>t</tt>:
+
Nasledujúca funkcia inicializuje prázdny prefixový strom <tt>t</tt>:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void trieInit(trie &t) {
+
void init(trie & t) {
 
     t.root = NULL;  
 
     t.root = NULL;  
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Likvidácia lexikografického stromu ===
+
Uvoľnenie pamäte alokovanej pre podstrom zakorenený v uzle <tt>root</tt> realizujeme obdobne ako pri binárnych vyhľadávacích stromoch. Jediný rozdiel spočíva v potenciálne väčšom počte detí uzla <tt>root</tt>.
 
 
Uvoľnenie pamäte alokovanej pre podstrom zakorenený v uzle <tt>root</tt> realizujeme obdobne ako pri binárnych vyhľadávacích stromoch. Jediný rozdiel spočíva v potenciálne väčšom počte synov uzla <tt>root</tt>.
 
  
 
<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 - 1; i++) {
+
         for (int i = 0; i < alphSize; i++) {
 
             destroySubtree(root->children[i]);
 
             destroySubtree(root->children[i]);
 
         }
 
         }
Riadok 267: Riadok 70:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Nasledujúca funkcia potom zlikviduje celý lexikografický strom <tt>t</tt>:
+
Nasledujúca funkcia potom zlikviduje celý prefixový strom <tt>t</tt>:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void trieDestroy(trie &t) {
+
void destroy(trie & t) {
 
     destroySubtree(t.root);
 
     destroySubtree(t.root);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Vkladanie do lexikografického stromu ===
+
=== Hľadanie v prefixovom strome ===
  
Pri vkladaní reťazca do množiny realizovanej lexikografickým stromom často vznikne potreba vytvárať nové uzly tohto stromu. Túto podúlohu realizuje funkcia <tt>createNode</tt>, ktorá vytvorí nový uzol s hodnotou <tt>isWord</tt> danou jej argumentom a so všetkými smerníkmi na synov rovnými <tt>NULL</tt>.
+
Funkcia <tt>contains</tt> pre daný prefixový strom <tt>t</tt> a reťazec <tt>word</tt> zistí, či slovo <tt>word</tt> patrí do množiny reprezentovanej stromom <tt>t</tt>.
 +
* Postupuje po písmenách reťazca <tt>word</tt>. 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 <tt>NULL</tt>, slovo <tt>word</tt> sa v strome nenachádza.  
 +
* V opačnom prípade toto slovo dočíta v nejakom uzle <tt>v</tt>. V takom prípade slovo <tt>word</tt> patrí do reprezentovanej množiny práve vtedy, keď <tt>v->isWord</tt> má hodnotu <tt>true</tt>.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
node *createNode(bool isWord) {
+
bool contains(trie & t, const char * word) {
     node *v = new node;
+
    node * v = t.root;
     for (int i = 0; i <= alphSize - 1; i++) {
+
    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;
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== 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 <tt>createNode</tt>, ktorá vytvorí nový uzol s hodnotou <tt>isWord</tt> danou jej argumentom a so všetkými smerníkmi na deti nastavenými na <tt>NULL</tt>.
 +
 
 +
<syntaxhighlight lang="C++">
 +
node * createNode(bool isWord) {
 +
     node * v = new node;
 +
     for (int i = 0; i < alphSize; i++) {
 
         v->children[i] = NULL;
 
         v->children[i] = NULL;
 
     }
 
     }
Riadok 290: Riadok 118:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Vloženie reťazca <tt>word</tt> do lexikografického stromu <tt>t</tt> potom realizuje funkcia <tt>trieInsert</tt>, ktorá pracuje nasledovne:
+
Vloženie reťazca <tt>word</tt> do prefixového stromu <tt>t</tt> vykoná funkcia <tt>add</tt>, ktorá pracuje nasledovne:
 
* Začne v koreni stromu, odkiaľ postupuje nižšie smerom k listom.
 
* Začne v koreni stromu, odkiaľ postupuje nižšie smerom k listom.
* V každom uzle sa pozrie na ďalšie písmeno slova <tt>word</tt>. Ak danému uzlu chýba syn pre toto písmeno, vytvorí ho pomocou funkcie <tt>createNode</tt>. Následne sa presunie do tohto syna.
+
* V každom uzle sa pozrie na ďalšie písmeno slova <tt>word</tt>. Ak danému uzlu chýba dieťa pre toto písmeno, vytvorí ho pomocou funkcie <tt>createNode</tt>. Následne sa presunie do tohto dieťaťa.
* Ak v nejakom uzle <tt>v</tt> príde na koniec slova <tt>word</tt>, nastaví hodnotu <tt>v->isWord</tt> na <tt>true</tt>.
+
* Keď v nejakom uzle <tt>v</tt> príde na koniec slova <tt>word</tt>, nastaví hodnotu <tt>v->isWord</tt> na <tt>true</tt>.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void trieInsert(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';
 +
        assert(c >= 0 && c < alphSize);
 
         if (v->children[c] == NULL) {
 
         if (v->children[c] == NULL) {
 
             v->children[c] = createNode(false);
 
             v->children[c] = createNode(false);
Riadok 312: Riadok 141:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Hľadanie v lexikografickom strome ===
+
=== Vymazanie slova z prefixového stromu ===
 
 
Funkcia <tt>trieFind</tt> pre daný lexikografický strom <tt>t</tt> a reťazec <tt>word</tt> zistí, či slovo <tt>word</tt> patrí do množiny reprezentovanej stromom <tt>t</tt>. Opäť pritom postupuje po písmenách reťazca <tt>word</tt>. 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 <tt>NULL</tt>, slovo <tt>word</tt> sa v strome nenachádza. V opačnom prípade toto slovo dočíta v nejakom uzle <tt>v</tt>. V takom prípade slovo <tt>word</tt> patrí do reprezentovanej množiny práve vtedy, keď <tt>v->isWord</tt> má hodnotu <tt>true</tt>.
 
 
 
<syntaxhighlight lang="C++">
 
bool trieFind(trie &t, const char *word) {
 
    node *v = t.root;
 
    if (v == NULL) {
 
        return false;
 
    }
 
    for (int i = 0; word[i] != 0; i++) {
 
        v = v->children[word[i] - 'a'];
 
        if (v == NULL) {
 
            return false;
 
        }
 
    }
 
    return v->isWord;
 
}
 
</syntaxhighlight>
 
 
 
=== Vymazávanie z lexikografického stromu ===
 
  
Vymazávanie slov z množiny reprezentovanej lexikografickým stromom budeme realizovať prostredníctvom pomocnej rekurzívnej funkcie <tt>removeFromSubtree</tt>, ktorá z podstromu zakorenenom v uzle <tt>*root</tt> vymaže sufix reťazca <tt>word</tt> začínajúci na pozícii <tt>index</tt>. Táto funkcia vráti na výstupe booleovskú hodnotu podľa toho, či sa pri tomto vymazaní sufixu z daného podstromu vymazal jeho koreň <tt>*root</tt> (táto situácia nenastane vždy: napríklad pri mazaní slova <tt>a</tt> z podstromu reprezentujúceho množinu slov <tt>{a,ab}</tt> uzol prislúchajúci k reťazcu <tt>a</tt> v strome ostáva; zmení sa len jeho hodnota <tt>isWord</tt>).
+
Vymazávanie slov z množiny reprezentovanej prefixovým stromom budeme realizovať prostredníctvom pomocnej rekurzívnej funkcie <tt>removeFromSubtree</tt>.
 
+
* Funkcia z podstromu zakorenenom v uzle <tt>root</tt> vymaže sufix reťazca <tt>word</tt> začínajúci na pozícii <tt>index</tt>.  
Ak sa slovo <tt>word</tt> v reprezentovanej množine nenachádza, funkcia <tt>removeFromSubtree</tt> vyhlási chybu pomocou funkcie <tt>assert</tt>.
+
* Funkcia vráti booleovskú hodnotu podľa toho, či sa pri tomto vymazaní sufixu z daného podstromu vymazal jeho koreň <tt>root</tt>.
 +
* Ak sa slovo <tt>word</tt> v reprezentovanej množine nenachádza, funkcia <tt>removeFromSubtree</tt> vyhlási chybu pomocou funkcie <tt>assert</tt>.
  
 
Funkcia <tt>removeFromSubtree</tt> pracuje nasledovne:
 
Funkcia <tt>removeFromSubtree</tt> pracuje nasledovne:
 
* Ak je sufix reťazca <tt>word</tt> začínajúci na indexe <tt>index</tt> prázdny, nastaví hodnotu <tt>root->isWord</tt> na <tt>false</tt>.
 
* Ak je sufix reťazca <tt>word</tt> začínajúci na indexe <tt>index</tt> prázdny, nastaví hodnotu <tt>root->isWord</tt> na <tt>false</tt>.
* V opačnom prípade funkcia <tt>removeFromSubtree</tt> zavolá rekurzívne samú seba pre syna zodpovedajúceho písmenu na pozícii <tt>index</tt> reťazca <tt>word</tt>. Ak toto volanie daného syna zmaže, prestaví smerník na tohto syna na <tt>NULL</tt>.
+
* V opačnom prípade funkcia <tt>removeFromSubtree</tt> zavolá rekurzívne samú seba pre dieťa zodpovedajúce písmenu na pozícii <tt>index</tt> reťazca <tt>word</tt>. Ak toto volanie dané dieťa zmaže, prestaví smerník na toto dieťa na <tt>NULL</tt>.
* V prípade, že po vykonaní jednej z predchádzajúcich dvoch operácií nemá uzol <tt>root</tt> žiadneho syna 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 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 351: 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 357: Riadok 171:
 
     }
 
     }
 
     int numChildren = 0;                         
 
     int numChildren = 0;                         
     for (int i = 0; i <= alphSize - 1; i++) {
+
     for (int i = 0; i < alphSize; i++) {
 
         if (root->children[i] != NULL) {
 
         if (root->children[i] != NULL) {
 
             numChildren++;
 
             numChildren++;
Riadok 365: Riadok 179:
 
         delete root;
 
         delete root;
 
         return true;
 
         return true;
 +
    } else {
 +
        return false;
 
     }
 
     }
    return false;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Samotné odstránenie reťazca <tt>word</tt> z množiny reprezentovanej stromom <tt>t</tt> potom realizuje nasledujúca funkcia <tt>trieRemove</tt>. Tá len zavolá funkciu <tt>removeFromSubtree</tt> pre koreň stromu <tt>t</tt> a v prípade, že volanie tejto funkcie koreň zo stromu odstráni, nastaví <tt>t.root</tt> na <tt>NULL</tt>.
+
Samotné odstránenie reťazca <tt>word</tt> z množiny reprezentovanej stromom <tt>t</tt> potom realizuje funkcia <tt>remove</tt>.  
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void trieRemove(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 koreň odstránený, nastavíme t.root na NULL
 
     if (rootRemoved) {
 
     if (rootRemoved) {
 
         t.root = NULL;
 
         t.root = NULL;
Riadok 381: Riadok 198:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Výška lexikografického stromu ===
+
=== Výška prefixového stromu ===
  
Nasledujúca funkcia vypočíta výšku podstromu zakoreneného v uzle <tt>*root</tt> (definovaná je rovnako ako pre binárne stromy):
+
Nasledujúca funkcia vypočíta výšku podstromu zakoreneného v uzle <tt>root</tt>:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 391: Riadok 208:
 
     }
 
     }
 
     int maxHeight = -1;
 
     int maxHeight = -1;
     for (int i = 0; i <= alphSize - 1; i++) {
+
     for (int i = 0; i < alphSize; i++) {
 
         int height = subtreeHeight(root->children[i]);
 
         int height = subtreeHeight(root->children[i]);
 
         if (height > maxHeight) {
 
         if (height > maxHeight) {
Riadok 401: Riadok 218:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Výšku samotného lexikografického stromu <tt>t</tt> potom spočíta nasledujúca funkcia:
+
Výšku samotného prefixového stromu <tt>t</tt> potom spočíta nasledujúca funkcia:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int trieHeight(trie &t) {
+
int height(trie &t) {
 
     return subtreeHeight(t.root);
 
     return subtreeHeight(t.root);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Vypisovanie slov reprezentovaných lexikografickým stromom ===
+
=== Vypisovanie slov reprezentovaných prefixovým stromom ===
  
Nasledujúca funkcia <tt>printSubtree</tt> prehľadáva podstrom zakorenený v uzle <tt>*root</tt> a v reťazci <tt>s</tt> postupne generuje všetky slová z reprezentovanej množiny, ktoré zároveň vypisuje na konzolu.
+
Nasledujúca funkcia <tt>printSubtree</tt> prehľadáva podstrom zakorenený v uzle <tt>root</tt> a v reťazci <tt>str</tt> postupne generuje všetky slová z reprezentovanej množiny, ktoré zároveň vypisuje na konzolu. V parametri <tt>index</tt> dostane hĺbku aktuálneho vrcholu, t.j. pozíciu v reťazci, na ktorú pridáme ďalší znak.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void printSubtree(node *root, char *s, int index) {
+
void printSubtree(node *root, char *str, int index) {
 
     if (root == NULL) {
 
     if (root == NULL) {
 
         return;
 
         return;
 
     }
 
     }
 
     if (root->isWord) {
 
     if (root->isWord) {
         s[index] = 0;
+
         // ukončíme a vypíšeme reťazec
         printf("%s\n", s);
+
        str[index] = 0;  
 +
         printf("%s\n", str);
 
     }
 
     }
     for (int i = 0; i <= alphSize - 1; i++) {
+
     for (int i = 0; i < alphSize; i++) {
         s[index] = 'a' + i;
+
         str[index] = 'a' + i;
         printSubtree(root->children[i], s, index + 1);
+
         printSubtree(root->children[i], str, index + 1);
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Funkcia <tt>triePrint</tt> vypisujúca všetky slová v množine reprezentovanej lexikografickým stromom <tt>t</tt> najprv spočíta výšku stromu <tt>t</tt>, 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 <tt>printSubtree</tt> pre koreň stromu <tt>t</tt>.
+
Funkcia <tt>printAll</tt> vypisujúca všetky slová v množine reprezentovanej prefixovým stromom <tt>t</tt> najprv spočíta výšku stromu <tt>t</tt>, 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 <tt>printSubtree</tt> pre koreň stromu <tt>t</tt>.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void triePrint(trie &t) {
+
void printAll(trie &t) {
     int height = trieHeight(t);
+
     int height = height(t);
 
     if (height >= 0) {  
 
     if (height >= 0) {  
         char *s = new char[height + 1];
+
         char *str = new char[height + 1];
         printSubtree(t.root, s, 0);
+
         printSubtree(t.root, str, 0);
         delete[] s;
+
         delete[] str;
 
     }
 
     }
 
}   
 
}   
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Program pracujúci s lexikografickými stromami ===
+
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 <tt>isWord</tt> počítadlo <tt>count</tt>, 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 <tt>treeInsert</tt> máme funkciu <tt>treeIncrement</tt>, 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ť <tt>strcmp</tt>, nie <tt>==</tt>, <tt><</tt> 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.)
  
Nasledujúci program načítava z konzoly príkazy zodpovedajúce operáciám na lexikografickom strome a za každým načítaným príkazom túto operáciu vykonáva.
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 452: Riadok 282:
 
using namespace std;
 
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;
 +
}
  
int main(void) {
+
// mazanie podstromu s korenom root
     trie t;
+
void destroySubtree(node *root) {
    trieInit(t);
+
     if (root != NULL) {
    char command[20];
+
         for (int i = 0; i < alphSize; i++) {
    char arg[20];
+
             destroySubtree(root->children[i]);
    while (true) {
 
         scanf("%19s", command);
 
        if (strcmp(command, "insert") == 0) {
 
             scanf("%19s", arg);
 
            trieInsert(t, arg);
 
 
         }
 
         }
         if (strcmp(command, "find") == 0) {
+
         delete root;
            scanf("%19s", arg);
+
    }
            bool b = trieFind(t, arg);
+
}
            if (b) {
+
 
                printf("YES\n");
+
// uvolnenie pamate celeho stromu
            } else {
+
void destroy(trie &t) {
                printf("NO\n");
+
    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();
 
         }
 
         }
         if (strcmp(command, "remove") == 0) {
+
         v = v->children[c];
            scanf("%19s", arg);
+
    }
            trieRemove(t, arg);
+
    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;
 
         }
 
         }
        if (strcmp(command, "height") == 0) {
+
    }
            printf("%d\n", trieHeight(t));
+
    return maxHeight + 1;
        }
+
}
        if (strcmp(command, "print") == 0) {
+
 
            triePrint(t);
+
// vyska stromu. t.j. dlzka najdlsieho slova
         }
+
int height(trie &t) {
         if (strcmp(command, "exit") == 0) {
+
    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;
 
             break;
 
         }
 
         }
 +
// pridanie slova resp. zvysenie pocitadla
 +
        increment(t, word);
 
     }
 
     }
     trieDestroy(t);
+
     // vypis a uvolnenie pamate
     return 0;
+
    printAll(t);
 +
     destroy(t);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
  
 
== Sylaby predmetu ==
 
== Sylaby predmetu ==
Riadok 504: Riadok 419:
 
* funkcie (a parametre funkcií - odovzdávanie hodnotou, referenciou, smerníkom)
 
* funkcie (a parametre funkcií - odovzdávanie hodnotou, referenciou, smerníkom)
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
void f1(int x){}                                 //hodnotou
+
void f1(int x){}           //hodnotou
void f2(int &x){}                               //referenciou
+
void f2(int &x){}           //referenciou
void f3(int* x){}                               //smerníkom
+
void f3(int* x){}           //smerníkom
void f(int a[], int n){}                         //polia bez & (ostanú zmeny)
+
void f(int a[], int n){}   //polia bez & (ostanú zmeny)
void kresli(Turtle &t){}                         //korytnačky, SVGdraw a pod. s &
+
void kresli(Turtle &t){}   //korytnačky, SVGdraw a pod. s &
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Riadok 520: 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;   // ukazovateľ na celočíselnú premennú
+
int * p; // smerník na celočíselnú premennú
  
p = &i;        // spravne
+
p = &i;        // správne
p = &(i + 3);  // zle i+3 nie je premenna
+
p = &(i + 3);  // zle, i+3 nie je premenná
p = &15;        // zle konstanta nema adresu
+
p = &15;        // zle, konštanta nemá adresu
i = *p;         // spravne ak p bol inicializovany
+
i = * p;       // správne ak p bol inicializovaný
  
 
int * cislo = new int;  // alokovanie jednej premennej
 
int * cislo = new int;  // alokovanie jednej premennej
Riadok 545: Riadok 461:
  
 
int a[4];
 
int a[4];
int *b = a;  // a,b su teraz takmer rovnocenne premenne
+
int *b = a;  // a,b su teraz takmer rovnocenné premenné
  
int *A = new int[n]; // alokovanie 1D pola danej dlzky
+
int *b = new int[n]; // alokovanie 1D poľa danej dĺžky
 
..
 
..
delete[] A;
+
delete[] b;
  
 
int **a;      // alokovanie 2D matice
 
int **a;      // alokovanie 2D matice
Riadok 562: 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, find, insert, 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
 
** utriedeného poľa
 
** utriedeného poľa
 
** spájaných zoznamov
 
** spájaných zoznamov
 +
** hašovacej tabuľky
 
** binárnych vyhľadávacích stromov
 
** binárnych vyhľadávacích stromov
** hašovacej tabuľky
+
** prefixového stromu (ak kľúč je reťazec)
** lexikografického stromu (ak kľúč je reťazec)
 
  
 
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
 
* kontrola zátvoriek a vyhodnocovanie výrazov pomocou zásobníka
 
* kontrola zátvoriek a vyhodnocovanie výrazov pomocou zásobníka
 
  
 
=== Dátové štruktúry ===
 
=== Dátové štruktúry ===
Riadok 587: Riadok 502:
 
struct node {
 
struct node {
 
     int data;
 
     int data;
     item* next;
+
     node* next;
 
};
 
};
 
struct linkedList {
 
struct linkedList {
     item* first;
+
     node* first;
 
};
 
};
 
void insertFirst(linkedList &z, int d){
 
void insertFirst(linkedList &z, int d){
 
     /* do zoznamu z vlozi na zaciatok novy prvok s datami d */
 
     /* do zoznamu z vlozi na zaciatok novy prvok s datami d */
     item* p = new item;  // vytvoríme nový prvok
+
     node* p = new node;  // vytvoríme nový prvok
 
     p->data = d;          // naplníme dáta
 
     p->data = d;          // naplníme dáta
     p->next = z.first;    // prvok bude prvým prvkom zoznamu (ukazuje na doterajší začiatok)
+
     p->next = z.first;    // uzol ukazuje na doterajší začiatok
 
     z.first = p;          // tento prvok je novým začiatkom
 
     z.first = p;          // tento prvok je novým začiatkom
 
}
 
}
Riadok 604: Riadok 519:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
struct node {
 
struct node {
     /* vrchol stromu  */
+
     /* uzol stromu  */
 
     dataType data;
 
     dataType data;
     node * left;  /* lavy syn */
+
     node * left;  /* ľavé dieťa */
     node * right; /* pravy syn */
+
     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 621: Riadok 536:
 
* použitie na uloženie aritmetických výrazov
 
* použitie na uloženie aritmetických výrazov
  
[[Image:P22-BST.png|200px|right]]'''Binárne vyhľadávacie stromy'''
+
[[Image:P22-BST.png|thumb|200px|right]]'''Binárne vyhľadávacie stromy'''
 
* vrcholy vľavo od koreňa menší kľúč, vpravo od koreňa väčší
 
* vrcholy vľavo od koreňa menší kľúč, vpravo od koreňa väčší
 
* insert, find, remove v čase závisiacom od hĺbky stromu
 
* insert, find, remove v čase závisiacom od hĺbky stromu
  
[[Image:trie.jpg|right]]'''Lexikografické stromy'''
+
[[Image:trie2.png|thumb|right|Prefixový strom reprezentujúci množinu reťazcov <tt>a, aj, ale, aleba, alebo, cez, na, nad</tt>.]]'''Prefixové stromy'''
 
* ukladajú množinu reťazcov
 
* ukladajú množinu reťazcov
* nie sú binárne: vrchol môže mať veľa synov
+
* 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
 
* insert, find, remove v čase závisiacom od dĺžky kľúča, ale nie od počtu kľúčov, ktoré už sú v strome
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
struct node {
+
struct node { // uzol prefixoveho stromu  
    /* vrchol lexikografickeho stromu */
+
     bool isWord; // je tento uzol koncom slova?
    char data; // pismeno ulozene v tomto vrchole
 
     bool isWord; // je tento vrchol koncom slova?
 
 
     node* next[Abeceda]; // pole smernikov na deti     
 
     node* next[Abeceda]; // pole smernikov na deti     
 
};
 
};
Riadok 641: 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 (0,...,m-1}  
+
* 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
Riadok 650: Riadok 563:
 
}
 
}
 
struct node {
 
struct node {
     int item;
+
     int data;
 
     node* next;
 
     node* next;
 
};
 
};

Aktuálna revízia z 08:01, 11. december 2024

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ý strom reprezentujúci množinu reťazcov a, aj, ale, aleba, alebo, cez, na, nad.

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

PROG-list.png

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
}
Strom pre výraz (65 – 3*5)/(2 + 3)

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
P22-BST.png

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ý strom reprezentujúci množinu reťazcov a, aj, ale, aleba, alebo, cez, na, nad.

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