nav_dugme codeBlog codeBlog
  • početna Početna stranica
  • Sačuvani članci Sačuvani članci
  • Članci
     (spisak)
  • Kontakt
Povratak na vrh stranice

Info & povezani članci Info o članku - dugme

Info

trejler_sat Datum objave: 14.11.2020.

trejler_dokument Jezici: Java

trejler_teg_narandzasti Težina: 8/10

Java
algoritam
avl
stablo pretrage
binarno stablo pretrage
strukture podataka
o(logn)
postfiksna notacija
optimizacija
teorija
saveti

Tema: AVL stablo

Visinski balansirano (AVL) stabloImplementacija - 1. deo - Osnovna strukturaImplementacija - 2. deo - PretragaImplementacija - 4. deo - Dodavanje čvorovaImplementacija - 5. deo - Uklanjanje čvorova
Generator AVL Stabla (web aplikacija)

Povezani članci

Binarno stablo pretrageBinarna stabla i algebarski izrazi (stablo izraza)Strukture podatakaBFS i DFS - Pronalaženje putanje kroz lavirintShunting Yard - Implementacija - 1. deo - Prevođenje izraza iz infiksnog u postfiksni zapisKlase složenosti algoritama (O notacija)Izbor prvog programskog jezikaASCII, Unicode i UTF - Predstavljanje znakova na računarimaUNIX Time - Predstavljanje datuma i vremena na računarimaKako napraviti syntax highlighter
Svi članci
What one programmer can do in one month, two programmers can do in two months.
Frederick P. Brooks

AVL Stablo - Implementacija - 3. deo - Obilazak stabla

Facebook LinkedIn Twitter Viber WhatsApp E-mail
zoom_plus zoom_minus bookmark
početna > Članci > Teorija

Uvod

Operacije obilaska, tipično nisu prvo što nekome može pasti na pamet kada se govori o stablima čija je primarna namena pretraga, ali, značaj operacija obilaska sasvim je nezanemarljiv i u AVL stablima (i drugim sličnim stablima).

Takođe, obilasci stabla su veoma zanimljivi sami po sebi (kao deo opšteg obrazovanja), i veoma su korisni u drugim algoritmima.

U AVL stablu, preko BFS obilaska može se prikazati struktura stabla (po nivoima), a inorder varijanta DFS obilaska može se iskoristiti za generisanje liste koja sadrži elemente stabla koji su sortirani u rastući poredak. *

Preostale dve varijante DFS obilaska (preorder i postorder), u slučaju AVL stabla ne igraju preveliku ulogu, ali, i te kako su bitne u slučaju implementacije algoritma Shunting Yard, kao i u stablima izraza (srodni algoritmi kojima smo posvetili zasebne članke).

* U stablu pretrage nema duplikata, što znači da je poredak, ne samo neopadajući, već - 'baš rastući'.

Obilazak stabla - osnovne ideje

Obilazak stabla je operacija koja podrazumeva pristup svakom pojedinačnom elementu (tipično, uz što manji broj poseta), što se dalje može iskorititi za ispis sadržaja stabla, kreiranje statičkog niza ili liste (koji su popunjeni vrednostima iz stabla), ili na neki drugi način.

U osnovnom smislu, stablo je moguće obići na dva načina, na koje smo se osvrnuli u članku o pronalaženju putanja kroz lavirint (iako struktura "lavirinta" nije isto što i struktura stabla, osnovni principi su isti).

U pitanju su algoritmi:

  • BFS - obilazak "po širini"
  • DFS - obilazak "u dubinu"

Algoritmi BFS i DFS (kao što smo objasnili u pomenutom članku), prevashodno su algoritmi za pretragu (Breadth/Depth First Search; eng. - pretraga), ali, nazivi BFS i DFS se koriste i kada su u pitanju obilasci stabala i grafova (i stoga ćemo u nastavku i dalje upotrebljavati dva pomenuta termina).

U praktičnom smislu, može se reći da nijedan element, osim traženog, u pretrazi ne igra bitnu ulogu, dok sa obilascima to nije slučaj.

Pri obilaženju, svi elementi su bitni (tj. "ravnopravni").

BFS - Obilazak stabla "po širini"

BFS obilazak podrazumeva da se čvorovima pristupa (ili, jednostavnije, da se čvorovi ispisuju) - "nivo po nivo": prvo se obilazi (tj. ispisuje) koreni čvor, potom, dva direktna potomka korenog čvora, zatim, potomci direktnih potomaka korenog čvora, i nadalje (redom), ostatak stabla (nivo-po-nivo), sve do 'listova' (to jest, čvorova koji nemaju potomke).

BFS - gif animacija
Slika 1. BFS - obilazak stabla "po širini" (GIF animacija).

Kao što je poznato od ranije, implementacija BFS obilaska podrazumeva korišćenje reda za čekanje (queue), a sam postupak koji ćemo prikazati, prilično je zanimljiv za razmatranje.

Algoritam započinje smeštanjem korenog čvora u red za čekanje, nakon čega sledi petlja koja ima sledeća svojstva:

  • u svakoj iteraciji, na početku se uzima u obradu (i uklanja iz reda), element sa početka reda, posle čega se pronalaze direktni potomci izdvojenog elementa i smeštaju na kraj reda
  • petlja se završava onda kada - pri ispitivanju uslova za ponovni ulazak u petlju - u redu za čekanje nema više elemenata za obradu

Ako pomislimo na to da se tek ubačeni čvor odmah preuzima iz reda, situacija verovatno deluje pomalo čudno (bar na prvi pogled), ali, pomenuta radnja se obavlja u skladu sa jednostavnim i elegantnim pravilima koja važe za sve čvorove u stablu (što ćemo još jasnije moći da sagledamo preko primera u nastavku) ....

U stablu koje smo do sada koristili za primere ....

AVL pretraga 01
Slika 2. - AVL stablo sa slike #1, koje ćemo koristiti u primerima.

.... BFS obilazak ima sledeći tok ....

Prvo se u red za čekanje smešta koreni čvor (čvor #10):

AVL obilazak 02
Slika 3. - BFS obilazak - korak 1. - postavljanje korenog čvora u red za čekanje.

.... i potom algoritam ulazi u petlju.

Petlja se "načinje" preuzimanjem čvora sa početka reda:

AVL obilazak 03
Slika 4. - BFS obilazak - korak 2. - uklanjanje prvog sledećeg čvora iz reda (praktično, jedinog čvora koji je ubačen), i ispis vrednosti.

Sama vrednost čvora koji je u obradi (10), direktno se ispisuje, a pre toga pronalaze se 'potomci' čvora #10 (čvorovi #5 i #15), i smeštaju se u red za čekanje:

AVL obilazak 04
Slika 5. - BFS obilazak - korak 3. - Umetanje 'potomaka' čvora #10 (čvorovi #5 i #15) u red za čekanje.

Već na ovom mestu možemo uvideti zašto koreni čvor nije odmah "uzet u razmatranje" (bez ubacivanja u red), jer - ukoliko se koreni čvor smesti u red i ukoliko se potom pokrene petlja (kao što smo i učinili), važe sledeća pravila:

  • u trenutku kada se petlja pokreće, red nije prazan (što znači da je uslov za ulazak u petlju zadovoljen)
  • posle uklanjanja korenog čvora iz reda, red jeste prazan (ali)
  • u tom trenutku petlja je već pokrenuta, i - pre nego što se program vrati na ispitivanje uslova - moraju se izvršiti naredbe iz tela petlje
  • u toku izvršavanja tela petlje, u red za čekanje se ubacuju dva nova čvora (potomci korenog čvora - čvorovi #5 i #15)
  • kada se program vrati na ispitivanje uslova, red (ponovo) nije prazan

Shodno ranije navedenim pravilima, jasno je da "čudni" korak ubacivanja korenog čvora u red - posle čega se ubačeni čvor odmah uklanja iz reda - zapravo ima smisla, jer takav postupak predstavlja (kao što smo već nagovestili), praktičan način organizacije petlje, koji podrazumeva da opšta pravila važe u svakom koraku, a ne samo "u svim koracima osim prvog".

U nastavku, preuzima se iz reda čvor #5 ....

AVL obilazak 05
Slika 6. - BFS obilazak - korak 4. - Uklanjanje čvora #5 iz reda za čekanje i ispis vrednosti.

.... ispisuje se vrednost čvora, a potomci se smeštaju u red (zapravo, samo jedan potomak - čvor #7):

AVL obilazak 06
Slika 7. - BFS obilazak - korak 5. - umetanje potomaka čvora #5 u red za čekanje (praktično, jedini potomak je čvor #7).

Sledeći čvor koji se preuzima iz reda je čvor #15 ....

AVL obilazak 07
Slika 8. - BFS obilazak - korak 6. - Uklanjanje čvora #15 iz reda za čekanje i ispis vrednosti.

.... vrednost 15 se ispisuje, a u red se smeštaju potomci - čvorovi #12 i #22 ....

AVL obilazak 08
Slika 9. - BFS obilazak - korak 7. - Umetanje potomaka čvora #15 (čvorovi #12 i #22) u red za čekanje.

Dalje se iz reda preuzima čvor #7 ....

AVL obilazak 09
Slika 10. - BFS obilazak - korak 8. - Uklanjanje čvora #7 iz reda za čekanje i ispis vrednosti (algoritam proverava levo i desno podstablo čvora #7, ali, pošto čvor #7 nema nijedno podstablo, navedene korake nećemo prikazivati).

.... vrednost se ispisuje, ali, budući da dati čvor nema potomke, iz reda se odmah preuzima sledeći čvor (#12):

AVL obilazak 10
Slika 11. - BFS obilazak - korak 9. - Uklanjanje čvora #12 iz reda za čekanje i ispis vrednosti.

Budući da čvor #12 takođe nema potomke, samo se ispisuje vrednost 12 i nastavlja se sa preuzimanjem čvorova iz reda ....

AVL obilazak 11
Slika 12. - BFS obilazak - korak 10. - Uklanjanje čvora #22 iz reda za čekanje i ispis vrednosti.

Vrednost sledećeg čvora (#22), dodaje se u ispis, a zatim se čvorovi #17 i #24 (potomci čvora #22), smeštaju u red:

AVL obilazak 12
Slika 13. - BFS obilazak - korak 11. - Umetanje potomaka čvora #22 (čvorovi #17 i #24) u red za čekanje.

Čvor #17 (koji je preuzet iz reda), nema potomke ....

AVL obilazak 13
Slika 14. - BFS obilazak - korak 12. - Uklanjanje čvora #17 iz reda za čekanje i ispis vrednosti (čvor nema potomke, pa nema ni čvorova koji bi mogli biti ubačeni u red za čekanje).

.... isto važi za čvor #24 ....

AVL obilazak 14
Slika 15. - BFS obilazak - korak 13. - Uklanjanje čvora #24 iz reda za čekanje i ispis vrednosti (čvor #24 takođe nema potomke).

.... i stoga nije bilo dodavanja novih čvorova u red (ali, naravno, vrednosti čvorova #17 i #24 su dodate u ispis).

Takođe, budući da je red za čekanje ispražnjen ....

AVL obilazak 15
Slika 16. - BFS obilazak - Završetak operacije (stablo je obiđeno i sve vrednosti su ispisane).

.... algoritam izlazi iz petlje, što znači da je BFS obilazak stabla završen.

BFS obilazak - implementacija

BFS obilazak stabla može se implementirati na sledeći način:

		
public void BFS_Ispis() {
	Queue<Cvor> red, sledeci, pom;
	red     = new LinkedList<Cvor>();
    sledeci = new LinkedList<Cvor>();
	boolean reseno = false;
	
	red.add(this.koren);
	
	while (!reseno) {
		while (!red.isEmpty()) {
			Cvor c = red.remove();
			System.out.printf("%d(%d %d) ", c.vrednost, c.visina, c.balansFaktor);
			if (c.levi  != null) sledeci.add(c.levi);
			if (c.desni != null) sledeci.add(c.desni);
		}
		
		System.out.printf("\n");
		
		pom     = red;
		red     = sledeci;
		sledeci = pom;
		
		reseno = red.isEmpty() && sledeci.isEmpty();
	}
}
		
	
Slika 17. - Implementacija BFS obilaska stabla.

Kao što vidimo, implementacija je prilično jednostavna, dosledno sledi principe koje smo izneli, ali - uz mali dodatak: "spratovi" AVL stabla ispisuju se red-po-red (uz dodatni ispis visine i faktora balansa za svaki čvor).

DFS - Obilazak stabla "u dubinu"

Pre nego što započnemo detaljniju diskusiju o DFS algoritmima, iznećemo jedno opšte opažanje koje čitaocima može biti od koristi.

Za razliku od algoritma BFS, koji ima jednostavnu ideju ali ne baš skroz jednostavnu implementaciju (iz perspektive većine mladih programera/studenata I i II godine fakulteta i sl), DFS algoritmi nisu baš skroz jednostavni za razumevanje pri prvom susretu, * ali, zato je implementacija DFS algoritama - pod uslovom da onaj ko piše implementaciju dobro razume rekurziju - krajnje jednostavna.

* Sa druge strane, uz ponešto truda, prvobitni utisak se prilično lako prevazilazi. :)

DFS obilasci podrazumevaju pronalaženje elemenata (a u primerima koje razmatramo, i ispis) - "po dubini" stabla.

U praktičnom smislu, bilo koji DFS obilazak (koji navodimo), podrazumeva da se algoritam "drži leve strane stabla" dokle god je moguće, a udesno se prelazi onda kada nije moguće dalje prelaziti ulevo (naravno, uz povratak jedan korak unazad, na poslednju "raskrsnicu" - pre nego što se 'skrene desno').

.... posle čega se ponovo skreće levo. :)

Najlakše će biti da princip obilaska sagledamo preko sledeće 'šeme' (slika ipak vredi "toliko-i-toliko" reči):

AVL obilazak 17
Slika 18. - Osnovni princip DFS obilaska, koji se uprošćeno može opisati na sledeći način: algoritam se drži leve strane "dokle god može", i vraća se nazad i skreće desno - onda kada mora (navedeni princip obezbeđuje tri različite vrste ispisa, u zavisnosti od toga da li se vrednost čvora ispisuje čim se naiđe na čvor, ili kasnije).

Opisani princip pristupanja čvorovima stabla omogućava tri različite varijante DFS obilaska (tj. tri različita redosleda obrade):

  • preorder (koreni čvor - levo podstablo - desno podstablo)
  • inorder (levo podstablo - koreni čvor - desno podstablo)
  • postorder (levo podstablo - desno podstablo - koreni čvor)

U literaturi se neretko sreću objašnjenja za DFS obilaske, koja, na primer za preorder obilazak, navode redosled izvršavanja operacija u obliku: "koren-levi-desni", ali, takva uputstva smatramo dvosmislenim i prilično zbunjujućim, jer, kada se neko prvi put susreće sa DFS obilascima (pa čak i drugi put, treći ....), veoma lako može steći (pogrešan) utisak da se odmah ispisuju direktni potomci (levi i desni čvor), dok se ostaci levog i desnog podstabla rešavaju naknadno.

Stoga, želimo na ovom mestu da budemo vrlo precizni, i zato navodimo da je reč o ispisu celog podstabla (pre ili posle, narednog ili prethodnog koraka).

Naravno, da bismo se upoznali kako dolikuje sa svime što smo naveli, razmotrićemo detaljno sva tri algoritma.

Preorder obilazak stabla

Preorder obilazak stabla podrazumeva obilazak (i ispis čvorova, popunjavanje liste, ili neku drugu operaciju), po sledećem redosledu:

  • koreni čvor
  • celokupno levo podstablo
  • celokupno desno podstablo

Dakle, posmatrano za bilo koji čvor u stablu, važi pravilo da će prvo biti ispisan dati čvor, potom, levo podstablo, a zatim i desno podstablo.

Da pojasnimo dodatno: ispisu levog podstabla pristupa se tek pošto se ispiše koren, a ispisu desnog podstabla pristupa se tek pošto se završe prethodna dva ispisa - u celosti.

Usvojićemo i šematski prikaz, u kome se crvenom bojom označava deo stabla koji se ispisuje odmah, zelenom bojom - deo stabla koji je drugi po redu za ispis, a plavom bojom - deo stabla koji je treći po redu za ispis (R-G-B).

U konkretnom primeru koji koristimo, navedeni šematski prikaz ima sledeći oblik:

AVL obilazak 18
Slika 19. - Preorder obilazak - opšta šema za celo stablo - Prvo se ispisuje koreni čvor, potom (preko rekurzije), celo levo podstablo i, na kraju (takođe preko rekurzije), celo desno podstablo.

Iz prikazanog možemo zaključiti da će prvo biti ispisan koreni čvor (#10), potom, čvorovi #5 i #7 (tek pošto bude ispisan koreni čvor), a zatim i čvorovi #15, #12, #22, #17 i #24 (tek pošto budu ispisani koreni čvor i levo podstablo).

Počinjemo sa ispisom i, shodno pravilu "koren -> levo podstablo -> desno podstablo", prvo se ispisuje vrednost 10:

AVL obilazak 19
Slika 20. - Preorder obilazak - Korak 1. - Koreni čvor se ispisuje odmah.

Potom se prelazi na levo podstablo korenog čvora i ispisuje se vrednost korenog čvora podstabla (5):

AVL obilazak 20
Slika 21. - Preorder obilazak - Korak 2. - Po prelasku na levo podstablo, koreni čvor podstabla (čvor #5), takođe se ispisuje odmah.

.... a zatim, budući da čvor #5 nema levo ("zeleno") podstablo, ispisuje se desno podstablo (u praktičnom smislu - samo čvor #7):

AVL obilazak 21
Slika 22. - Preorder obilazak - Korak 3. - Čvor #7 ispisuje se odmah, u svojstvu korenog čvora desnog podstabla čvora #5. Algoritamskim putem se mora proveriti da li čvor #7 - ili bilo koji drugi čvor - ima podstabla, ali, pošto čvor #7 praktično nema podstabla, nećemo prikazivati korake koji se tiču potrage za nepostojećim podstablima (a isto važi i u nastavku, za čvorove koji nemaju podstabla).

Budući da su obrađeni koren stabla (čvor #10) i levo podstablo, na redu je desno podstablo.

Na slikama nismo prikazivali (rekurzivne) korake koji se odnose na povratak na čvorove #5 i #10 (niti pokušaje obilazaka podstabala čvorova #5 i #7), ali, u implementaciji, takvi koraci svakako postoje.

U desnom podstablu, prvo se ispisuje koren podstabla (čvor #15):

AVL obilazak 22
Slika 23. - Preorder obilazak - Korak 4. - Po prelasku na desno podstablo, koreni čvor podstabla (čvor #15), ispisuje se odmah.

.... zatim, levo podstablo (praktično, samo čvor #12) ....

AVL obilazak 23
Slika 24. - Preorder obilazak - Korak 5. - Po prelasku na levo podstablo čvora #15, koreni čvor (čvor #12), ispisuje se odmah.

.... i, na kraju, krajnje desno podstablo.

Ponovo nismo prikazivali korake koji se odnose na povratak prema korenom čvoru (pri čemu bi u implementaciji postojali i (rekurzivni) pokušaji obilazaka podstabala čvora #12).

U "krajnjem desnom podstablu", prvo se ispisuje koreni čvor (čvor #22) ....

AVL obilazak 24
Slika 25. - Preorder obilazak - Korak 6. - Po prelasku na desno podstablo čvora #15, koreni čvor (čvor #22), takođe se ispisuje odmah.

.... zatim levo podstablo (praktično - samo čvor #17):

AVL obilazak 25
Slika 26. - Preorder obilazak - Korak 7. - Koreni čvor levog podstabla čvora #22, čvor #17, ispisuje se odmah (pošto smo skoro na završetku, mali podsetnik: čvor #17 nema podstabla, pa nećemo prikazivati korake koji se tiču pokušaja pristupa nepostojećim podstablima).

.... i, na kraju, desno podstablo (praktično - samo čvor #24):

AVL obilazak 26
Slika 27. - Preorder obilazak - Korak 8. - Na kraju, koreni čvor desnog podstabla čvora #22, čvor #24, takođe se ispisuje odmah (a pošto ni čvor #24 nema podstabla, obilazak je praktično gotov).

Obilazak je završen ....

AVL obilazak 27
Slika 28. - Preorder obilazak - Kraj operacije i konačni ispis.

.... i sve vrednosti su ispisane.

Preorder obilazak stabla - Implementacija

Obećali smo da će implementacija DFS algoritama biti krajnje jednostavna (pod uslovom da dobro razumete rekurziju) ....

		
public void DFSIspisPreorder(Cvor c) {
	if (c == null) return;
	System.out.printf("%d ", c.vrednost);
	DFSIspisPreorder(c.levi);
	DFSIspisPreorder(c.desni);
}
		
	
Slika 29. - Implementacija metode za Preorder obilazak stabla.

.... i verujemo da ćete se složiti sa tim da vas uopšte nismo 'prevarili'. :)

Ako se dvoumite oko implementacije koju ste videli, pokušajte (u mislima ili na papiru), da 'provučete' stablo iz primera kroz prikazani program.

Kao što vidimo, sve funkcioniše (doslovno) onako kako smo naveli na početku, pri čemu za svaki čvor važi:

  • prvo se ispisuje vrednost datog čvora
  • nakon prethodnog koraka, ispisuje se celo levo podstablo
  • na kraju se ispisuje celo desno podstablo

Prosto rečeno, rekurzivni poziv DFSIspisPreorder(C.Desni) neće se desiti sve dok rekurzivni poziv DFSIspisPreorder(C.Levi) ne obavi svoj posao.

Naravno, oba navedena poziva dolaze na red, tek pošto se ispiše koreni čvor (pod)stabla.

Inorder obilazak stabla

Inorder obilazak stabla podrazumeva obilazak (i ispis čvorova, smeštanje u listu, ili neku drugu operaciju), po sledećem redosledu:

  • celokupno levo podstablo
  • koreni čvor
  • celokupno desno podstablo

Kao što smo naveli u uvodnom odeljku: preko inorder obilaska, elementi stabla se mogu ispisati u rastućem poretku (odnosno: istim redosledom, od elemenata se može formirati lista, statički niz i sl).

Ovoga puta, šematski prikaz obilaska ima sledeći oblik:

AVL obilazak 29
Slika 30. - Inorder obilazak - Opšta šema za celo stablo - Prvo se (rekurzivno) ispisuje celo levo podstablo, zatim, koreni čvor i potom (rekurzivno), desno podstablo.

.... i mnogi čitaoci mogu se zapitati: kako će tačno biti ispisano levo podstablo (budući da nije kompletno)?

Odgovor je (ponovo) - 'biće ispisano shodno ranije navedenim pravilima'. :)

S obzirom na to da ne postoji levo podstablo (prvobitnog levog podstabla), prvo se ispisuje koreni čvor podstabla (čvor #5).

AVL obilazak 30
Slika 31. - Inorder obilazak - Korak 1. - Algoritam proverava levo podstablo čvora #5, ali, budući da levo podstablo ne postoji, odmah se ispisuje vrednost čvora #5.

Prema ranije utvrđenoj šemi ("RGB"), čvor #5 je drugi po redu za ispisu u levom podstablu - i zato je označen zelenom bojom, ali, u praktičnom smislu (budući da u podstablu u čijem je korenu čvor #5, ne postoji levo podstablo) - čvor #5 se ispisuje odmah.

Potom se prelazi na desno podstablo (čiji je koren, čvor #7):

AVL obilazak 31
Slika 32. - Inorder obilazak - Korak 2. - Posle čvora #5, ispisuje se čvor #7, ali, bitno je razumeti da je čvor #7 došao na red za ispis tek pošto je provereno njegovo levo podstablo (pri čemu je algoritam ustanovio da levo podstablo ne postoji).

Budući da čvor #7 nema levo podstablo, može se ispisati vrednost korenog čvora podstabla (7), a zatim, s obzirom na to da čvor #7 nema ni desno podstablo, algoritam se vraća korak unazad, na čvor #5.

Pošto je čvor #5 (već) rešen, sledi još jedan korak unazad, skroz do korenog čvora početnog stabla (čvor #10).

Sada, kada je rešeno celo levo podstablo početnog stabla (u čijem korenu stoji čvor #10), može se ispisati i vrednost korenog čvora početnog stabla (10):

AVL obilazak 32
Slika 33. - Inorder obilazak - Korak 3 - Ispis korenog čvora.

Pošto su levo podstablo i koreni čvor osnovnog stabla ispisani, prelazi se na desno podstablo:

AVL obilazak 33
Slika 34. - Inorder obilazak - Korak 4 - Prelazak na desno podstablo.

Čvor #15 ima levo podstablo, pa algoritam prvo prelazi na levu stranu:

AVL obilazak 34
Slika 35. - Inorder obilazak - Korak 5. - Čvor #12 se ispisuje naizgled odmah (ali znamo da se u implementaciji prvo proverava levo podstablo čvora #12).

U praktičnom smislu, vidimo da se čvor #12 može ispisati odmah.

Međutim, u implementaciji, rekurzivni poziv bi prvo ispitao da li čvor #12 ima levo podstablo (ukoliko podstablo postoji, pristupilo bi se rekurzivnom obilasku levog podstabla) - i tek potom bi čvor #12 bio ispisan.

Posle navedenih koraka, drugi rekurzivni poziv bi pokušao da ispiše desno podstablo (čvora #12), ali, budući da nijedno od dva podstabla ne postoji, posle neuspešne probe ispisa desnog podstabla, sledi povratak na čvor #12.

Naravno, sve što smo naveli za desno podstablo početnog stabla (rekurzivni pozivi koji proveravaju podstabla koja praktično ne postoje i sl), odnosi se i na levo podstablo (čvorovi #5 i #7).

Pošto je čvor #12 rešen, može se ispisati i vrednost čvora #15 (s obzirom na to da je celo levo podstablo ovog čvora rešeno).

AVL obilazak 35
Slika 36. - Inorder obilazak - Korak 6. - Budući da je levo podstablo čvora #15 rešeno, u ispis se dodaje vrednost 15.

Pošto je čvor #15 ispisan, obilazak se usmerava na desno podstablo čvora #15 ....

AVL obilazak 36
Slika 37. - Inorder obilazak - Korak 7. - Tri čvora koji su preostali za ispis.

Praktično - odmah će biti ispisan čvor #17, ali, u implementaciji bi se to desilo tek pošto se završi rekurzivni obilazak 'nepostojećeg levog podstabla' * (postupak ne prikazujemo na slikama).

AVL obilazak 37
Slika 38. - Inorder obilazak - Korak 8. - Čvor #17 dolazi na red za ispis (praktično) odmah, budući da nema levo podstablo.

* U praksi, rekurzivni "obilazak" u situaciji sa slike (i sličnim situacijama), podrazumeva samo proveru, preko koje se "konstatuje" da podstablo ne postoji (posle čega odmah sledi povratak na preostale korake), ali - i takav "obilazak" mora se izvršiti.

Na red za ispis dolazi čvor #22 ....

AVL obilazak 38
Slika 39. - Inorder obilazak - Korak 9. - Budući da je rešeno levo podstablo čvora #22, vrednost 22 dodaje se u ispis.

.... i, na kraju, čvor #24:

AVL obilazak 39
Slika 40. - Inorder obilazak - Korak 10. - Čvor #24 nema levo podstablo, i stoga se vrednost 24 takođe odmah može dodati u ispis (a budući da čvor #24 nema ni desno podstablo, ceo obilazak stabla je praktično završen).

Kao i do sada, zelena boja sugeriše da je čvor #24 drugi po redu za ispis, ali, s obzirom na to da čvor nema levo podstablo, praktično se ispisuje odmah.

S obzirom na to da nema ni desnog podstabla (i drugih obilazaka) ....

AVL obilazak 40
Slika 41. - Inorder obilazak - Kraj operacije i konačni ispis.

.... celokupan obilazak stabla je gotov i sve vrednosti su ispisane:

Inorder obilazak stabla - Implementacija

Pogledajmo i implementaciju inorder obilaska:

		
public void DFSIspisInorder(Cvor c) {
	if (c == null) return;
	DFSIspisInorder(c.levi);
	System.out.printf("%d ", c.vrednost);
	DFSIspisInorder(c.desni);
}
		
	
Slika 42. - Implementacija metode za Inorder obilazak stabla.

Ponovo dosledno sprovodimo u delo teoretske principe, uz korišćenje rekurzije, a može se reći da je rekurzija - u slučaju implementacije DFS obilazaka: i mehanizam za preusmeravanje, i svojevrstan sigurnosni mehanizam (koji omogućava da se na sledeće korake ne prelazi dok prethodni koraci nisu rešeni).

Postorder obilazak stabla

Postorder obilazak stabla podrazumeva obilazak (i ispis čvorova, upis u listu, ili neku drugu operaciju), po sledećem redosledu:

  • celokupno levo podstablo
  • celokupno desno podstablo
  • koreni čvor

Šematski prikaz obilaska ima sledeći oblik:

AVL obilazak 42
Slika 43. - Postorder obilazak - Opšta šema za celo stablo - Prvo se (rekurzivno) ispisuje celo levo podstablo, potom (takođe rekurzivno), celo desno podstablo i, na kraju, koreni čvor.

Shodno navedenim pravilima, obrada korenog čvora ostaje 'za sam kraj', a u obzir se prvo uzima levo podstablo:

AVL obilazak 43
Slika 44. - Postorder obilazak - Korak 1. - Čvor #5 se preskače u ispisu (za sada), budući da nisu ispisana oba podstabla ovog čvora (algoritam je ustanovio da levo stablo ne postoji, i u sledećem koraku je prešao na desno podstablo).

Čvor #5, kao koren podstabla, poslednji je po redu za ispis u podstablu kome pripada; levog podstabla nema .... što praktično znači da se prvo ispisuje čvor #7 ....

AVL obilazak 44
Slika 45. - Postorder obilazak - Korak 2. - Oba podstabla čvora #7 su rešena (algoritam je, pri pokušaju obilaska, zapravo ustanovio da nijedno od podstabala ne postoji), i stoga se vrednost 7 može dodati u ispis.

U svemu (kao i do sada), "ispod haube" postoje rekurzivni pozivi (jedan je "pokušao" da ispiše levo podstablo čvora #5, a u implementaciji bi postojala i dva rekurzivna poziva koja traže podstabla čvora #7).

U praksi, sledi povratak na čvor #5 i ispis vrednost čvora:

AVL obilazak 45
Slika 46. - Postorder obilazak - Korak 3. - Budući da je sada rešeno i desno podstablo čvora #5 (levo podstablo je rešeno još ranije), vrednost 5 se dodaje u ispis.

Međutim, po povratku na koreni čvor, vrednost 10 se ne može ispisati, budući da (još uvek) nije rešeno desno podstablo ....

AVL obilazak 46
Slika 47. - Postorder obilazak - Korak 4. - Pri povratku na koreni čvor, algoritam konstatuje da i dalje nije došlo vreme za ispis vrednosti, budući da nije obiđeno desno podstablo.

.... i stoga se obilazak usmerava (upravo), na desno podstablo:

AVL obilazak 47
Slika 48. - Postorder obilazak - Korak 5. - Po prelasku na desno podstablo korenog čvora, čvor #15 se preskače (čeka na ispis sve dok se ne obiđu oba njegova podstabla).

Koreni čvor (15) se u prvom koraku preskače, i prelazi se na koren levog podstabla (čvor #12).

U praktičnom smislu, čvor #12 se ispisuje odmah (a "ispod haube": preko rekurzivnih obilazaka je ustanovljeno da čvor #12 nema podstabla).

AVL obilazak 48
Slika 49. - Postorder obilazak - Korak 6. - Pošto je algoritam ustanovio da čvor #12 nema potomke, vrednost 12 se dodaje u ispis.

Sledi povratak "jedan korak unazad" i provera čvora #15 * ....

AVL obilazak 49
Slika 50. - Postorder obilazak - Korak 7. - Budući da nije obiđeno desno podstablo čvora #15, čvor #15 i dalje čeka na ispis.

.... ali, može se samo konstatovati da vrednost 15 još uvek nije na redu za ispis, i stoga je potrebno prvo obraditi desno podstablo čvora #15:

AVL obilazak 50
Slika 51. - Postorder obilazak - Korak 8. - Čvor #22 takođe čeka na ispis, budući da nisu obiđena oba njegova podstabla.

* Budući da je postorder obilazak ponešto neintuitivniji od prva dva obilaska, prikazujemo i neke dodatne korake, "za svaki slučaj".

U "poslednjem podstablu", prvo se rešava levo podstablo, što (u praktičnom smislu) znači da se vrednost 17 ispisuje odmah (budući da čvor #17 nema podstabla):

AVL obilazak 51
Slika 52. - Postorder obilazak - Korak 9. - Vrednost 17 odmah se dodaje u ispis, budući da čvor #17 nema podstabla ("ispod haube" je naravno došlo do provere podstabala čvora #17).

Čvor #22 (na koji se algoritam vraća u sledećem koraku) ....

AVL obilazak 52
Slika 53. - Postorder obilazak - Korak 10. - Čvor #22 i dalje čeka na ispis, budući da nije obiđeno njegovo desno podstablo.

.... ponovo se mora preskočiti (shodno pravilima) ....

Desno podstablo čvora #22 ("baš poslednje desno podstablo" početnog stabla) ....

AVL obilazak 53
Slika 54. - Postorder obilazak - Korak 11. - Čvor #24 (koji takođe nema podstabla), može se odmah ispisati.

.... praktično sadrži samo čvor #24, čija se vrednost može odmah ispisati (budući da čvor #24 takođe nema podstabla).

Sledi "povratak unazad", prema korenom čvoru: prvo se ispisuje čvor #22 ....

AVL obilazak 54
Slika 55. - Postorder obilazak - Korak 12. - Pri povratku na čvor #22 (budući da su sada rešena oba podstabla ovog čvora), vrednost 22 dodaje se u ispis.

.... zatim, čvor #15 ....

AVL obilazak 55
Slika 56. - Postorder obilazak - Korak 13. - Pri povratku na čvor #15, takođe se može ispisati vrednost čvora (budući da su obiđena oba podstabla ovog čvora).

.... i, na kraju, koreni čvor (čvor #10):

AVL obilazak 56
Slika 57. - Postorder obilazak - Korak 14. - Na samom kraju, sledi povratak na koreni čvor i ispis vrednosti korenog čvora (a budući da su već rešena i oba podstabla korenog čvora, praktično je završen obilazak celog stabla).

Postorder obilazak je završen ....

AVL obilazak 57
Slika 58. - Postorder obilazak - Kraj operacije i konačni ispis.

.... i sve vrednosti su ispisane.

Postorder obilazak stabla - Implementacija

Implementacija postorder obilaska ima sledeći oblik:

		
public void DFSIspisPostorder(Cvor c) {
	if (c == null) return;
	DFSIspisPostorder(c.levi);
	DFSIspisPostorder(c.desni);
	System.out.printf("%d ", c.vrednost);
}
		
	
Slika 59. - Implementacija metode za Postorder obilazak stabla.

I ovoga puta koristimo iste principe kao u prethodna dva slučaja (a verujemo i da smo uspeli da bar neke "protivnike rekurzije" ubedimo da promene stav o ovoj krajnje korisnoj pojavi u programiranju). :)

Uprošćena šema DFS obilazaka

Za kraj, razmotrićemo uprošćenu šemu DFS obilazaka.

U sva tri slučaja, "algoritam se drži leve strane" koliko god je moguće, i vraća se korak unazad - pa zatim "skreće desno" - onda kada je skretanje udesno neizbežno.

Ispis je različit u sve tri situacije i (za svaki čvor) važi da će vrednost čvora biti ispisana (ili da će čvor biti ubačen u listu/niz i sl):

  • u preorder obilasku - prvi put kada se pristupi čvoru
  • u inorder obilasku - drugi put kada se pristupi čvoru
  • u postorder obilasku - poslednji put kada se pristupi čvoru

Mogli biste nam zameriti na tome što vam nismo još na početku naveli 'prečice' (čime bismo vas 'poštedeli' čitanja više desetina redova (umesto nekoliko prethodnih)), ali, takav pristup ne smatramo ni izdaleka pravilnim - i takav pristup ne bi vam zapravo pomogao.

Ni najmanje! :)

Verujemo da bi dobar deo čitalaca bio u stanju da okvirno "shvati poentu" uprošćene šeme (pri čemu bi se stvorio lažni utisak pravog razumevanja, koji smatramo veoma opasnim i štetnim za pravilan razvoj programerske veštine), ali, iz iskustva znamo da bi gotovo svi "promašili" pravi smisao.

Mi naravno ne želimo da čitaoci ovog članka 'promaše' pravi smisao. :)

Međutim, do pravog razumevanja (i u programiranju, i inače u životu), dolazi se samo temeljitim radom, a ne "prečicama"!

Uprošćene šeme, kao što je šema koju ste maločas videli, dobro dođu (da ne kažemo, veoma dobro), ali - tek pošto neko temeljito 'pretrese' osnovne principe i usvoji ih (to jest, tek pošto neko, na pravi način, dođe do pravog razumevanja).

Sledeći koraci ....

Ako ste došli do ovih poslednjih redova (a nadamo se, i do razumevanja :)), pretpostavićemo da tematiku obilazaka stabala smatrate (dovoljno) zanimljivom, ali, kao što smo još ranije konstatovali, "upotrebna vrednost" obilazaka u AVL stablu gotovo je iscrpljena onim što smo prikazali u članku.

Međutim, sada ste upoznati sa opštim principima BFS i DFS obilazaka.

Navedeni principi dobro će doći pri proučavanju algoritama za tumačenje algebarskih izraza i kreiranje stabala izraza (o kojima ćemo uskoro pisati), a takođe će dobro doći pri proučavanju drugih algoritama (o kojima ćemo pisati u nešto daljoj budućnosti), u kojima se pojavljuju obilasci stabala.

Što se tiče AVL stabla, "podižemo lestvicu", tj. prelazimo na "ozbiljne teme" (svakako bar ponešto kompleksnije od dosadašnjih): u narednom članku o implementaciji algoritma AVL - bavićemo se dodavanjem čvorova.

Autor članka Nikola Vukićević Za web portal codeblog.rs
Napomena: Tekstovi, slike, web aplikacije i svi ostali sadržaji na sajtu codeblog.rs (osim u slučajevima gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog pismenog odobrenja autora.
© 2020-2025. Sva prava zadržana.
Facebook LinkedIn Twitter Viber WhatsApp E-mail
početna > Članci > AVL Stablo - Implementacija - 3. deo - Obilazak stabla
codeBlog codeBlog
Sajt posvećen popularizaciji kulture i veštine programiranja.
Napomena: Tekstovi i slike na sajtu codeblog.rs (osim u slučajevima, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora.
© 2020-2025. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt