Vodič za AVL stabla na Javi
1. Uvod
U ovom uputstvu predstavit ćemo AVL stablo i razmotrit ćemo algoritme za umetanje, brisanje i traženje vrijednosti.
2. Što je AVL stablo?
Stablo AVL, nazvano po izumiteljima Adelson-Velskyju i Landisu, samo je uravnotežljivo binarno stablo pretraživanja (BST).
Stablo samobalansiranja je binarno stablo pretraživanja koje uravnotežuje visinu nakon umetanja i brisanja prema nekim pravilima uravnoteženja.
Najgora vremenska složenost BST-a ovisi o visini stabla. Točnije, najduži put od korijena stabla do čvora. Za BST s N čvorova, recimo da svaki čvor ima samo nula ili jedno dijete. Stoga je njegova visina jednaka N, a vrijeme pretraživanja u najgorem slučaju je O (N). Dakle, naš glavni cilj u BST je zadržati maksimalnu visinu blizu dnevnika (N).
Faktor ravnoteže čvora N je visina (desno (N)) - visina (lijevo (N)). U AVL stablu faktor ravnoteže čvora može biti samo jedna od vrijednosti 1, 0 ili -1.
Definirajmo a Čvor objekt za naše stablo:
javni razred Node {int ključ; visina int; Čvor lijevo; Čvor desno; ...}
Dalje, definirajmo AVLTree:
javna klasa AVLTree {korijen privatnog čvora; void updateHeight (čvor n) {n.height = 1 + Math.max (visina (n.lijevo), visina (n.desno)); } int visina (čvor n) {return n == null? -1: n.visina; } int getBalance (čvor n) {return (n == null)? 0: visina (n.desno) - visina (n.lijevo); } ...}
3. Kako uravnotežiti AVL stablo?
AVL stablo provjerava faktor ravnoteže svojih čvorova nakon umetanja ili brisanja čvora. Ako je faktor ravnoteže čvora veći od jednog ili manji od -1, stablo se ponovno uravnoteži.
Postoje dvije operacije rebalansa stabla:
- desna rotacija i
- lijeva rotacija.
3.1. Desna rotacija
Krenimo s pravom rotacijom.
Pretpostavimo da imamo BST pod nazivom T1, s Y kao korijenskim čvorom, X kao lijevim podređenim dijelom Y i Z kao desnim podređenim dijelom X. S obzirom na karakteristike BST-a, znamo da je X <Z <Y.
Nakon desne rotacije Y, imamo stablo zvano T2 s X kao korijenom i Y kao desno dijete X i Z kao lijevo dijete Y. T2 je i dalje BST jer zadržava redoslijed X <Z <Y .
Pogledajmo pravu operaciju rotacije za našu AVLTree:
Čvor rotateRight (čvor y) {Čvor x = y.left; Čvor z = x.desno; x.desno = y; y.lijevo = z; updateHeight (y); updateHeight (x); povrat x; }
3.2. Lijeva rotacija
Imamo i operaciju lijeve rotacije.
Pretpostavimo BST pod nazivom T1, s Y kao korijenskim čvorom, X kao desnim podređenim dijelom Y i Z kao lijevim podređenim dijelom X. S obzirom na to, znamo da je Y <Z <X.
Nakon lijeve rotacije Y, imamo stablo zvano T2 s X kao korijenom i Y kao lijevim podređenim dijelom X i Z kao desnim podređenim Y. T2 je i dalje BST jer zadržava redoslijed Y <Z <X .
Pogledajmo operaciju lijeve rotacije za naš AVLTree:
Čvor rotateLeft (Node y) {Čvor x = y.desno; Čvor z = x.lijevo; x.lijevo = y; y.desno = z; updateHeight (y); updateHeight (x); povrat x; }
3.3. Tehnike rebalansa
Operacije desne rotacije i lijeve rotacije možemo koristiti u složenijim kombinacijama za održavajte AVL stablo uravnoteženim nakon bilo koje promjene u njegovim čvorovima. U neuravnoteženoj strukturi, najmanje jedan čvor ima faktor ravnoteže jednak 2 ili -2. Pogledajmo kako u tim situacijama možemo uravnotežiti drvo.
Kada je faktor ravnoteže čvora Z 2, podstablo sa Z kao korijenom nalazi se u jednom od ova dva stanja, smatrajući Y pravim podređenim dijelom Z.
U prvom slučaju, visina u desnom djetetu Y (X) veća je od visine lijevog djeteta (T2). Stablo možemo lako uravnotežiti lijevom rotacijom Z.
U drugom slučaju, visina desnog djeteta Y (T4) manja je od visine lijevog djeteta (X). Za ovu situaciju potrebna je kombinacija rotacijskih operacija.
U ovom slučaju prvo okrećemo Y udesno, pa stablo dobiva isti oblik kao i prethodni slučaj. Tada možemo uravnotežiti stablo lijevom rotacijom Z.
Također, kada je faktor ravnoteže čvora Z -2, njegovo podstablo je u jednom od ova dva stanja, pa Z smatramo korijenom, a Y lijevim djetetom.
Visina u lijevom djetetu Y veća je od visine njegova desnog djeteta, pa drvo uravnotežujemo desnom rotacijom Z.
Ili u drugom slučaju, desno dijete Y ima veću visinu od svog lijevog djeteta.
Dakle, prije svega, transformiramo ga u prijašnji oblik lijevom rotacijom Y, a zatim uravnotežimo stablo desnom rotacijom Z.
Pogledajmo operaciju rebalansa za naše AVLTree:
Rebalans čvora (čvor z) {updateHeight (z); int ravnoteža = getBalance (z); if (ravnoteža> 1) {if (visina (z.desno.desno)> visina (z.desno.lijevo)) {z = rotateLeft (z); } else {z.desno = rotateRight (z.desno); z = rotateLeft (z); }} inače if (visina ravnoteže (z.lijevo.desno)) z = rotateRight (z); else {z.left = rotateLeft (z.left); z = rotateRight (z); }} povratak z; }
Koristit ćemo rebalans nakon umetanja ili brisanja čvora za sve čvorove na putu od promijenjenog čvora do korijena.
4. Umetnite čvor
Kad ćemo umetnuti ključ u stablo, moramo pronaći njegov pravilan položaj da bismo prošli BST pravila. Dakle, krećemo od korijena i njegovu vrijednost uspoređujemo s novim ključem. Ako je ključ veći, nastavljamo desno - u suprotnom idemo lijevom djetetu.
Jednom kad pronađemo odgovarajući nadređeni čvor, tada dodajemo novi ključ kao čvor lijevo ili desno, ovisno o vrijednosti.
Nakon umetanja čvora imamo BST, ali to možda nije AVL stablo. Stoga provjeravamo čimbenike ravnoteže i ponovno uravnotežujemo BST za sve čvorove na putu od novog čvora do korijena.
Pogledajmo operaciju umetanja:
Umetanje čvora (čvor čvora, int ključ) {if (node == null) {return new Node (key); } else if (node.key> ključ) {node.left = insert (node.left, key); } else if (node.key <key) {node.right = insert (node.right, key); } else {baciti novi RuntimeException ("duplicirani ključ!"); } povratak rebalansa (čvor); }
Važno je zapamtiti da je ključ jedinstven u stablu - niti dva čvora ne dijele isti ključ.
Složenost vremena algoritma za umetanje ovisi o visini. Budući da je naše stablo uravnoteženo, možemo pretpostaviti da je vremenska složenost u najgorem slučaju O (log (N)).
5. Izbrišite čvor
Da bismo izbrisali ključ sa stabla, prvo ga moramo pronaći u BST-u.
Nakon što pronađemo čvor (nazvan Z), moramo predstaviti novog kandidata koji će biti njegova zamjena u stablu. Ako je Z list, kandidat je prazan. Ako Z ima samo jedno dijete, ovo je dijete kandidat, ali ako Z ima dvoje djece, postupak je malo složeniji.
Pretpostavimo desno podređeno dijete Z koje se zove Y. Prvo, pronalazimo krajnji lijevi čvor Y i nazivamo ga X. Zatim postavljamo novu vrijednost Z jednaku vrijednosti X ’s i nastavljamo brisati X iz Y.
Napokon, metodu rebalansa pozivamo na kraju da BST ostane AVL stablo.
Evo naše metode brisanja:
Izbriši čvor (čvor čvora, int ključ) {if (node == null) {return čvor; } else if (node.key> ključ) {node.left = delete (node.left, key); } else if (node.key <key) {node.right = delete (node.right, key); } else {if (node.left == null || node.right == null) {node = (node.left == null)? node.desno: node.left; } else {Čvor mostLeftChild = mostLeftChild (node.right); node.key = mostLeftChild.key; node.right = izbriši (node.right, node.key); }} if (čvor! = null) {node = rebalans (čvor); } povratni čvor; }
Složenost vremena algoritma za brisanje ovisi o visini stabla. Slično metodi umetanja, možemo pretpostaviti da je vremenska složenost u najgorem slučaju O (log (N)).
6. Potražite čvor
Traženje čvora u AVL stablu je isto kao i kod bilo kojeg BST-a.
Počnite od korijena stabla i usporedite ključ s vrijednošću čvora. Ako je ključ jednak vrijednosti, vratite čvor. Ako je ključ veći, tražite od desnog djeteta, u suprotnom nastavite s lijevim djetetom.
Vremenska složenost pretraživanja ovisi o visini. Možemo pretpostaviti da je vremenska složenost u najgorem slučaju O (log (N)).
Pogledajmo uzorak koda:
Pronaći čvor (tipka int) {Čvor čvora = korijen; while (current! = null) {if (current.key == key) {break; } current = current.key <tipka? current.right: current.left; } povratna struja; }
7. Zaključak
U ovom uputstvu implementirali smo AVL stablo s operacijama umetanja, brisanja i pretraživanja.
Kao i uvijek, kod možete pronaći na Githubu.