Deo zbornika Učimo algoritme

Binarno stablo pretrage

Email Twitter LinkedIn Facebook Google

Binarno stablo pretrage

Binarno stablo pretrage (en. binary search tree) je jedna od najvažnijih struktura podataka u računarstvu. To je uređeno binarno stablo za koje važi da su vrednosti svih elemenata u levom podstablu manje od vrednosti čvora, a svih elemenata u desnom podstablu veće.

Ovakva struktura podataka ima veliku primenu u računarstvu jer se u njoj brže pronalaze podaci nego u listi ili u nizu.

Operacije

Najčešće operacije nad uređenim binarnim stablom su:

  • dodavanje elemenata na odgovarajuće mesto
  • pronalazak elementa
  • ispis elemenata
  • brisanje pojedinačnog elemenata
  • brisanje stabla i oslobađanje memorijskih resursa

Većina ovih operacija implementira se korišćenjem rekurzije.

Implementacija

Da bismo implementirali binarno stablo koje se sastoji od više čvorova, potrebno je prvo napraviti jedan čvor.

Čvor

U Javascriptu klasu čvor možemo implementirati na sledeći način:

class Cvor {
  constructor(vrednost) {
    this.vrednost = vrednost
    this.levo = null
    this.desno = null
  }
}

Ovom klasom opisan je jedan čvor koji sadrži sopstvenu vrednost i pokazivače na levo i desno podstablo. Sada, na primer, možemo instancirati čvor i dodeliti mu vrednost:

const cvor = new Cvor(5)

Jedan čvor još uvek nije stablo, ali ako napravimo tri čvora i povežemo ih, na pravom smo putu:

class Cvor {
  constructor(vrednost) {
    this.vrednost = vrednost
    this.levo = null
    this.desno = null
  }
}

const koren = new Cvor(5)
koren.levo = new Cvor(3)
koren.desno = new Cvor(7)

console.log(JSON.stringify(koren, null, 4))

Primetite da smo manju vrednost dodali sa leve, a veću sa desne strane. Sada otprilike imamo ovakvu strukturu:

             5
           /   \
          3     7

Stablo

Sada napravimo još jednu klasu, Stablo, koja će voditi računa o kreiranju čvorova i dodavanju na pravo mesto. Za sada, neka samo napravi jedan čvor i postavi ga za svoj koren:

class Cvor {
  constructor(vrednost) {
    this.vrednost = vrednost
    this.levo = null
    this.desno = null
  }
}

class Stablo {
  constructor(vrednost) {
    this.koren = new Cvor(vrednost)
  }
}

const stablo = new Stablo(5)

console.log(JSON.stringify(stablo, null, 4))

Dodavanje elementa

Da ne bismo ručno dodavali čvorove, potrebno je implementirati metodu koja to radi:

  dodaj(vrednost, cvor = this.koren) {
    if (vrednost === cvor.vrednost) return
    const strana = vrednost > cvor.vrednost ? 'desno' : 'levo'
    if (!cvor[strana]) cvor[strana] = new Cvor(vrednost)
    else this.dodaj(vrednost, cvor[strana])
  }

Ubacivanje elementa implementirano je korišćenjem rekurzije. Rekurzija se ponavlja dok se nova vrednost ne ubaci kao čvor na odgovarajuće mesto u binarnom stablu.

Tumačenje: Metoda prima dva argumena, vrednost i cvor. Vrednost je broj koji dodajemo, a cvor element sa čije leve ili desne strane kačimo novi čvor (podrazumevani argument je this.koren). Na početku imamo rani return ako je vrednost jednaka vrednosti cvora (mora biti manja ili veća). Nakon toga odlučujemo na koju stranu dodati novu vrednost, poredeći je sa vrednošću čvora. Potom proveravamo da li je željena strana čvora prazna. Ako jeste, dodajemo novi čvor na tu stranu i završili smo. Ako nije, rekurzivno pozivamo metodu sa jednim nivom niže (npr. proveravali smo koren, sad proveravamo koren.levo).

Ispis elemenata

Sledeća metoda ispisuje vrednosti čvorova, redom po veličini. Prvo rekurzivno ispiše levo podstablo (elementi manji od korena), zatim koren, a zatim desno (elementi veći od korena):

 ispisi(koren = this.koren) {
    if (!koren) return
    this.ispisi(koren.levo)
    console.log(koren.vrednost)
    this.ispisi(koren.desno)
  }

Primer u Javascriptu

Binarno stablo pretraživanja smo implementirali u Javascriptu na sledeći način:

class Cvor {
  constructor(vrednost) {
    this.vrednost = vrednost
    this.levo = null
    this.desno = null
  }
}

class Stablo {
  constructor(vrednost) {
    this.koren = new Cvor(vrednost)
  }

  dodaj(vrednost, cvor = this.koren) {
    if (vrednost === cvor.vrednost) return
    const strana = vrednost > cvor.vrednost ? 'desno' : 'levo'
    if (!cvor[strana]) cvor[strana] = new Cvor(vrednost)
    else this.dodaj(vrednost, cvor[strana])
  }

  ispisi(cvor = this.koren) {
    if (!cvor) return
    this.ispisi(cvor.levo)
    console.log(cvor.vrednost)
    this.ispisi(cvor.desno)
  }
}

const stablo = new Stablo(5)
stablo.dodaj(3)
stablo.dodaj(7)
stablo.dodaj(4)
stablo.dodaj(2)

stablo.ispisi()

Stablo koje smo napravili ima sledeću strukturu:

        5
       / \
      3   7
     / \
    2   4

Primer u C/C++

Binarno stablo traženja možemo implementirati u C/C++ na sledeći način:

#include<stdio.h>
#include<stdlib.h>
  
struct Cvor
{
    int podatak;
    struct Cvor *levi, *desni;
};
  
Cvor *noviCvor(int item)
{
    Cvor *novi =  (Cvor *)malloc(sizeof(Cvor));
    novi->podatak = item;
    novi->levi = novi->desni = NULL;
    return novi;
}

void ispisi(Cvor *koren)
{
    if (koren != NULL)
    {
        ispisi(koren->levi);
        printf("%d \n", koren->podatak);
        ispisi(koren->desni);
    }
}

Cvor* ubaciCvor(Cvor* koren, int podatak)
{
    /* ako je stablo prazno, vraca novi koren */
    if (koren == NULL) return noviCvor(podatak);
 
    /* inace, rekurise niz stablo */
    if (podatak < koren->podatak)
        koren->levi  = ubaciCvor(koren->levi, podatak);
    else if (podatak > koren->podatak)
        koren->desni = ubaciCvor(koren->desni, podatak);   
 
    /* vraca (nepromenjen) koren pokazivac */
    return koren;
}


int main()
{
    /* pravimo sledece stablo:
              50
           /     \
          30      70
         /  \    /  \
       20   40  60   80 */
    Cvor *koren = NULL;
    koren = ubaciCvor(koren, 50);
    ubaciCvor(koren, 30);
    ubaciCvor(koren, 20);
    ubaciCvor(koren, 40);
    ubaciCvor(koren, 70);
    ubaciCvor(koren, 60);
    ubaciCvor(koren, 80);
  
    ispisi(koren);
  
    return 0;
}

Literatura