Bináris fa megvalósítása Java-ban
1. Bemutatkozás
Ebben a cikkben egy bináris fa implementálását ismertetjük a Java-ban.
E cikk kedvéért egy rendezett bináris fát fogunk használni, amely tartalmazza int értékek.
2. bináris fa
A bináris fa egy rekurzív adatszerkezet, ahol minden csomópont legfeljebb 2 gyermeket vállalhat.
A bináris fa általános típusa a bináris keresőfa, amelyben minden csomópont értéke nagyobb vagy egyenlő a bal alfa csomópontértékeivel, és kisebb vagy egyenlő a jobb oldali alcsomópont csomópontértékeivel. fa.
Az alábbiakban bemutatunk egy ilyen típusú bináris fát:

A megvalósításhoz egy segédprogramot fogunk használni Csomópont osztály, amely tárolni fog int értékeket, és tartson hivatkozást minden gyermekre:
class Node {int érték; Csomópont maradt; Csomópont jobbra; Csomópont (int érték) {this.value = érték; right = null; bal = null; }}
Ezután tegyük hozzá a fánk kezdő csomópontját, amelyet általában úgy hívnak gyökér:
public class BinaryTree {Csomópont gyökere; // ...}
3. Közös műveletek
Most nézzük meg a leggyakoribb műveleteket, amelyeket egy bináris fán elvégezhetünk.
3.1. Elemek beszúrása
Az első művelet, amelyre kiterjedünk, új csomópontok beillesztése.
Első, meg kell találnunk azt a helyet, ahová új csomópontot szeretnénk felvenni, hogy a fa rendezve legyen. Ezeket a szabályokat a gyökércsomóponttól kezdve követjük:
- ha az új csomópont értéke alacsonyabb, mint a jelenlegi csomópont értéke, akkor a bal gyermekhez megyünk
- ha az új csomópont értéke nagyobb, mint a jelenlegi csomópont értéke, akkor a megfelelő gyermekhez megyünk
- amikor az aktuális csomópont nulla, elértünk egy levélcsomópontot, és ebbe a helyzetbe helyezhetjük be az új csomópontot
Először létrehozunk egy rekurzív módszert a beszúrás elvégzésére:
private Node addRecursive (Node current, int value) {if (current == null) {return new Node (value); } if (value current.value) {current.right = addRecursive (current.right, value); } else {// érték már létezik visszatérő áram; } visszatérő áram; }
Ezután létrehozzuk a nyilvános módszert, amely a rekurziót a gyökér csomópont:
public void add (int érték) {root = addRecursive (root, érték); }
Most nézzük meg, hogyan használhatjuk ezt a módszert a fa létrehozására a példánkból:
privát BinaryTree createBinaryTree () {BinaryTree bt = new BinaryTree (); bt. (6); bt.add (4); bt. (8); bt.add (3); bt. (5); bt. (7); bt.add (9); return bt; }
3.2. Elem megtalálása
Most adjunk hozzá egy módszert annak ellenőrzésére, hogy a fa tartalmaz-e meghatározott értéket.
Mint korábban, először létrehozunk egy rekurzív módszert, amely bejárja a fát:
privát logikai érték tartalmazzaNodeRecursive (Node current, int value) {if (current == null) {return false; } if (érték == aktuális.érték) {return true; } return value <aktuális.érték? containsNodeRecursive (current.lft, value): tartalmazzaNodeRecursive (current.right, value); }
Itt az értéket úgy keressük, hogy összehasonlítjuk az aktuális csomópont értékével, majd ennek függvényében folytatjuk a bal vagy a jobb gyermekben.
Ezután hozzuk létre a nyilvános módszert, amely a gyökér:
nyilvános logikai érték tartalmazzaNode (int érték) {return tartalmazNodeRecursive (gyökér, érték); }
Most hozzunk létre egy egyszerű tesztet annak ellenőrzésére, hogy a fa valóban tartalmazza-e a beillesztett elemeket:
@Test public void givenABinaryTree_WhenAddingElements_ThenTreeContainsThoseElements () {BinaryTree bt = createBinaryTree (); assertTrue (bt.containNode (6)); assertTrue (bt.containNode (4)); assertFalse (bt.containNode (1)); }
Az összes hozzáadott csomópontnak tartalmaznia kell a fát.
3.3. Elem törlése
Egy másik gyakori művelet egy csomópont törlése a fáról.
Először is meg kell találnunk a törlendő csomópontot hasonló módon, mint korábban:
privát csomópont deleteRecursive (Node current, int value) {if (current == null) {return null; } if (érték == aktuális.érték) {// Csomópont a megtalált törléséhez // ... a csomópont törléséhez szükséges kód ide kerül} érték); visszatérő áram; } current.right = deleteRecursive (current.right, value); visszatérő áram; }
Miután megtaláljuk a törölni kívánt csomópontot, három fő különbség van:
- egy csomópontnak nincs gyermeke - ez a legegyszerűbb eset; csak le kell cserélnünk ezt a csomópontot nulla szülőcsomópontjában
- egy csomópontnak pontosan egy gyermeke van - a szülőcsomópontban ezt a csomópontot kicseréljük egyetlen gyermekére.
- egy csomópontnak két gyermeke van - ez a legösszetettebb eset, mert egy fa átszervezését igényli
Lássuk, hogyan tudjuk megvalósítani az első esetet, amikor a csomópont levélcsomópont:
if (current.lft == null && current.right == null) {return null; }
Most folytassuk azt az esetet, amikor a csomópontnak egy gyermeke van:
if (current.right == null) {return current.balra; } if (current.left == null) {return current.right; }
Itt adjuk vissza a nem semleges gyermek, így hozzárendelhető a szülőcsomóponthoz.
Végül kezelnünk kell azt az esetet, amikor a csomópontnak két gyermeke van.
Először meg kell találnunk azt a csomópontot, amely felváltja a törölt csomópontot. A törlendő csomópont legkisebb csomópontját fogjuk használni a jobb alfában:
private int findSmallestValue (Node root) {return root.left == null? root.value: findSmallestValue (root.bal); }
Ezután hozzárendeljük a legkisebb értéket a törlendő csomóponthoz, majd ezt követően töröljük a jobb alfáról:
int legkisebb érték = findSmallestValue (jelenlegi.jobb); current.value = legkisebb érték; current.right = deleteRecursive (current.right, legkisebbValue); visszatérő áram;
Végül hozzuk létre a nyilvános módszert, amely elindítja a törlést a gyökér:
public void törlés (int érték) {root = deleteRecursive (root, érték); }
Most ellenőrizzük, hogy a törlés a várt módon működik-e:
@Test public void givenABinaryTree_WhenDeletingElements_ThenTreeDoesNotContainThoseElements () {BinaryTree bt = createBinaryTree (); assertTrue (bt.containNode (9)); bt.delete (9); assertFalse (bt.containsNode (9)); }
4. A fa bejárása
Ebben a szakaszban a fa áthaladásának különböző módjait fogjuk megismerni, részletesen bemutatva a mélység és az első szélesség kereséseket.
Ugyanazt a fát fogjuk használni, amelyet korábban használtunk, és minden esetben megmutatjuk a bejárási sorrendet.
4.1. Mélység-első keresés
A mélység-első keresés egyfajta bejárás, amely a lehető legnagyobb mértékben elmélyül minden gyermekben, mielőtt felfedezné a következő testvért.
A mélységi keresés végrehajtásának több módja van: megrendelés, előrendelés és utólagos megrendelés.
A rendezett bejárás abból áll, hogy először meglátogatja a bal alfa, majd a gyökércsomópontot, végül a jobb alfát:
public void traverseInOrder (Csomópont csomópont) {if (csomópont! = null) {traverseInOrder (csomópont.balra); System.out.print ("" + csomópont.érték); traverseInOrder (node.right); }}
Ha ezt a módszert hívjuk, a konzol kimenete a sorrendben történő bejárást mutatja:
3 4 5 6 7 8 9
Az előrendelés bejárása először a gyökércsomópontot, majd a bal és végül a jobb alfát keresi fel:
public void traversePreOrder (Csomópont csomópont) {if (csomópont! = null) {System.out.print ("" + csomópont.érték); traversePreOrder (node.balra); traversePreOrder (csomópont.jobb); }}
Ellenőrizzük az előrendelés bejárását a konzol kimenetében:
6 4 3 5 8 7 9
A megrendelés utáni bejárás meglátogatja a bal alfát, a jobb alfát és a gyökércsomópontot a végén:
public void traversePostOrder (Node node) {if (csomópont! = null) {traversePostOrder (node.left); traversePostOrder (csomópont.jobb); System.out.print ("" + csomópont.érték); }}
Íme a csomópontok utólagos sorrendben:
3 5 4 7 9 8 6
4.2. Szélesség-első keresés
Ez egy másik gyakori bejárási típus, amely meglátogatja a szint összes csomópontját, mielőtt a következő szintre lépne.
Ezt a fajta bejárást szint-sorrendnek is nevezik, és meglátogatja a fa minden szintjét a gyökértől kezdve, és balról jobbra.
A megvalósításhoz a Sor hogy az egyes szintek csomópontjai rendben legyenek. Minden csomópontot kivonunk a listából, kinyomtatjuk az értékeket, majd hozzáadjuk gyermekeit a sorhoz:
public void traverseLevelOrder () {if (root == null) {return; } Várakozási csomópontok = új LinkedList (); csomópontok.add (gyökér); while (! csomópontok.isEmpty ()) {Csomópont csomópont = csomópontok.eltávolítás (); System.out.print ("" + csomópont.érték); if (csomópont.balon! = null) {csomópontok.add (csomópont.balra); } if (csomópont.jobb! = null) {csomópontok.add (csomópont.jobb); }}}
Ebben az esetben a csomópontok sorrendje a következő lesz:
6 4 8 3 5 7 9
5. Következtetés
Ebben a cikkben azt láttuk, hogyan lehet egy rendezett bináris fát megvalósítani a Java-ban és annak leggyakoribb műveleteiben.
A példák teljes forráskódja elérhető a GitHub oldalon.