Binarna stabla i binarno stablo pretrage

Lekcija 6 od 17 · Grafovi i stabla

U binarnom stablu svaki čvor ima najviše dva deteta – levo i desno. Na takvom stablu su definisana tri klasična obilaska, a binarno stablo pretrage (BST) čuva brojeve tako da se traženje radi kao binarna pretraga.

Pre nego što počneš: potrebno predznanje – stabla, rekurzija.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

Zapisivanje binarnog stabla

          1
        /   \
       2     3
      / \     \
     4   5     6

Najjednostavnije je čuvati dva niza: L[v] (levo dete) i R[v] (desno dete), sa 0 kada deteta nema. Za stablo sa slike: L[1]=2, R[1]=3, L[2]=4, R[2]=5, L[3]=0, R[3]=6, a čvorovi 4, 5 i 6 nemaju decu.

Tri obilaska: preorder, inorder, postorder

Obilasci se razlikuju samo po tome kada se ispisuje čvor u odnosu na podstabla:

ObilazakRedosledZa stablo sa slike
preorder (pre)čvor, levo, desno1 2 4 5 3 6
inorder (između)levo, čvor, desno4 2 5 1 3 6
postorder (posle)levo, desno, čvor4 5 2 6 3 1
#include <iostream>
using namespace std;

int L[1001], R[1001];

void preorder(int v) {
    if (v == 0) return;            // nema čvora
    cout << v << " ";
    preorder(L[v]);
    preorder(R[v]);
}
void inorder(int v) {
    if (v == 0) return;
    inorder(L[v]);
    cout << v << " ";
    inorder(R[v]);
}
void postorder(int v) {
    if (v == 0) return;
    postorder(L[v]);
    postorder(R[v]);
    cout << v << " ";
}

int main() {
    int n;
    cin >> n;                       // čvorovi 1..n, koren je 1
    for (int v = 1; v <= n; v++) cin >> L[v] >> R[v];
    preorder(1);  cout << endl;
    inorder(1);   cout << endl;
    postorder(1); cout << endl;
    return 0;
}

Postorder se koristi kada rezultat čvora zavisi od dece (visina, veličina podstabla), a preorder kada se informacija prenosi od korena ka listovima (dubina).

Visina binarnog stabla

Visina praznog stabla je −1, a inače 1 + veća od visina levog i desnog podstabla:

int visina(int v) {
    if (v == 0) return -1;                       // prazno stablo
    return 1 + max(visina(L[v]), visina(R[v]));
}

Binarno stablo pretrage (BST)

U BST-u za svaki čvor važi: sve vrednosti u levom podstablu su manje, a u desnom veće od vrednosti čvora. Traženje počinje od korena: ako je traženi broj manji, idemo levo, ako je veći – desno. Ubacivanje ide istim putem dok se ne dođe do praznog mesta.

Ubacivanje redom: 8 3 10 1 6 14 4

          8
        /   \
       3     10
      / \      \
     1   6      14
        /
       4
#include <iostream>
using namespace std;

const int MAXN = 100001;
int vr[MAXN], L[MAXN], R[MAXN];   // čvorovi se prave redom 1, 2, 3, ...
int brojCvorova = 0;

int ubaci(int v, int x) {         // ubacuje x u podstablo čvora v, vraća (novi) koren podstabla
    if (v == 0) {                 // prazno mesto: novi čvor
        brojCvorova++;
        vr[brojCvorova] = x;
        return brojCvorova;
    }
    if (x < vr[v]) L[v] = ubaci(L[v], x);
    else if (x > vr[v]) R[v] = ubaci(R[v], x);
    return v;                     // jednaki se ne ubacuju ponovo
}

bool nadji(int v, int x) {
    while (v != 0) {
        if (x == vr[v]) return true;
        v = (x < vr[v]) ? L[v] : R[v];
    }
    return false;
}

void inorder(int v) {
    if (v == 0) return;
    inorder(L[v]);
    cout << vr[v] << " ";
    inorder(R[v]);
}

int main() {
    int n, koren = 0;
    cin >> n;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        koren = ubaci(koren, x);
    }
    inorder(koren);               // ispisuje brojeve sortirano!
    cout << endl;
    cout << (nadji(koren, 6) ? "6 postoji" : "6 ne postoji") << endl;
    cout << (nadji(koren, 7) ? "7 postoji" : "7 ne postoji") << endl;
    return 0;
}

Inorder obilazak BST-a daje vrednosti sortirano – to je ujedno i način da se proveri da li je stablo ispravan BST.

Složenost i balansirana stabla

Traženje i ubacivanje rade onoliko koraka kolika je visina stabla. Ako su brojevi ubacivani u slučajnom redosledu, visina je oko log₂ n. Ali ako se ubacuju već sortirani (1, 2, 3, …), stablo postaje lanac visine n i sve operacije su O(n).

Balansirana stabla (crveno-crna, AVL) se sama preuređuju tako da visina ostane O(log n). Ne treba ih pisati ručno: C++ set i map su upravo balansirana binarna stabla pretrage.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Srednje

Da li je stablo BST?

Dato je binarno stablo sa n čvorova (koren je 1). Za svaki čvor data je vrednost i deca L R (0 ako nema). Ispiši DA ako je stablo binarno stablo pretrage (sve vrednosti različite), inače NE.

Ulaz: n, zatim za svaki čvor vrednost L R.

Primer 1

Ulaz

5
8 2 3
3 4 5
10 0 0
1 0 0
6 0 0

Izlaz

DA

Primer 2

Ulaz

3
5 2 3
3 0 0
4 0 0

Izlaz

NE
ZADATAK 02Težina: Lako

Visina BST-a

Brojevi se redom ubacuju u prazno binarno stablo pretrage. Ispiši visinu dobijenog stabla (stablo sa jednim čvorom ima visinu 0).

Primer 1

Ulaz

7
8 3 10 1 6 14 4

Izlaz

3

Primer 2

Ulaz

4
1 2 3 4

Izlaz

3
Česte greške i saveti
  • Bazni slučaj obilaska je „nema čvora“ (v == 0), a ne „list“.
  • Kod provere BST-a nije dovoljno da je levo dete manje od roditelja – ceo levi deo mora biti manji.
  • Sortiran ulaz pravi BST-lanac; tada je rekurzija duboka n, a operacije O(n). Za garantovano O(log n) koristi set.

Proveri znanje

Otvori kviz

Binarna stabla

1. Koji obilazak BST-a daje vrednosti sortirano?




2. Redosled „čvor, levo, desno“ je:




3. Koliko koraka traje traženje u BST-u koji je nastao ubacivanjem brojeva 1, 2, …, n redom?