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 21: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(39 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
  
Plán prednášok a cvičení na zvyšok semestra:
+
Test
* Dnes informácia o skúškach, [[Zimný semester, skúška|detaily skúšky z programovania]], pokračujeme v učive o stromoch
+
* Túto stredu '''11.12. o 18:10''' v posluchárňach F1 a F2 v trvaní 90 minút.
* Tento piatok 10.12. cez cvičenia [[semestrálny test]].  
+
* Viac informácií na stránke [[Zimný semester, semestrálny test]].
* V pondelok 13.12. bude bežná prednáška, pokračujú stromy.
+
* Zajtra pošleme pokyny emailom, prosím pozrite si ich.
* V stredu 15.12. 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 14.12. v rámci cvičení tréning na skúšku.
 
** Už dnes po prednáške sa na testovači objavia 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 piatok 17.12. od 12:00 predtermín skúšky, doplnkové cvičenia nebudú
 
  
== Sylaby predmetu ==
+
Prednášky
=== Základy ===
+
* V stredu v prvej polovici prednášky budú informácie k skúške a rady k skúškovému všeobecne, potom doberieme posledné učivo.
 +
* Budúci pondelok bude nepovinná prednáška o nepreberaných črtách jazykov C a C++.
 +
* Budúcu stredu prednáška nebude.
  
'''Konštrukcie jazyka C'''
+
Cvičenia
* premenné typov <tt>int</tt>, <tt>double</tt>, <tt>char</tt>, <tt>bool</tt>, konverzie medzi nimi
+
* Zajtra normálne cvičenia, dva príklady už máte zverejnené.
* podmienky (<tt>if</tt>, <tt>else</tt>, <tt>switch</tt>), cykly (<tt>for</tt>, <tt>while</tt>)
+
* Piatkové cvičenia povinné pre tých, ktorí v utorok nevyriešia rozcvičku.
* funkcie (a parametre funkcií - odovzdávanie hodnotou, referenciou, smerníkom)
+
* Budúci utorok v rámci cvičení tréning na skúšku.
<syntaxhighlight lang="C++">
+
* V piatok 20.12. od 13:10 predtermín skúšky, cvičenia nebudú.
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 &
 
</syntaxhighlight>
 
 
 
'''Polia, reťazce''' (char[])
 
<syntaxhighlight lang="C++">
 
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};
 
</syntaxhighlight>
 
* funkcie strlen, strcpy, strcmp, strcat
 
 
 
'''Súbory, spracovanie vstupu'''
 
* cin, cout alebo printf, scanf
 
* 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'''
 
<syntaxhighlight lang="C++">
 
int i;    // „klasická“ celočíselná premenná
 
int *p;  // ukazovateľ na celočíselnú premennú
 
 
 
p = &i;        // spravne
 
p = &(i + 3);  // zle i+3 nie je premenna
 
p = &15;        // zle konstanta nema adresu
 
i = *p;        // spravne ak p bol inicializovany
 
 
 
int * cislo = new int;  // alokovanie jednej premennej
 
*cislo = 50;
 
..
 
delete cislo;
 
 
 
int a[4];
 
int *b = a;  // a,b su teraz takmer rovnocenne premenne
 
 
 
int *A = new int[n]; // alokovanie 1D pola danej dlzky
 
..
 
delete[] A;
 
 
 
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;
 
</syntaxhighlight>
 
 
 
=== Abstraktné dátové typy ===
 
 
 
Abstraktný dátový typ '''dynamické pole''' (rastúce pole)
 
* operácie init, add, get, set, length
 
 
 
Abstraktný dátový typ '''množina''' (set)
 
* operácie init, find, insert, remove
 
* implementácie pomocou
 
** neutriedeného poľa
 
** utriedeného poľa
 
** spájaných zoznamov
 
** binárnych vyhľadávacích stromov
 
** hašovacej tabuľky
 
** lexikografického stromu (ak kľúč je reťazec)
 
 
 
<!--
 
Abstraktný dátový typ '''slovník''' (asociatívne pole, map)
 
* kľúče a ďalšie dáta
 
* operácie init, insert, find, remove (hľadáme podľa kľúča)
 
* implementácie podobné ako množina
 
-->
 
 
 
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 ===
 
[[Image:PROG-list.png|right|200px]]'''Spájané zoznamy'''
 
<syntaxhighlight lang="C++">
 
struct node {
 
    int data;
 
    item* next;
 
};
 
struct linkedList {
 
    item* first;
 
};
 
void insertFirst(linkedList &z, int d){
 
    /* do zoznamu z vlozi na zaciatok novy prvok s datami d */
 
    item* p = new item;  // vytvoríme nový prvok
 
    p->data = d;          // naplníme dáta
 
    p->next = z.first;    // prvok bude prvým prvkom zoznamu (ukazuje na doterajší začiatok)
 
    z.first = p;          // tento prvok je novým začiatkom
 
}
 
</syntaxhighlight>
 
  
[[Image:PROG-P21-aritm.png|thumb|right|Strom pre výraz (65 – 3*5)/(2 + 3)]]'''Binárne stromy'''
+
== Opakovanie: aritmetický výraz ako strom ==
<syntaxhighlight lang="C++">
 
struct node {
 
    /* vrchol stromu  */
 
    dataType data;
 
    node * left;  /* lavy syn */
 
    node * right; /* pravy syn */
 
};
 
  
node * createNode(dataType data, node *left, node *right) {
+
[[Image:PROG-P21-aritm.png|thumb|right|Strom pre výraz <tt>(65 – 3 * 5)/(2 + 3)</tt>]]
    node *v = new node;
 
    v->data = data;
 
    v->left = left;
 
    v->right = right;
 
    return v;
 
}
 
</syntaxhighlight>
 
* prehľadávanie inorder, preorder, postorder
 
* použitie na uloženie aritmetických výrazov
 
  
[[Image:P22-BST.png|200px|right]]'''Binárne vyhľadávacie stromy'''
+
Uzol takéhoto stromu môžeme reprezentovať nasledujúcou štruktúrou:
* vrcholy vľavo od koreňa menší kľúč, vpravo od koreňa väčší
 
* insert, find, remove v čase závisiacom od hĺbky stromu
 
  
[[Image:trie.jpg|right]]'''Lexikografické stromy'''
 
* ukladajú množinu reťazcov
 
* nie sú binárne: vrchol môže mať veľa synov
 
* 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 treeNode {
     /* vrchol lexikografickeho stromu  */
+
     // číselná hodnota (len v listoch)
    char data; // pismeno ulozene v tomto vrchole
+
     double val;
    bool isWord; // je tento vrchol koncom slova?
 
     node* next[Abeceda]; // pole smernikov na deti   
 
};
 
</syntaxhighlight>
 
  
 +
    // operátor vo vnútorných uzloch, pre listy medzera
 +
    char op;
  
'''Hašovanie'''
+
    // smerníky na podstromy
* hašovacia tabuľka veľkosti ''m''
+
     treeNode * left, * right;
* 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
 
<syntaxhighlight lang="C++">
 
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 item;
 
     node* next;
 
};
 
 
 
struct set {
 
    node** data;
 
    int m;
 
 
};
 
};
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Algoritmy ===
+
* Vnútorné uzly stromu zodpovedajú operátorom
 +
* Listy zodpovedajú číselným hodnotám
  
'''Rekurzia'''
+
Videli sme
* Rekurzívne funkcie
+
* Štruktúru <tt>treeNode</tt>
* Vykresľovanie fraktálov
+
* Vytvorenie stromu z postfixového výrazu (podobné na vyhodnocovanie, využíva sa zásobník hotových podstromov)
* Prehľadávanie s návratom (backtracking)
+
* Vyhodnotenie výrazu, výpis v postfixovej, prefixovej a infixovej forme, uvoľnenie pamäte stromu (jednoduché rekurzívne funkcie)
* 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
 
  
 +
Zaoberali sme sa aj všeobecnými binárnymi stromami, videli sme
 +
* Terminológiu
 +
* Štruktúru <tt>node</tt>
 +
* Výpis v poradí preorder, inorder, postorder, uvoľnenie pamäte stromu (jednoduchá rekurzia, podobne ako pre výrazy)
 +
* Hĺbka vrchola a výška stromu
  
 
== Binárne vyhľadávacie stromy ==
 
== Binárne vyhľadávacie stromy ==
  
 
[[Image:P22-BST.png|200px|right|thumb|Príklad binárneho vyhľadávacieho stromu.]]
 
[[Image:P22-BST.png|200px|right|thumb|Príklad binárneho vyhľadávacieho stromu.]]
Stromy sa v informatike často používajú. Ďalším príkladom sú binárne vyhľadávacie stromy, ktoré slúžia na ukladanie množiny prvkov. Prvky množiny teda nemáme v poli, ani v spájanom zozname, ale vo vrcholoch binárneho stromu.
+
Stromy sa v informatike používajú na veľa účelov. Ďalším príkladom sú binárne vyhľadávacie stromy, ktoré môžu slúžiť na implementovanie abstraktného dátového typu dynamická množina, ktorý sme videli na [[Prednáška 14#Dynamick.C3.A1_mno.C5.BEina_2|prednáške 14]]. Prvky množiny nebudeme ukladať  do poľa, ani v spájaného zoznamu, ale do vrcholoch binárneho stromu.
* V binárnom vyhľadávacom strome má každý vrchol 0,1 alebo 2 deti
+
* 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>)
+
* V každom vrchole máme položku s dátami (pre jednoduchosť typu <tt>int</tt>), tieto dáta niekedy voláme aj kľúč
 
* Pre každý vrchol ''v'' stromu platí:
 
* 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 ľavom podstrome ''v'' má hodnotu <tt>data</tt> menšiu ako vrchol ''v''
Riadok 211: Riadok 62:
 
* Z toho vyplýva, že ak vypíšeme strom v inorder poradí, dostaneme prvky usporiadané
 
* Z toho vyplýva, že ak vypíšeme strom v inorder poradí, dostaneme prvky usporiadané
 
* Pre danú množinu kľúčov existuje veľa vyhľadávacích stromov
 
* Pre danú množinu kľúčov existuje veľa vyhľadávacích stromov
 
  
 
''Cvičenie'': nájdite všetky binárne vyhľadávacie stromy pozostávajúce z troch uzlov s kľúčmi ''1, 2, 3''.
 
''Cvičenie'': nájdite všetky binárne vyhľadávacie stromy pozostávajúce z troch uzlov s kľúčmi ''1, 2, 3''.
Riadok 225: Riadok 75:
 
struct node {
 
struct node {
 
     /* vrchol binárneho vyhľadávacieho stromu  */
 
     /* vrchol binárneho vyhľadávacieho stromu  */
     int data;      /* hodnota */
+
     int data;      /* hodnota uložená vo vrchole */
 
     node * parent;  /* rodič vrchola, NULL v koreni */
 
     node * parent;  /* rodič vrchola, NULL v koreni */
 
     node * left;    /* ľavé dieťa, NULL ak neexistuje */
 
     node * left;    /* ľavé dieťa, NULL ak neexistuje */
Riadok 231: Riadok 81:
 
};
 
};
  
/* Samotná štruktúra binárneho vyhľadávacieho stromu (obal pre používateľa). */
+
/* Samotná dynamická množina (obal pre používateľa). */
struct binarySearchTree {
+
struct set {
 
     node *root;  /* koreň stromu, NULL pre prázdny strom */
 
     node *root;  /* koreň stromu, NULL pre prázdny strom */
 
};
 
};
Riadok 239: Riadok 89:
 
Inicializácia prázdneho binárneho vyhľadávacieho stromu
 
Inicializácia prázdneho binárneho vyhľadávacieho stromu
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
/** Funkcia inicializuje prázdny binárny vyhľadávací strom */
+
/* Funkcia inicializuje prázdny binárny vyhľadávací strom */
void init(binarySearchTree &t) {  
+
void init(set &s) {
     t.root = NULL;
+
     s.root = NULL;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 247: Riadok 97:
 
=== Likvidácia binárneho vyhľadávacieho stromu ===
 
=== Likvidácia binárneho vyhľadávacieho stromu ===
  
Likvidáciu podstromu zakoreneného v danom uzle <tt>root</tt> realizujeme funkciou <tt>destroy</tt>, obdobne ako pri všeobecných binárnych stromoch. Používateľovi navyše dáme k dispozícii aj funkciu <tt>bstDestroy</tt>, ktorá zlikviduje binárny vyhľadávací strom <tt>t</tt> tak, že zavolá funkciu <tt>destroy</tt> na jeho koreň.
+
Likvidáciu podstromu zakoreneného v danom uzle <tt>root</tt> realizujeme funkciou <tt>destroy</tt>, obdobne ako pri všeobecných binárnych stromoch. Vytvoríme aj funkciu <tt>destroy</tt>, ktorá dostane množinu implementovanú ako strom a tento strom zlikviduje.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
/* Uvolni pamat pre podstrom s korenom *root. */
+
/* Uvoľní pamäť pre strom s koreňom root */
 
void destroy(node *root) {
 
void destroy(node *root) {
 
     if (root != NULL) {
 
     if (root != NULL) {
Riadok 259: Riadok 109:
 
}
 
}
  
/* Zlikviduje strom t (uvolni pamat). */
+
/* Zlikviduje množinu s (uvoľní pamäť) */
void bstDestroy(binarySearchTree &t) {
+
void destroy(set &s) {
     destroy(t.root);
+
     destroy(s.root);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 267: Riadok 117:
 
=== Hľadanie v binárnom vyhľadávacom strome ===
 
=== Hľadanie v binárnom vyhľadávacom strome ===
  
Nasledujúca funkcia <tt>findNode</tt> sa pokúsi v podstrome zakorenenom v uzle <tt>*root</tt> vyhľadať uzol, ktorého kľúč je rovný <tt>key</tt>. Ak existuje aspoň jeden taký uzol, vráti smerník na niektorý z nich (to je užitočné najmä v prípade, keď sú kľúče po dvoch rôzne). V opačnom prípade vráti <tt>NULL</tt>.
+
Funkcia <tt>findNode</tt> v podstrome s koreňom <tt>root</tt> hľadá uzol, ktorého položka dáta obsahuje hľadanú hodnotu <tt>x</tt>. Vráti takýto uzol alebo NULL ak neexistuje.
 +
* Najskôr porovná hľadaný kľúč s dátami v koreni:
 +
** ak sa rovnajú, končíme (našli sme, čo hľadáme),
 +
** ak je <tt>x</tt> menšie ako dáta v koreni, musí byť v ľavom podstrome,
 +
** ak je <tt>x</tt> väčšie, musí byť v pravom podstrome.
 +
* V príslušnom podstrome sa rozhodujeme podľa tých istých pravidiel.
 +
* Keď narazíme na prázdny podstrom, dáta sa v strome nenachádzajú.
 +
* Dá sa zapísať rekurzívne alebo cyklom, lebo vždy ideme iba do jedného podstromu.
  
Pri hľadaní uzla s hodnotou <tt>key</tt> bude funkcia <tt>findNode</tt> využívať definujúcu vlastnosť binárnych vyhľadávacích stromov: ak je hľadaná hodnota kľúča <tt>key</tt> menšia, než kľúč koreňa podstromu <tt>*root</tt>, pokračuje v hľadaní v jeho ľavom podstrome; ak je naopak väčšia, pokračuje v hľadaní v jeho pravom podstrome. Ak je kľúč koreňa <tt>*root</tt> rovný <tt>key</tt>, ide o hľadaný uzol a smerník naň tak možno ihneď vrátiť na výstupe.
+
Funkcia <tt>contains</tt> zavolá funkciu <tt>findNode</tt> pre koreň daného binárneho vyhľadávacieho stromu <tt>t</tt> a pomocou nej zistí, či tento strom obsahuje uzol s kľúčom <tt>x</tt>.
  
Používateľovi pritom opäť poskytneme aj &bdquo;baliacu&rdquo; funkciu <tt>bstFind</tt>, ktorá zavolá funkciu <tt>findNode</tt> pre koreň daného binárneho vyhľadávacieho stromu <tt>t</tt> a pomocou nej zistí, či tento strom obsahuje uzol s kľúčom <tt>key</tt>.
+
<syntaxhighlight lang="C++">
 +
/* Ak v strome s koreňom root existuje uzol s kľúčom x,  
 +
* vráti ho. Inak vráti NULL. */
 +
node * findNode(node *root, int x) {
 +
    node * v = root;
 +
    while (v != NULL && v->data != x) {
 +
        if (x < v->data) {
 +
            v = v->left;
 +
        } else {
 +
            v = v->right;
 +
        }
 +
    }
 +
    return v;
 +
}
  
<syntaxhighlight lang="C++">
+
/* Rekurzívna verzia */
/* Ak v strome s korenom *root existuje uzol s klucom key, vrati ho na vystupe. Inak vrati NULL. */
+
node *findNodeR(node *root, int x) {
node *findNode(node *root, int key) {
+
     if (root == NULL || x == root->data) {
     if (root == NULL || root->key == key) {
 
 
         return root;
 
         return root;
     } else if (key < root->key) {
+
     } else if (x < root->data) {
         return findNode(root->left, key);
+
         return findNodeR(root->left, x);
     } else {
+
     } else { // x > root->data
         return findNode(root->right, key);
+
         return findNodeR(root->right, x);
 
     }
 
     }
 
}
 
}
  
 +
/* Zistí, či strom reprezentujúci množinu s
 +
* obsahuje uzol s kľúčom x. */
 +
bool contains(set &s, int x) {
 +
    return findNode(s.root, x) != NULL;
 +
}
 +
</syntaxhighlight>
  
// ...
+
Čas výpočtu je v najhoršom prípade úmerný výške stromu.
  
  
/* Zisti, ci strom t obsahuje uzol s klucom key. */
+
=== Vkladanie do binárneho vyhľadávacieho stromu ===
bool bstFind(binarySearchTree &t, int key) {
 
    return findNode(t.root, key) != NULL;
 
}
 
</syntaxhighlight>
 
  
Čas výpočtu je v najhoršom prípade úmerný výške stromu. Poznamenajme ešte, že funkciu <tt>findNode</tt> je možné realizovať aj nerekurzívne, napríklad takto:
+
Nasledujúca funkcia <tt>insertNode</tt> vloží nový uzol s kľúčom <tt>x</tt> na správne miesto podstromu zakoreneného v <tt>*root</tt>.  
 +
* 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, ale dá sa aj cyklom, podobne ako pri hľadaní
 +
* Funkcia <tt>add</tt> vloží nový uzol pomocou funkcie <tt>insertNode</tt> ale zvlášť ošetrí prípad, keď ide o prvý uzol.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
node *findNode(node *root, int key) {
+
/* Funkcia vytvorí uzol s daným kľúčom
     node *v = root;
+
* a rodičom, deti nastaví na NULL. */
     while (v != NULL && v->key != key) {
+
node * createBSTNode(int data, node * parent) {
        if (key < v->key) {
+
     node *v = new node;
            v = v->left;
+
     v->data = data;
        } else {
+
    v->left = NULL;
            v = v->right;
+
    v->right = NULL;
        }
+
    v->parent = parent;
    }
 
 
     return v;
 
     return v;
 
}
 
}
</syntaxhighlight>
 
  
=== Vkladanie do binárneho vyhľadávacieho stromu ===
+
/* Vloží nový uzol s hodnotou x
 
+
* na správne miesto stromu s koreňom root */
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. Postupuje pritom rekurzívne: ak zistí, že uzol <tt>*v</tt> má kľúč menší, než <tt>*root</tt>, pokúsi sa ho vložiť do ľavého podstromu uzla <tt>*root</tt>; v opačnom prípade sa ho pokúsi vložiť do pravého podstromu.
+
void insertNode(node *root, int x) {
 
+
     assert(root != NULL);
Používateľovi následne poskytneme &bdquo;baliacu&rdquo; funkciu <tt>bstInsert</tt>, ktorá 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>.
+
     if (x < root->data) {
 
 
<syntaxhighlight lang="C++">
 
/* Vlozi uzol *v na spravne miesto podstromu zakoreneneho v *root */
 
void insertNode(node *root, node *v) {
 
     assert(root != NULL && v != NULL);
 
     if (v->key < root->key) {
 
 
         if (root->left == NULL) {
 
         if (root->left == NULL) {
             root->left = v;
+
             root->left = createBSTNode(x, root);
            v->parent = root;
 
 
         } else {
 
         } else {
             insertNode(root->left, v);
+
             insertNode(root->left, x);
 
         }
 
         }
 
     } else {
 
     } else {
 
         if (root->right == NULL) {
 
         if (root->right == NULL) {
             root->right = v;
+
             root->right = createBSTNode(x, root);
            v->parent = root;
 
 
         } else {
 
         } else {
             insertNode(root->right, v);
+
             insertNode(root->right, x);
 
         }
 
         }
 
     }
 
     }
 
}
 
}
  
 
+
/* Vloží do stromu pre množinu s nový uzol s kľúčom x */
// ...
+
void add(set &s, int x) {
 
+
     if (s.root == NULL) {
 
+
         // špeciálny prípad, keď vkladáme prvý uzol
/* Vlozi do stromu t novy uzol s klucom key. */
+
        s.root = createBSTNode(x, NULL);
void bstInsert(binarySearchTree &t, int key) {
 
    node *v = new node;
 
    v->key = key;
 
    v->left = NULL;
 
    v->right = NULL;
 
    v->parent = NULL;
 
     if (t.root == NULL) {
 
         t.root = v;
 
 
     } else {
 
     } else {
         insertNode(t.root, v);
+
         insertNode(s.root, x);
 
     }
 
     }
 
}
 
}
Riadok 359: Riadok 217:
 
Čas vkladania je tiež v najhoršom prípade úmerný hĺbke stromu.
 
Čas vkladania je tiež v najhoršom prípade úmerný hĺbke stromu.
  
''Cvičenie č. 1'': napíšte nerekurzívny variant funkcie <tt>insertNode</tt>.
+
'''Cvičenia'''
 +
* Ako bude vyzerať strom po nasledujúcej postupnosti operácií?
 +
<pre>
 +
    set s;
 +
    init(s);
 +
    add(s, 2);
 +
    add(s, 5);
 +
    add(s, 3);
 +
    add(s, 10);
 +
    add(s, 7); 
 +
</pre>
 +
* Napíšte nerekurzívny variant funkcie <tt>insertNode</tt>.
 +
* Do funkcie doplňte <tt>assert</tt>, ktorý deteguje prípad, že vkladaná hodnota sa už v strome nachádza.
 +
* Napíšte funkciu <tt>treeSort</tt>, ktorá z poľa celých čísel <tt>a</tt> pomocou volaní funkcie <tt>add</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.
 +
 
 +
===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 <tt>data</tt> v uzloch.
 +
* Dá sa napísať cyklom aj rekurzívne.
 +
* Obalom pre používateľa bude funkcia <tt>min</tt>, ktorá pomocou funkcie <tt>minNode</tt> nájde minimálny kľúč v danej množine.
 +
 
 +
<syntaxhighlight lang="C++">
 +
/* Vráti uzol s minimálnym kľúčom v strome s koreňom v */
 +
node *minNode(node *v) {
 +
    assert(v != NULL);
 +
    while (v->left != NULL) {
 +
        v = v->left;
 +
    }
 +
    return v;
 +
}
  
''Cvičenie č. 2'': 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.
+
/* Vráti minimálnu hodnotu v množine s */
 +
int min(set &s) {
 +
    assert(s.root != NULL);
 +
    return minNode(s.root)->data;
 +
}
 +
</syntaxhighlight>
  
=== Minimálny uzol ===
+
''Cvičenia'':
 +
* Napíšte rekurzívny variant funkcie <tt>minNode</tt>.
 +
* Ako by bolo treba funkciu zmeniť, aby hľadala maximum?
  
Nasledujúca funkcia <tt>minNode</tt> nájde v podstrome zakorenenom v <tt>*root</tt> uzol s minimálnym kľúčom. Je pritom založená na skutočnosti, že všetky uzly tohto podstromu s kľúčom menším ako <tt>root->key</tt> sa musia nachádzať v ľavom podstrome uzla <tt>*root</tt>.
 
  
&bdquo;Obalom&rdquo; 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>.
+
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>.
 +
* Ak má uzol <tt>v</tt> pravé dieťa, následník uzla <tt>v</tt> bude vrchol s minimálnym kľúčom v pravom podstrome.
 +
* V opačnom prípade môže byť následníkom uzla <tt>v</tt> jeho rodič, ak <tt>v</tt> je jeho ľavé dieťa.
 +
* Ak je <tt>v</tt> pravým dieťaťom svojho rodiča, môže to byť jeho prarodič (ak je rodič uzla <tt>v</tt> ľavým dieťaťom tohto prarodiča), 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 (najväčší prvok).
 +
** Ako presne sa bude funkcia nižšie pre tento prvok správať?
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
/* Vrati (niektory) uzol s minimalnou hodnotou key v podstrome s korenom *root. */
+
/* Vráti uzol, ktorý vo vzostupnom poradí uzlov
node *minNode(node *root) {
+
* podľa kľúčov nasleduje za v.
     assert(root != NULL);
+
* Ak taký uzol neexistuje, vráti NULL. */
     if (root->left != NULL) {
+
node *successorNode(node *v) {
         return minNode(root->left);
+
     assert(v != NULL);
     } else {
+
     if (v->right != NULL) {
         return root;
+
         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>remove</tt> zmaže z binárneho vyhľadávacieho stromu uzol s kľúčom <tt>x</tt> (predpokladá, že taký je).  
  
 +
* Najprv pomocou funkcie <tt>findNode</tt> nájde uzol <tt>v</tt> s kľúčom <tt>x</tt>.
 +
* Ak je ''v'' list, jednoducho ho zmažeme.
 +
* Ak má ''v'' jedno dieťa, toto dieťa prevesíme priamo pod rodiča ''v'' a ''v'' zmažeme.
 +
* Ak má ''v'' dve deti, nájdeme nasledovníka ''v'', t.j. minimum v pravom podstrome ''v''.
 +
* Tento nasledovník nemá ľavé dieťa, vieme ho teda zmazať.
 +
* Jeho údaje presunieme do vrcholu ''v''.
 +
* Tiež treba dať pozor na mazanie koreňa.
  
/* Vrati minimalny kluc uzla v strome t. */
+
<syntaxhighlight lang="C++">
int bstMin(binarySearchTree &t) {
+
/* Zmaže zo stromu pre množinu s uzol s kľúčom x */
     assert(t.root != NULL);
+
void remove(set &s, int x) {
     return minNode(t.root)->key;  
+
     // Nájde uzol s hodnotou, ktorú treba vymazať.
}  
+
    node *v = findNode(s.root, x);
 +
    // Skontrolujme, že požadovaný uzol existuje
 +
    assert(v != NULL);
 +
   
 +
    // Nájde uzol *rm, ktorý sa reálne zmaže
 +
    node *rm;                                         
 +
    if (v->left == NULL || v->right == NULL) {       
 +
        rm = v;
 +
    } else  {
 +
        rm = successorNode(v);
 +
        // Presunie kľúč uzla *rm do uzla *v
 +
        v->data = rm->data;
 +
    }
 +
 
 +
    // ak má uzol rm dieťa, jeho rodičom bude rodič rm
 +
    node *child;                                     
 +
    if (rm->left != NULL) {
 +
        child = rm->left;
 +
    } else {
 +
        child = rm->right;
 +
    }
 +
    if (child != NULL) {
 +
        child->parent = rm->parent;
 +
     }
 +
    if (rm->parent == NULL) {
 +
        s.root = child;
 +
    } else if (rm == rm->parent->left) {
 +
        rm->parent->left = child;   
 +
    } else if (rm == rm->parent->right) {
 +
        rm->parent->right = child;
 +
    }
 +
    // rm už nie je v strome, uvoľníme jeho pamäť
 +
    delete rm;
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
''Cvičenie'': napíšte nerekurzívny variant funkcie <tt>minNode</tt>.
+
=== Zložitosť jednotlivých operácií ===
<!--=== Príklad programu pracujúceho s binárnymi vyhľadávacími stromami ===
 
  
Nasledujúci program realizuje základné operácie s binárnymi vyhľadávacími stromami podľa príkazov zadávaných používateľom na konzolu.
+
* Časová zložitosť operácií <tt>contains</tt>, <tt>add</tt> aj <tt>remove</tt> je úmerná výške stromu, ktorú označíme <tt>h</tt>.
 +
* Minule sme ukázali, že pre strom s ''n'' uzlami máme 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 ak sa prvky vkladajú v náhodnom poradí, výška stromu bude v priemere 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.
 +
 
 +
== Ukážkový program s binárnymi vyhľadávacími stromami ==
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <cstdio>
+
#include <iostream>
 +
#include <cassert>
 
using namespace std;
 
using namespace std;
  
 +
struct node {
 +
    /* vrchol binárneho vyhľadávacieho stromu  */
 +
    int data;      /* hodnota uložená vo vrchole */
 +
    node * parent;  /* rodič vrchola, NULL v koreni */
 +
    node * left;    /* ľavé dieťa, NULL ak neexistuje */
 +
    node * right;  /* pravé dieťa, NULL ak neexistuje */
 +
};
  
// ...
+
/* Samotná dynamická množina (obal pre používateľa). */
 +
struct set {
 +
    node *root;  /* koreň stromu, NULL pre prázdny strom */
 +
};
  
 +
/* Funkcia inicializuje prázdny binárny vyhľadávací strom */
 +
void init(set &s) {
 +
    s.root = NULL;
 +
}
  
int main(void) {
+
/* Uvoľní pamäť pre strom s koreňom root */
     binarySearchTree t;
+
void destroy(node *root) {
    bstInit(t);
+
     if (root != NULL) {
    char command[20];
+
        destroy(root->left);
     int key;
+
        destroy(root->right);
    while (true) {
+
        delete root;
        scanf("%19s", command);
+
     }
        if (strcmp(command, "insert") == 0) {
+
}
            scanf("%d", &key);
+
 
             bstInsert(t, key);
+
/* Zlikviduje množinu s (uvoľní pamäť) */
 +
void destroy(set &s) {
 +
    destroy(s.root);
 +
}
 +
 
 +
/* Ak v strome s koreňom root existuje uzol s kľúčom x,
 +
* vráti ho. Inak vráti NULL. */
 +
node * findNode(node *root, int x) {
 +
    node * v = root;
 +
    while (v != NULL && v->data != x) {
 +
        if (x < v->data) {
 +
            v = v->left;
 +
        } else {
 +
             v = v->right;
 
         }
 
         }
         if (strcmp(command, "find") == 0) {
+
    }
            scanf("%d", &key);
+
    return v;
            bool b = bstFind(t, key);
+
}
            if (b) {
+
 
                printf("YES\n");
+
/* Rekurzívna verzia */
            } else {
+
node *findNodeR(node *root, int x) {
                printf("NO\n");
+
    if (root == NULL || x == root->data) {
            }
+
         return root;
 +
    } else if (x < root->data) {
 +
        return findNodeR(root->left, x);
 +
    } else {  // x > root->data
 +
        return findNodeR(root->right, x);
 +
    }
 +
}
 +
 
 +
/* Zistí, či strom reprezentujúci množinu s
 +
* obsahuje uzol s kľúčom x. */
 +
bool contains(set &s, int x) {
 +
    return findNode(s.root, x) != NULL;
 +
}
 +
 
 +
/* Funkcia vytvorí uzol s daným kľúčom
 +
* a rodičom, deti nastaví na NULL. */
 +
node * createBSTNode(int data, node * parent) {
 +
    node *v = new node;
 +
    v->data = data;
 +
    v->left = NULL;
 +
    v->right = NULL;
 +
    v->parent = parent;
 +
    return v;
 +
}
 +
 
 +
/* Vloží nový uzol s hodnotou x
 +
* na správne miesto podstromu zakoreneného v root */
 +
void insertNode(node *root, int x) {
 +
    assert(root != NULL);
 +
    if (x < root->data) {
 +
        if (root->left == NULL) {
 +
            root->left = createBSTNode(x, root);
 +
        } else {
 +
            insertNode(root->left, x);
 
         }
 
         }
         if (strcmp(command, "min") == 0) {
+
    } else {
             printf("%d\n", bstMin(t));
+
         if (root->right == NULL) {
         }
+
             root->right = createBSTNode(x, root);
        if (strcmp(command, "exit") == 0) {
+
         } else {
            break;
+
            insertNode(root->right, x);
 
         }
 
         }
 
     }
 
     }
    bstDestroy(t);                   
 
    return 0;
 
 
}
 
}
</syntaxhighlight>-->
 
  
=== Následník uzla ===
+
/* Vloží do stromu pre množinu s nový uzol s kľúčom x */
 +
void add(set &s, int x) {
 +
    if (s.root == NULL) {
 +
        // špeciálny prípad, keď vkladáme prvý uzol
 +
        s.root = createBSTNode(x, NULL);
 +
    } else {
 +
        insertNode(s.root, x);
 +
    }
 +
}
 +
 
 +
/* Vráti uzol s minimálnym kľúčom v strome s koreňom v */
 +
node *minNode(node *v) {
 +
    assert(v != NULL);
 +
    while (v->left != NULL) {
 +
        v = v->left;
 +
    }
 +
    return v;
 +
}
  
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 &ndash; č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:
+
/* Vráti minimálnu hodnotu v množine s */
* 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.
+
int min(set &s) {
* 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.
+
    assert(s.root != NULL);
* V strome existuje práve jeden uzol bez následníka (jeden spomedzi najväčších prvkov).
+
    return minNode(s.root)->data;
 +
}
  
<syntaxhighlight lang="C++">
+
/* Vráti uzol, ktorý vo vzostupnom poradí uzlov
/* Vrati uzol, ktory vo vzostupnom poradi uzlov podla klucov nasleduje za *v. Ak taky uzol neexistuje, vrati NULL. */
+
* podľa kľúčov nasleduje za v.  
 +
* Ak taký uzol neexistuje, vráti NULL. */
 
node *successorNode(node *v) {
 
node *successorNode(node *v) {
 
     assert(v != NULL);
 
     assert(v != NULL);
Riadok 450: Riadok 483:
 
         return minNode(v->right);
 
         return minNode(v->right);
 
     }
 
     }
     while (v->parent != NULL && v == v->parent->right) {
+
     while (v->parent != NULL  
 +
          && v == v->parent->right) {
 
         v = v->parent;
 
         v = v->parent;
 
     }
 
     }
 
     return 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.
+
/* Zmaže zo stromu pre množinu s uzol s kľúčom x */
 
+
void remove(set &s, int x) {
<syntaxhighlight lang="C++">
+
    // Nájde uzol s hodnotou, ktorú treba vymazať.
/* Zmaze zo stromu t prave jeden uzol s klucom key (ak tam taky je). */
+
     node *v = findNode(s.root, x);
void bstRemove(binarySearchTree &t, int key) {
+
    // Skontrolujme, že požadovaný uzol existuje
     node *v = findNode(t.root, key);                   // Najde uzol v s hodnotou, ktoru treba vymazat.
+
     assert(v != NULL);
     if (v == NULL) {
 
        return;
 
    }
 
 
      
 
      
     node *rm;                                          // Najde uzol *rm stromu t, ktory sa napokon realne zmaze. 
+
     // Nájde uzol *rm, ktorý sa reálne zmaže
 +
    node *rm;                                         
 
     if (v->left == NULL || v->right == NULL) {         
 
     if (v->left == NULL || v->right == NULL) {         
 
         rm = v;
 
         rm = v;
 
     } else  {
 
     } else  {
 
         rm = successorNode(v);
 
         rm = successorNode(v);
 +
        // Presunie kľúč uzla *rm do uzla *v
 +
        v->data = rm->data;
 
     }
 
     }
 
+
 
     if (rm != v) {                                    // Ak rm != v, presunie kluc uzla *rm do uzla *v.
+
     // ak má uzol rm dieťa, jeho rodičom bude rodic rm
        v->key = rm->key;
+
     node *child;                                    
    }
 
   
 
     node *child;                                       // Zmaze uzol *rm a uvolni pamat alokovanu pre tento uzol.
 
 
     if (rm->left != NULL) {
 
     if (rm->left != NULL) {
 
         child = rm->left;
 
         child = rm->left;
 
     } else {
 
     } else {
 
         child = rm->right;
 
         child = rm->right;
     }                  
+
     }
 
     if (child != NULL) {
 
     if (child != NULL) {
 
         child->parent = rm->parent;
 
         child->parent = rm->parent;
 
     }
 
     }
 
     if (rm->parent == NULL) {
 
     if (rm->parent == NULL) {
         t.root = child;
+
         s.root = child;
 
     } else if (rm == rm->parent->left) {
 
     } else if (rm == rm->parent->left) {
 
         rm->parent->left = child;     
 
         rm->parent->left = child;     
Riadok 496: Riadok 525:
 
         rm->parent->right = child;
 
         rm->parent->right = child;
 
     }
 
     }
 +
    // rm už nie je v strome, uvoľníme jeho pamäť
 
     delete rm;
 
     delete rm;
 
}
 
}
</syntaxhighlight>
 
  
=== Zložitosť jednotlivých operácií ===
+
/* Vypíše podstrom s koreňom * root v poradí preorder */
 +
void printInorder(node * root) {
 +
    if (root != NULL) {
 +
        cout << " " << root->data;
 +
        printInorder(root->left);
 +
        printInorder(root->right);
 +
  }
 +
}
  
* Č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>.
+
int main() {
* 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.
 
  
=== Príklad programu pracujúceho s binárnymi vyhľadávacími stromami ===
+
    set s;
 +
    init(s);
  
Nasledujúci program realizuje základné operácie s binárnymi vyhľadávacími stromami podľa príkazov zadávaných používateľom na konzolu.
+
    cout << "Vkladame 2,5,3,10,7 do s." << endl;
 +
    add(s, 2);
 +
    add(s, 5);
 +
    add(s, 3);
 +
    add(s, 10);
 +
    add(s, 7); 
  
<syntaxhighlight lang="C++">
+
    cout << "Obsahuje s cisla 2 a 4?" << endl;
#include <cstdio>
+
    cout << contains(s, 2) << endl;
#include <cstring>
+
    cout << contains(s, 4) << endl;
#include <cassert>
 
using namespace std;
 
  
 +
    cout << "Minimum s: " << min(s) << endl;;
  
// ...
+
    cout << "Vypis v inorder poradi" << endl;
 +
    printInorder(s.root);
 +
    cout << endl;
  
 +
    cout << "Mazeme 2,5,10 z s." << endl;
 +
    remove(s, 5);
 +
    remove(s, 2);
 +
    remove(s, 10);
  
int main(void) {
+
     cout << "Vypis v inorder poradi" << endl;
     binarySearchTree t;
+
     printInorder(s.root);
     bstInit(t);
+
     cout << endl;
     char command[20];
+
      
    int key;
+
     destroy(s);
    while (true) {
 
        scanf("%19s", command);
 
        if (strcmp(command, "insert") == 0) {
 
            scanf("%d", &key);
 
            bstInsert(t, key);
 
        }
 
        if (strcmp(command, "remove") == 0) {
 
            scanf("%d", &key);
 
            bstRemove(t, key);
 
        }
 
        if (strcmp(command, "find") == 0) {
 
            scanf("%d", &key);
 
            bool b = bstFind(t, key);
 
            if (b) {
 
                printf("YES\n");
 
            } else {
 
                printf("NO\n");
 
            }
 
        }
 
        if (strcmp(command, "min") == 0) {
 
            printf("%d\n", bstMin(t));
 
        }
 
        if (strcmp(command, "exit") == 0) {
 
            break;
 
        }
 
     }
 
     bstDestroy(t);                   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>

Aktuálna revízia z 09:19, 9. december 2024

Oznamy

Test

  • Túto stredu 11.12. o 18:10 v posluchárňach F1 a F2 v trvaní 90 minút.
  • Viac informácií na stránke Zimný semester, semestrálny test.
  • Zajtra pošleme pokyny emailom, prosím pozrite si ich.

Prednášky

  • V stredu v prvej polovici prednášky budú informácie k skúške a rady k skúškovému všeobecne, potom doberieme posledné učivo.
  • Budúci pondelok bude nepovinná prednáška o nepreberaných črtách jazykov C a C++.
  • Budúcu stredu prednáška nebude.

Cvičenia

  • Zajtra normálne cvičenia, dva príklady už máte zverejnené.
  • Piatkové cvičenia povinné pre tých, ktorí v utorok nevyriešia rozcvičku.
  • Budúci utorok v rámci cvičení tréning na skúšku.
  • V piatok 20.12. od 13:10 predtermín skúšky, cvičenia nebudú.

Opakovanie: aritmetický výraz ako strom

Strom pre výraz (65 – 3 * 5)/(2 + 3)

Uzol takéhoto stromu môžeme reprezentovať nasledujúcou štruktúrou:

struct treeNode {
    // číselná hodnota (len v listoch)
    double val;

    // operátor vo vnútorných uzloch, pre listy medzera
    char op;

    // smerníky na podstromy
    treeNode * left, * right;
};
  • Vnútorné uzly stromu zodpovedajú operátorom
  • Listy zodpovedajú číselným hodnotám

Videli sme

  • Štruktúru treeNode
  • Vytvorenie stromu z postfixového výrazu (podobné na vyhodnocovanie, využíva sa zásobník hotových podstromov)
  • Vyhodnotenie výrazu, výpis v postfixovej, prefixovej a infixovej forme, uvoľnenie pamäte stromu (jednoduché rekurzívne funkcie)


Zaoberali sme sa aj všeobecnými binárnymi stromami, videli sme

  • Terminológiu
  • Štruktúru node
  • Výpis v poradí preorder, inorder, postorder, uvoľnenie pamäte stromu (jednoduchá rekurzia, podobne ako pre výrazy)
  • Hĺbka vrchola a výška stromu

Binárne vyhľadávacie stromy

Príklad binárneho vyhľadávacieho stromu.

Stromy sa v informatike používajú na veľa účelov. Ďalším príkladom sú binárne vyhľadávacie stromy, ktoré môžu slúžiť na implementovanie abstraktného dátového typu dynamická množina, ktorý sme videli na prednáške 14. Prvky množiny nebudeme ukladať do poľa, ani v spájaného zoznamu, ale do vrcholoch binárneho stromu.

  • 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 int), tieto dáta niekedy voláme aj kľúč
  • Pre každý vrchol v stromu platí:
    • Každý vrchol v ľavom podstrome v má hodnotu data menšiu ako vrchol v
    • Každý vrchol v pravom podstrome v má hodnotu data väčšiu ako vrchol v
  • Z toho vyplýva, že ak vypíšeme strom v inorder poradí, dostaneme prvky usporiadané
  • Pre danú množinu kľúčov existuje veľa vyhľadávacích stromov

Cvičenie: nájdite všetky binárne vyhľadávacie stromy pozostávajúce z troch uzlov s kľúčmi 1, 2, 3.

Definícia dátových štruktúr

Vrchol typu node

  • Položka data typu int
  • Smerník na ľavé a pravé dieťa
  • Na niektoré úlohy (napr. mazanie vrcholu) sa hodí aj smerník na rodiča (ten má hodnotu NULL v koreni)

Celý strom je štruktúra obsahujúca iba smerník na koreň

  • Pre prázdny strom je to NULL.
struct node {
    /* vrchol binárneho vyhľadávacieho stromu  */
    int data;       /* hodnota uložená vo vrchole */
    node * parent;  /* rodič vrchola, NULL v koreni */
    node * left;    /* ľavé dieťa, NULL ak neexistuje */
    node * right;   /* pravé dieťa, NULL ak neexistuje */
};

/* Samotná dynamická množina (obal pre používateľa). */
struct set {
    node *root;  /* koreň stromu, NULL pre prázdny strom */
};

Inicializácia prázdneho binárneho vyhľadávacieho stromu

/* Funkcia inicializuje prázdny binárny vyhľadávací strom */
void init(set &s) {
    s.root = NULL;
}

Likvidácia binárneho vyhľadávacieho stromu

Likvidáciu podstromu zakoreneného v danom uzle root realizujeme funkciou destroy, obdobne ako pri všeobecných binárnych stromoch. Vytvoríme aj funkciu destroy, ktorá dostane množinu implementovanú ako strom a tento strom zlikviduje.

/* Uvoľní pamäť pre strom s koreňom root */
void destroy(node *root) {
    if (root != NULL) {
        destroy(root->left);
        destroy(root->right);
        delete root;
    }
}

/* Zlikviduje množinu s (uvoľní pamäť) */
void destroy(set &s) {
    destroy(s.root);
}

Hľadanie v binárnom vyhľadávacom strome

Funkcia findNode v podstrome s koreňom root hľadá uzol, ktorého položka dáta obsahuje hľadanú hodnotu x. Vráti takýto uzol alebo NULL ak neexistuje.

  • Najskôr porovná hľadaný kľúč s dátami v koreni:
    • ak sa rovnajú, končíme (našli sme, čo hľadáme),
    • ak je x menšie ako dáta v koreni, musí byť v ľavom podstrome,
    • ak je x väčšie, musí byť v pravom podstrome.
  • V príslušnom podstrome sa rozhodujeme podľa tých istých pravidiel.
  • Keď narazíme na prázdny podstrom, dáta sa v strome nenachádzajú.
  • Dá sa zapísať rekurzívne alebo cyklom, lebo vždy ideme iba do jedného podstromu.

Funkcia contains zavolá funkciu findNode pre koreň daného binárneho vyhľadávacieho stromu t a pomocou nej zistí, či tento strom obsahuje uzol s kľúčom x.

/* Ak v strome s koreňom root existuje uzol s kľúčom x, 
 * vráti ho. Inak vráti NULL. */
node * findNode(node *root, int x) {
    node * v = root;
    while (v != NULL && v->data != x) {
        if (x < v->data) {
            v = v->left;
        } else {
            v = v->right;
        }
    }
    return v;
}

/* Rekurzívna verzia */
node *findNodeR(node *root, int x) {
    if (root == NULL || x == root->data) {
        return root;
    } else if (x < root->data) {
        return findNodeR(root->left, x);
    } else {  // x > root->data
        return findNodeR(root->right, x);
    }
}

/* Zistí, či strom reprezentujúci množinu s 
 * obsahuje uzol s kľúčom x. */
bool contains(set &s, int x) {
    return findNode(s.root, x) != NULL;
}

Č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 insertNode vloží nový uzol s kľúčom x na správne miesto podstromu zakoreneného v *root.

  • 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, ale dá sa aj cyklom, podobne ako pri hľadaní
  • Funkcia add vloží nový uzol pomocou funkcie insertNode ale zvlášť ošetrí prípad, keď ide o prvý uzol.
/* Funkcia vytvorí uzol s daným kľúčom
 * a rodičom, deti nastaví na NULL. */
node * createBSTNode(int data, node * parent) {
    node *v = new node;
    v->data = data;
    v->left = NULL;
    v->right = NULL;
    v->parent = parent;
    return v;
}

/* Vloží nový uzol s hodnotou x 
 * na správne miesto stromu s koreňom root */
void insertNode(node *root, int x) {
    assert(root != NULL);
    if (x < root->data) {
        if (root->left == NULL) {
            root->left = createBSTNode(x, root);
        } else {
            insertNode(root->left, x);
        }
    } else {
        if (root->right == NULL) {
            root->right = createBSTNode(x, root);
        } else {
            insertNode(root->right, x);
        }
    }
}

/* Vloží do stromu pre množinu s nový uzol s kľúčom x */
void add(set &s, int x) {
    if (s.root == NULL) {
        // špeciálny prípad, keď vkladáme prvý uzol
        s.root = createBSTNode(x, NULL);
    } else {
        insertNode(s.root, x);
    }
}

Čas vkladania je tiež v najhoršom prípade úmerný hĺbke stromu.

Cvičenia

  • Ako bude vyzerať strom po nasledujúcej postupnosti operácií?
    set s;
    init(s);
    add(s, 2);
    add(s, 5);
    add(s, 3);
    add(s, 10);
    add(s, 7);  
  • Napíšte nerekurzívny variant funkcie insertNode.
  • Do funkcie doplňte assert, ktorý deteguje prípad, že vkladaná hodnota sa už v strome nachádza.
  • Napíšte funkciu treeSort, ktorá z poľa celých čísel a pomocou volaní funkcie add vytvorí binárny vyhľadávací strom a následne pomocou prehľadávania tohto stromu v poradí inorder pole a utriedi.

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 minNode 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 min, ktorá pomocou funkcie minNode nájde minimálny kľúč v danej množine.
/* Vráti uzol s minimálnym kľúčom v strome s koreňom v */
node *minNode(node *v) {
    assert(v != NULL);
    while (v->left != NULL) {
        v = v->left;
    }
    return v;
}

/* Vráti minimálnu hodnotu v množine s */
int min(set &s) {
    assert(s.root != NULL);
    return minNode(s.root)->data; 
}

Cvičenia:

  • Napíšte rekurzívny variant funkcie minNode.
  • Ako by bolo treba funkciu zmeniť, aby hľadala maximum?


Funkcia successorNode nájde pre daný uzol v 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 v.

  • Ak má uzol v pravé dieťa, následník uzla v bude vrchol s minimálnym kľúčom v pravom podstrome.
  • V opačnom prípade môže byť následníkom uzla v jeho rodič, ak v je jeho ľavé dieťa.
  • Ak je v pravým dieťaťom svojho rodiča, môže to byť jeho prarodič (ak je rodič uzla v ľavým dieťaťom tohto prarodiča), atď.
  • Vo všeobecnosti teda ide o najbližšieho predka uzla v takého, že v patrí do jeho ľavého podstromu.
  • V strome existuje práve jeden uzol bez následníka (najväčší prvok).
    • Ako presne sa bude funkcia nižšie pre tento prvok správať?
/* Vráti uzol, ktorý vo vzostupnom poradí uzlov
 * podľa kľúčov nasleduje za v. 
 * Ak taký uzol neexistuje, vráti 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;
}

Mazanie z binárneho vyhľadávacieho stromu

Nasledujúca funkcia remove zmaže z binárneho vyhľadávacieho stromu uzol s kľúčom x (predpokladá, že taký je).

  • Najprv pomocou funkcie findNode nájde uzol v s kľúčom x.
  • Ak je v list, jednoducho ho zmažeme.
  • Ak má v jedno dieťa, toto dieťa prevesíme priamo pod rodiča v a v zmažeme.
  • Ak má v dve deti, nájdeme nasledovníka v, t.j. minimum v pravom podstrome v.
  • Tento nasledovník nemá ľavé dieťa, vieme ho teda zmazať.
  • Jeho údaje presunieme do vrcholu v.
  • Tiež treba dať pozor na mazanie koreňa.
/* Zmaže zo stromu pre množinu s uzol s kľúčom x */
void remove(set &s, int x) {
    // Nájde uzol s hodnotou, ktorú treba vymazať.
    node *v = findNode(s.root, x);
    // Skontrolujme, že požadovaný uzol existuje
    assert(v != NULL);
    
    // Nájde uzol *rm, ktorý sa reálne zmaže
    node *rm;                                          
    if (v->left == NULL || v->right == NULL) {         
        rm = v;
    } else  {
        rm = successorNode(v);
        // Presunie kľúč uzla *rm do uzla *v
        v->data = rm->data;
    }

    // ak má uzol rm dieťa, jeho rodičom bude rodič rm
    node *child;                                      
    if (rm->left != NULL) {
        child = rm->left;
    } else {
        child = rm->right;
    }
    if (child != NULL) {
        child->parent = rm->parent;
    }
    if (rm->parent == NULL) {
        s.root = child;
    } else if (rm == rm->parent->left) {
        rm->parent->left = child;    
    } else if (rm == rm->parent->right) {
        rm->parent->right = child;
    }
    // rm už nie je v strome, uvoľníme jeho pamäť
    delete rm;
}

Zložitosť jednotlivých operácií

  • Časová zložitosť operácií contains, add aj remove je úmerná výške stromu, ktorú označíme h.
  • Minule sme ukázali, že pre strom s n uzlami máme log2(n+1)-1 ≤ h ≤ 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 ak sa prvky vkladajú v náhodnom poradí, výška stromu bude v priemere 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.

Ukážkový program s binárnymi vyhľadávacími stromami

#include <iostream>
#include <cassert>
using namespace std;

struct node {
    /* vrchol binárneho vyhľadávacieho stromu  */
    int data;       /* hodnota uložená vo vrchole */
    node * parent;  /* rodič vrchola, NULL v koreni */
    node * left;    /* ľavé dieťa, NULL ak neexistuje */
    node * right;   /* pravé dieťa, NULL ak neexistuje */
};

/* Samotná dynamická množina (obal pre používateľa). */
struct set {
    node *root;  /* koreň stromu, NULL pre prázdny strom */
};

/* Funkcia inicializuje prázdny binárny vyhľadávací strom */
void init(set &s) {
    s.root = NULL;
}

/* Uvoľní pamäť pre strom s koreňom root */
void destroy(node *root) {
    if (root != NULL) {
        destroy(root->left);
        destroy(root->right);
        delete root;
    }
}

/* Zlikviduje množinu s (uvoľní pamäť) */
void destroy(set &s) {
    destroy(s.root);
}

/* Ak v strome s koreňom root existuje uzol s kľúčom x, 
 * vráti ho. Inak vráti NULL. */
node * findNode(node *root, int x) {
    node * v = root;
    while (v != NULL && v->data != x) {
        if (x < v->data) {
            v = v->left;
        } else {
            v = v->right;
        }
    }
    return v;
}

/* Rekurzívna verzia */
node *findNodeR(node *root, int x) {
    if (root == NULL || x == root->data) {
        return root;
    } else if (x < root->data) {
        return findNodeR(root->left, x);
    } else {  // x > root->data
        return findNodeR(root->right, x);
    }
}

/* Zistí, či strom reprezentujúci množinu s 
 * obsahuje uzol s kľúčom x. */
bool contains(set &s, int x) {
    return findNode(s.root, x) != NULL;
}

/* Funkcia vytvorí uzol s daným kľúčom
 * a rodičom, deti nastaví na NULL. */
node * createBSTNode(int data, node * parent) {
    node *v = new node;
    v->data = data;
    v->left = NULL;
    v->right = NULL;
    v->parent = parent;
    return v;
}

/* Vloží nový uzol s hodnotou x 
 * na správne miesto podstromu zakoreneného v root */
void insertNode(node *root, int x) {
    assert(root != NULL);
    if (x < root->data) {
        if (root->left == NULL) {
            root->left = createBSTNode(x, root);
        } else {
            insertNode(root->left, x);
        }
    } else {
        if (root->right == NULL) {
            root->right = createBSTNode(x, root);
        } else {
            insertNode(root->right, x);
        }
    }
}

/* Vloží do stromu pre množinu s nový uzol s kľúčom x */
void add(set &s, int x) {
    if (s.root == NULL) {
        // špeciálny prípad, keď vkladáme prvý uzol
        s.root = createBSTNode(x, NULL);
    } else {
        insertNode(s.root, x);
    }
}

/* Vráti uzol s minimálnym kľúčom v strome s koreňom v */
node *minNode(node *v) {
    assert(v != NULL);
    while (v->left != NULL) {
        v = v->left;
    }
    return v;
}

/* Vráti minimálnu hodnotu v množine s */
int min(set &s) {
    assert(s.root != NULL);
    return minNode(s.root)->data; 
}

/* Vráti uzol, ktorý vo vzostupnom poradí uzlov
 * podľa kľúčov nasleduje za v. 
 * Ak taký uzol neexistuje, vráti 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;
}


/* Zmaže zo stromu pre množinu s uzol s kľúčom x */
void remove(set &s, int x) {
    // Nájde uzol s hodnotou, ktorú treba vymazať.
    node *v = findNode(s.root, x);
    // Skontrolujme, že požadovaný uzol existuje
    assert(v != NULL);
    
    // Nájde uzol *rm, ktorý sa reálne zmaže
    node *rm;                                          
    if (v->left == NULL || v->right == NULL) {         
        rm = v;
    } else  {
        rm = successorNode(v);
        // Presunie kľúč uzla *rm do uzla *v
        v->data = rm->data;
    }

    // ak má uzol rm dieťa, jeho rodičom bude rodic rm
    node *child;                                      
    if (rm->left != NULL) {
        child = rm->left;
    } else {
        child = rm->right;
    }
    if (child != NULL) {
        child->parent = rm->parent;
    }
    if (rm->parent == NULL) {
        s.root = child;
    } else if (rm == rm->parent->left) {
        rm->parent->left = child;    
    } else if (rm == rm->parent->right) {
        rm->parent->right = child;
    }
    // rm už nie je v strome, uvoľníme jeho pamäť
    delete rm;
}

/* Vypíše podstrom s koreňom * root v poradí preorder */
void printInorder(node * root) {
    if (root != NULL) {
        cout << " " << root->data;
        printInorder(root->left);
        printInorder(root->right);
   } 
}

int main() {

    set s;
    init(s);

    cout << "Vkladame 2,5,3,10,7 do s." << endl;
    add(s, 2);
    add(s, 5);
    add(s, 3);
    add(s, 10);
    add(s, 7);  

    cout << "Obsahuje s cisla 2 a 4?" << endl;
    cout << contains(s, 2) << endl;
    cout << contains(s, 4) << endl;

    cout << "Minimum s: " << min(s) << endl;;

    cout << "Vypis v inorder poradi" << endl;
    printInorder(s.root);
    cout << endl;

    cout << "Mazeme 2,5,10 z s." << endl;
    remove(s, 5);
    remove(s, 2);
    remove(s, 10);

    cout << "Vypis v inorder poradi" << endl;
    printInorder(s.root);
    cout << endl;
    
    destroy(s);
}