AVL Stablo - Implementacija - 2. deo - Pretraga
Uvod
U drugom nastavku serijala o implementaciji algoritma AVL bavićemo se pretragom stabla, odnosno, pronalaženjem određenog čvora preko pripisane celobrojne vrednosti.
Osnovna metoda koju ćemo pisati ima povratni tip int, a konkretne povratne vrednosti, biće:
- celobrojna vrednost koja predstavlja 'dubinu' stabla na kojoj je tražena vrednost pronađena (to jest, broj koraka pretrage) - pod uslovom da je traženi ključ pronađen
- vrednost
-1- ako traženi ključ nije pronađen u stablu
Takođe, budući da se stabla pretrage tipično koriste za pronalaženje obimnijih struktura sa više sadržaja - preko celobrojnih ključeva, prikazaćemo i implementaciju sa strukturom čvora koja je dopunjena klasom sa dodatnim podacima.
U navedenim okolnostima, funkcija (naravno) ne vraća celobrojnu vrednosti, već - referencu na pronađeni čvor (ili vrednost null, ako čvor nije pronađen).
Pretraga stabla - osnovni principi
Podsetićemo se prvo na opšte smernice koje smo naveli u članku o binarnom stablu pretrage:
- pretraga počinje od korenog čvora i praktično se usmerava prema potencijalnoj lokaciji traženog čvora
- ukoliko čvor sa traženom vrednošću nije pronađen na mestu na kome se traži u određenom koraku (za početak u korenom čvoru), prelazi se u levo podstablo - ukoliko je tražena vrednost manja, ili desno podstablo - ukoliko je tražena vrednost veća - i potom se pretraga nastavlja po istom obrascu *
Na kraju pretrage (koja se obavlja po gore opisanoj proceduri), nastaje jedna od dve moguće situacije:
- čvor sa traženom vrednošću uspešno je pronađen u najviše log(n) koraka
- utvrđeno je da tražena vrednost ne postoji u stablu (takođe u najviše logn koraka)
Zarad temeljitosti, prikazaćemo ponovo postupak pretrage binarnog stabla, kroz dva primera (prvo ćemo tražiti element koji postoji u stablu, a zatim i element koji ne postoji u stablu).
Pretraga stabla u kome postoji traženi element
U prvom primeru, tražimo čvor sa vrednošću 17 (pri čemu takav čvor postoji u sledećem stablu):
Na samom početku, proverava se koreni čvor:
U konkretnom primeru, koreni čvor ne sadrži traženu vrednost; tražena vrednost (17), veća je od vrednosti korenog čvora (10), i stoga se iz pretrage može isključiti koreni čvor i celo levo podstablo korenog čvora ....
.... i potom se pretraga usmerava na desno podstablo (u kome se ključ nalazi - ili ne nalazi).
Vrednost korenog čvora desnog podstabla (15) ....
.... manja je od 17, i stoga se iz dalje pretrage isključuje: i čvor #15, i levo podstablo čvora #15:
Pretraga prelazi na koren desnog podstabla prethodno ispitivanog čvora ....
.... i budući da je ispitivana vrednost (22), veća od tražene, iz pretrage se isključuje čvor #22 i desno podstablo čvora #22 ....
.... nakon čega se prelazi u levo podstablo.
Na kraju, u korenu levog podstabla čvora #22 ....
.... pronađena je vrednost koju smo naveli kao ključ za pretragu:
Traženi ključ pronađen je iz četiri pokušaja (pri čemu broj 4 predstavlja visinu stabla).
Pogledajmo u nastavku šta se događa pri pokušaju da pronađemo čvor koji ne može biti pronađen.
Pretraga stabla u kome ne postoji traženi element
U drugom primeru tražimo čvor sa vrednošću 25 (ovoga puta takav čvor ne postoji u stablu), i ponovo se prvo proverava koreni čvor:
Budući da je tražena vrednost veća od 10, iz dalje pretrage se isključuje koreni čvor, kao i celo levo podstablo:
Pretraga prelazi na vrh desnog podstabla korenog čvora:
Pošto se na vrhu desnog podstabla nalazi vrednost koja je manja od 25 (čvor #15), iz dalje pretrage se isključuje čvor #15 i njegovo levo podstablo: *
Pretraga prelazi na desno podstablo ....
.... i budući da je ispitivana vrednost (22), manja od tražene vrednosti, odbacuje se levo podstablo čvora #22 (kao, naravno, i sam čvor #22).
Sledi prelazak na koren desnog podstabla čvora #22 (čvor #24) ....
.... ali, na navedenoj poziciji takođe nije pronađena tražena vrednost:
Preostaje pokušaj prelaska na desno podstablo čvora #24 ....
.... međutim, budući da na mestu desnog podstabla uopšte nije pronađen ikakav čvor ....
.... može se zaključiti da čvor sa traženom vrednošću (25) - ne postoji u stablu.
Pretraga stabla - Implementacija
Pošto smo se podsetili na osnovne principe pretrage, prikazaćemo kako se mogu implementirati dve varijante funkcije za pretragu:
- prvo ćemo se osvrnuti na osnovnu implementaciju sa 'običnim' celobrojnim čvorovima
- nakon toga, prikazaćemo implementaciju koja koristi složenije čvorove kojima je pripisana klasa sa dodatnim podacima.
Osnovna pretraga
Metodu za pretragu pozivaćemo uz predavanje korenog čvora u svojstvu argumenta (naravno, predaje se i vrednost koju pretražujemo), a prelasci na leva i desna podstabla će biti implementirani preko rekurzije.
Za "školsku" implementaciju sa 'brojačem koraka', biće potrebna i pomoćna metoda proveraCvora, koja je zapravo radna metoda (koja rekurzivno sprovodi korake pretrage):
public int pretraga(int vrednost, Cvor c) {
this.brojac = 0;
return proveraCvora(vrednost, c);
}
public int proveraCvora(int vrednost, Cvor c) {
// 1.
if (c == null) {
return -1;
}
// 2.
this.brojac++;
// 3.
if (c.vrednost == vrednost) {
return this.brojac;
}
// 4.
if (vrednost < c.vrednost) {
return proveraCvora(vrednost, c.levi);
}
// 5.
if (vrednost > c.vrednost) {
return proveraCvora(vrednost, c.desni);
}
return -1;
}
Kao što vidimo, metoda Pretraga poziva radnu metodu proveraCvora, uz prethodno resetovanje brojača, a vidimo i to da metoda za proveru čvorova doslovno sprovodi u delo principe koje smo definisali na početku:
- prvi
ifproverava da li čvor koji je predat kao argument uopšte postoji (ako metoda naiđe na nepostojeći čvor, može se zaključiti da pretraga nije dala rezultat, i stoga metoda treba da vrati vrednost -1) - u sledećem koraku, brojač se uvećava za 1 (i upravo je broj koraka pretrage, podatak koji osnovna verzija metode za pretragu potencijalno treba da vrati)
- ako je vrednost pronađena (komentar #3), metoda vraća broj koraka u pronalaženju tražene vrednosti, a ako vrednost nije pronađena, preostaje neki od poslednja dva koraka (komentari #4 i #5):
- ako je tražena vrednost manja, pretraga se usmerava na levo podstablo
- ako je tražena vrednost veća, pretraga se usmerava na desno podstablo
Bez korišćenja pomoćne metode (budući da koristimo rekurziju (na pomalo idiosinkratičan način)), ne bismo mogli realizovati resetovanje brojača pri svakom pozivu, na iole praktičan i elegantan način.
Pretraga polja baze podataka čiji su ključevi čvorovi AVL stabla
U drugom primeru (koji je, može se reći, konkretniji i praktičniji), koristićemo AVL stablo za pronalaženje slogova koji su povezani sa celobrojnim vrednostima čvorova.
Dodaćemo u program (privremeno, samo za ovaj primer), klasu Osoba ....
.... koju ćemo povezati sa klasom Cvor, posle čega će stablo imati sledeću strukturu ....
Sada možemo dodati i kod za klasu Osoba koja će sadržati (glavne) podatke koje skladištimo, i možemo (privremeno) dopuniti klasu Cvor:
public class Osoba {
public int id;
public string ime,
prezime,
email;
/*
Konstruktor je izostavljen
zarad preglednosti
*/
}
public class Cvor {
public int vrednost; // Primarni ključ (ID)
public Cvor levi, desni;
public int visina,
balansFaktor;
public Osoba osobaPodaci;
/*
I na ovom mestu je
(zarad preglednosti)
izostavljen konstruktor
*/
}
Metoda za pretragu takođe će pretrpeti izmene, pri čemu povratna vrednost (ponovo/očekivano) zavisi od toga da li je id osobe pronađen:
- ukoliko se preko id-a pronađe osoba - povratna vrednost je referenca na objekat klase
Osoba(čiji se id 'propušta' kroz AVL stablo kao kriterijum za pretragu) - ukoliko se ne pronađe traženi id - povratna vrednost je objekat sa sistemskom vrednošću
null
Budući da ovoga puta ne koristimo brojač koraka, sve se može realizovati preko (samo) jedne rekurzivne metode:
public Osoba pretraga(int id, Cvor c) {
if (c == null) return null;
if (c.Vrednost == id) {
return c.osobaPodaci;
}
if (c.vrednost < id) {
return pretraga(id, c.levi);
}
if (c.vrednost > id) {
return pretraga(id, c.desni);
}
}
Kao što vidimo, pretraga čvorova koji 'nose' dodatne podatke, takođe sprovodi u delo principe koji su opisani u uvodnim poglavljima.
Sledeći koraci ....
Verujemo da će stariji i iskusniji čitaoci, po želji (ili još bolje, u skladu sa potrebama, nekog samostalnog projekta i sl. :)), biti u stanju da preprave i ostale metode koje ćemo implementirati, tako da budu prilagođene radu sa kompleksnijim čvorovima koji nose dodatne podatke, tj. objekte, * međutim, što se tiče budućih članaka, vratićemo se (zarad preglednosti) na pisanje jednostavnijih funkcija koje ne vraćaju pokazivače već int vrednosti.
U sledećem nastavku obradićemo četiri metode za obilazak stabla:
- preko algoritma BFS
- preko algoritma DFS - u tri varijante (preorder, inorder i postorder)
Do tada, za vežbu, probajte da implementirate metodu pretrage na iterativni način (bez rekurzije, uz upotrebu petlji).