PrelĂŒĂŒd
See artikkel kĂ€sitleb binaarseid otsingupuusid. Hiljuti kirjutasin artikli Seal ei pööranud ma palju tĂ€helepanu binaarsetele puudele, kuna otsimise, lisamise ja kustutamise meetodid polnud aktuaalsed. NĂŒĂŒd otsustasin kirjutada artikli just puude kohta. Alustame siis.
Puud on andmestruktuurid, mis koosnevad sĂ”lmedest, mis on ĂŒhendatud servadega. VĂ”ib öelda, et puu on graafi erijuht. Siin on puu nĂ€ide:

See ei ole binaarne otsingupuu! KÔik allpool!
Terminoloogia
Juurepunkt
Puulehe juurepunkt on selle kĂ”ige ĂŒlemine sĂ”lm. NĂ€ites on see sĂ”lm A. Puust juurepunktist vĂ”ib iga teise sĂ”lmeni viia ainult ĂŒks tee! Tegelikult saab ĂŒhtegi sĂ”lme kĂ€sitleda vastava alampuu juurepunktina.
Vanemad/ajalugu
KĂ”ik sĂ”lmed, vĂ€lja arvatud juurepunkt, omavad tĂ€pselt ĂŒhte serva, mis viib ĂŒlespoole teise sĂ”lmeni. SĂ”lm, mis asub praegusest kĂ”rgemal, nimetatakse vanemaks selle sĂ”lme jaoks. SĂ”lm, mis asub praegusest allpool ja on sellega ĂŒhendatud, nimetatakse jĂ€reltulijaks selle sĂ”lme jaoks. VĂ”tame nĂ€iteks sĂ”lme B, siis tema vanem on sĂ”lm A ja jĂ€reltulijad on sĂ”lmed D, E ja F.
Leht
Puu sÔlmed, millel ei ole jÀrgnenud sÔlmi, nimetatakse puu lehtedeks. Antud nÀites on lehtedeks sÔlmed D, E, F, G, I, J, K.
See on pÔhiterminoloogia. Teised mÔisted kÀsitletakse hiljem. Nii on binaarne puu puu, millel igal sÔlmel on mitte rohkem kui kaks jÀrglast. Nagu arvatavasti teate, ei ole antud puu binaarne, kuna sÔlmed B ja H omavad rohkem kui kahte jÀrglast. Siin on nÀide binaarsest puust:

Puu sÔlmedes vÔib paikneda mis tahes teave. Binaarne otsingu puu on binaarne puu, millel on jÀrgmised omadused:
- MĂ”lemad alampuud â vasak ja parem â on binaarsed otsingu puud.
- KÔik vasakpoolse alampuu sÔlmed mistahes sÔlme X puhul on andmekohtade vÔtmete vÀÀrtused vÀiksemad kui sÔlme X enda vÔtme vÀÀrtus.
- KÔik parempoolse alampuu sÔlmed mistahes sÔlme X puhul on andmekohtade vÔtmete vÀÀrtused suuremad vÔi vÔrdsed kui sÔlme X enda vÔtme vÀÀrtus.
Ala â mĂ”ni sĂ”lme omadus (nĂ€iteks number). VĂ”ti on vajalik, et leida puu element, millele see vĂ”ti vastab. NĂ€ide binaarsest otsingu puust:

Puu esitus
Etenemisprotsessis toon ma vÀlja mÔned (vÔimalikult mitte tÀielikud) koodilÔigud, et aidata teil paremini mÔista. TÀielik kood on artikli lÔpus.
Puu koosneb sÔlmedest. SÔlme struktuur:
public class Node {
private T data;
private int key;
private Node leftChild;
private Node rightChild;
public Node(T data, int key) {
this.data = data;
this.key = key;
}
public Node getLeftChild() {
return leftChild;
}
public Node getRightChild() {
return rightChild;
}
//...muud sÔlme meetodid
}
Igal sÔlmel on kaks last (tÔenÀoliselt vÔivad leftChild ja/vÔi rightChild sisaldada vÀÀrtust null). Olete ilmselt aru saanud, et antud juhul number data on sÔlmes hoitavad andmed; key on sÔlme vÔti.
NĂŒĂŒd, kui oleme sĂ”lmega tutvunud, rÀÀgime puude pĂ€evakajalistest probleemidest. Siin ja edaspidi mĂ”istan sĂ”na "puu" all binaarse otsingu puu mĂ”istet. Binaarse puu struktuur:
public class BinaryTree {
private Node root;
//puu meetodid
}
Klassi vÀljana vajame ainult puu juuri, kuna juuri kaudu on vÔimalik meetodite getLeftChild() ja getRightChild() abil pÀÀseda iga puu sÔlmeni.
Algoritmid puus
Otsi
Oletame, et teil on loodud puu. Kuidas leida elementi, millel on vĂ”ti key? Tuleb jĂ€rk-jĂ€rgult liikuda juurest allapoole puu ja vĂ”rrelda vÀÀrtust key jĂ€rgmise sĂ”lme vĂ”tmega: kui key on vĂ€iksem kui jĂ€rgmise sĂ”lme vĂ”ti, siis liikuda sĂ”lme vasakule alampuudule, kui suurem â paremale, kui vĂ”tmed on vĂ”rdsed â otsitav sĂ”lm on leitud! Vastav kood:
public Node<T> find(int key) {
Node<T> current = root;
while (current.getKey() != key) {
if (key < current.getKey())
current = current.getLeftChild();
else
current = current.getRightChild();
if (current == null)
return null;
}
return current;
}
Kui current muutub nulliks, tĂ€hendab see, et lĂ€bimine on jĂ”udnud puu lĂ”ppu (kontseptuaalses mĂ”ttes viibite te puudulikkuse koha â lehes oleva jĂ€rglase juures).
Vaadakem otsingu algoritmi efektiivsust tasakaalustatud puus (puus, kus sĂ”lmed on jaotatud enam-vĂ€hem ĂŒhtlaselt). Seega on otsingu efektiivsus O(log(n)), kusjuures logaritm on alus 2. NĂ€iteks: kui tasakaalustatud puus on n elementi, tĂ€hendab see, et puu taset on log(n) alusel 2. Ja otsingus, ĂŒhe tsĂŒkli sammu jooksul, laskute ĂŒhe taseme vĂ”rra.
Sisestamine
Kui olete otsimise mĂ”tte tabanud, siis ei tohiks lisamine teile raskusi valmistada. Tuleb lihtsalt laskuda puu lehte (langemise reeglite kohaselt, nagu on kirjas otsingus) ja saada selle jĂ€reltulijaks â vasakuks vĂ”i paremaks, sĂ”ltuvalt vĂ”tmest. Teostus:
public void insert(T insertData, int key) {
Node current = root;
Node parent;
Node newNode = new Node(insertData, key);
if (root == null)
root = newNode;
else {
while (true) {
parent = current;
if (key < current.getKey()) {
current = current.getLeftChild();
if (current == null) {
parent.setLeftChild(newNode);
return;
}
}
else {
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return;
}
}
}
}
}
Sellisel juhul tuleb lisaks praegusele sÔlmele hoida teavet ka praeguse sÔlme vanema kohta. Kui current saab nulliks, sisaldab muutuja parent vajalikku lehte.
Sisestamise efektiivsus on kindlasti sama mis otsimise puhul â O(log(n)).
Kustuta
Kustutamine on kÔige keerulisem toiming, mida puu juures teha tuleb. Esiteks tuleb leida element, mille me kavatseme eemaldada. Aga mis siis edasi? Kui lihtsalt mÀÀrata selle viide vÀÀrtusele null, kaotame me teabe selle alampuu kohta, mille juureks on see sÔlm. Puu eemaldamise meetodid jagunevad kolme juhtumi vahel.
Esimene juhtum. Eemaldatav sÔlm ei oma jÀreltulijaid.
Kui eemaldatav sÔlm ei oma jÀreltulijaid, siis tÀhendab see, et ta on leht. Seega vÔib lihtsalt mÀÀrata tema vanema leftChild vÔi rightChild vÀljadele vÀÀrtuse null.
Teine juhtum. Eemaldatav sĂ”lm omab ĂŒhte jĂ€reltulijat.
See juht ei ole ka eriti keeruline. Naaseme meie nĂ€ite juurde. Oletame, et peame eemaldama elemendi, mille vĂ”tme number on 14. NĂ”ustute, et kuna see on parempoolne jĂ€reltulija sĂ”lme, mille vĂ”tme number on 10, siis igal tema jĂ€reltulijal (antud juhul parempoolsel) on kindlasti vĂ”tme number, mis on suurem kui 10. Seega saab selle lihtsasti âvĂ€lja lĂ”igataâ puust, ning vanemate sĂ”lme saab otse ĂŒhendada eemaldatava sĂ”lme jĂ€reltulijaga, st sĂ”lm, mille vĂ”tme number on 10, saab ĂŒhendatud sĂ”lmega 13. Sarnane olukord oleks ka siis, kui peaksime eemaldama sĂ”lme, mis on tema vanema vasak jĂ€reltulija. MĂ”elge sellele ise â tĂ€pselt analoogne.
Kolmas juhtum. SÔlm omab kahte jÀreltulijat.
Ăige keerulisem juhtum. Lahkame selle uue nĂ€ite pĂ”hjal.

JĂ€reltulija otsimine.
Oletame, et peame eemaldama sÔlme, mille vÔtme number on 25. Keda paneme tema asemele? Keegi tema jÀrgmistest (jÀreltulijatest vÔi jÀreltulijate jÀreltulijatest) peaks saama jÀreltulijaks.(see, kes asendab eemaldatavat sÔlme).
Kuidas mĂ”ista, kes peab olema jĂ€rglane? Intuitiivselt on selge, et see on puu sĂ”lm, mille vĂ”ti on eemaldatava sĂ”lme jĂ€rgmiseks suuruseks. Algoritm on jĂ€rgmine. Tuleb minna selle paremale jĂ€reltulijale (aina paremale, sest on juba öeldud, et jĂ€rglase vĂ”ti on suurem eemaldatava sĂ”lme vĂ”tmel), ja siis liikuda selle parema jĂ€reltulija vasakute jĂ€reltulijate ahelas. NĂ€ites peame liikuma sĂ”lme juurde, mille vĂ”ti on 35, ja seejĂ€rel liikuma alla lehe kaudu tema vasakute jĂ€reltulijate ahelas â antud juhul koosneb see ahel vaid sĂ”lmest, mille vĂ”ti on 30. Rangelt öeldes otsime me vĂ€ikseimat sĂ”lme nende sĂ”lmede kogumis, mis on otsitavast sĂ”lmest suuremad.

JĂ€rglase otsimise meetodi kood:
public Node getSuccessor(Node deleteNode) {
Node parentSuccessor = deleteNode; // vanema jÀrglane
Node successor = deleteNode; // jÀrglane
Node current = successor.getRightChild(); // lihtsalt "lÀbiv" sÔlm
while (current != null) {
parentSuccessor = successor;
successor = current;
current = current.getLeftChild();
}
// tsĂŒklist vĂ€ljudes on meil jĂ€rglane ja vanema jĂ€rglane
if (successor != deleteNode.getRightChild()) { // kui jĂ€rglane ei ĂŒhti kustutatava sĂ”lme parema lapsega
parentSuccessor.setLeftChild(successor.getRightChild()); // siis tema vanem vÔtab jÀrglase jÀreltulija, et seda mitte kaotada
successor.setRightChild(deleteNode.getRightChild()); // seome jÀrglase kustutatava sÔlme parema lapsega
}
return successor;
}
Kogu delete meetodi kood:
public boolean delete(int deleteKey) {
Node current = root;
Node parent = current;
boolean isLeftChild = false; // SÔltuvalt sellest, kas kustutatav sÔlm on oma vanemast vasak vÔi parem jÀreltulija, omandab loogiline muutuja isLeftChild vÀÀrtuse true vÔi false vastavalt.
while (current.getKey() != deleteKey) {
parent = current;
if (deleteKey < current.getKey()) {
current = current.getLeftChild();
isLeftChild = true;
} else {
isLeftChild = false;
current = current.getRightChild();
}
if (current == null)
return false;
}
if (current.getLeftChild() == null && current.getRightChild() == null) { // esimene juhtum
if (current == root)
current = null;
else if (isLeftChild)
parent.setLeftChild(null);
else
parent.setRightChild(null);
}
else if (current.getRightChild() == null) { // teine juhtum
if (current == root)
root = current.getLeftChild();
else if (isLeftChild)
parent.setLeftChild(current.getLeftChild());
else
current.setRightChild(current.getLeftChild());
} else if (current.getLeftChild() == null) {
if (current == root)
root = current.getRightChild();
else if (isLeftChild)
parent.setLeftChild(current.getRightChild());
else
parent.setRightChild(current.getRightChild());
}
else { // kolmas juhtum
Node successor = getSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.setLeftChild(successor);
else
parent.setRightChild(successor);
}
return true;
}
Aja vÔib hinnata O(log(n))-ks.
Maksimaalse/minimaalse otsimine puus
Ilmselgelt, kuidas leida puus minimaalne/maximaalne vÀÀrtus â tuleb jĂ€rjestikku liikuda puu vasakute/paretute elementide kaudu; kui jĂ”uad lehtedeni, on see minimaalne/maximaalne element.
public Node<T> getMinimum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getLeftChild();
}
return parent;
}
public Node<T> getMaximum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getRightChild();
}
return parent;
}
Aja keerukus â O(log(n))
SĂŒmbiootiline lĂ€bimine
LĂ€bimine â iga puu sĂ”lme kĂŒlastamine tegevuse sooritamiseks.
Rekursiivse sĂŒmbiootilise lĂ€bimise algoritm:
- Teha tegevus vasakul lapsel
- Teha tegevus endaga
- Teha tegevus paremal lapsel
Kood:
public void inOrder(Node<T> current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");//Siin vÔib olla mis iganes
inOrder(current.getRightChild());
}
}
KokkuvÔte
LÔpuks! Kui ma midagi ei selgitanud vÔi on mingeid mÀrkusi, ootan neid kommentaarides. Nagu lubatud, esitan tÀis koodi.
Node.java:
public class Node {
private T data;
private int key;
private Node leftChild;
private Node rightChild;
public Node(T data, int key) {
this.data = data;
this.key = key;
}
public void setLeftChild(Node newNode) {
leftChild = newNode;
}
public void setRightChild(Node newNode) {
rightChild = newNode;
}
public Node getLeftChild() {
return leftChild;
}
public Node getRightChild() {
return rightChild;
}
public T getData() {
return data;
}
public int getKey() {
return key;
}
}
BinaryTree.java:
public class BinaryTree<T> {
private Node<T> root;
public Node<T> find(int key) {
Node<T> current = root;
while (current.getKey() != key) {
if (key < current.getKey())
current = current.getLeftChild();
else
current = current.getRightChild();
if (current == null)
return null;
}
return current;
}
public void insert(T insertData, int key) {
Node<T> current = root;
Node<T> parent;
Node<T> newNode = new Node<>(insertData, key);
if (root == null)
root = newNode;
else {
while (true) {
parent = current;
if (key < current.getKey()) {
current = current.getLeftChild();
if (current == null) {
parent.setLeftChild(newNode);
return;
}
}
else {
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return;
}
}
}
}
}
public Node<T> getMinimum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getLeftChild();
}
return parent;
}
public Node<T> getMaximum(Node<T> startPoint) {
Node<T> current = startPoint;
Node<T> parent = current;
while (current != null) {
parent = current;
current = current.getRightChild();
}
return parent;
}
public Node<T> getSuccessor(Node<T> deleteNode) {
Node<T> parentSuccessor = deleteNode;
Node<T> successor = deleteNode;
Node<T> current = successor.getRightChild();
while (current != null) {
parentSuccessor = successor;
successor = current;
current = current.getLeftChild();
}
if (successor != deleteNode.getRightChild()) {
parentSuccessor.setLeftChild(successor.getRightChild());
successor.setRightChild(deleteNode.getRightChild());
}
return successor;
}
public boolean delete(int deleteKey) {
Node<T> current = root;
Node<T> parent = current;
boolean isLeftChild = false;
while (current.getKey() != deleteKey) {
parent = current;
if (deleteKey < current.getKey()) {
current = current.getLeftChild();
isLeftChild = true;
} else {
isLeftChild = false;
current = current.getRightChild();
}
if (current == null)
return false;
}
if (current.getLeftChild() == null && current.getRightChild() == null) {
if (current == root)
current = null;
else if (isLeftChild)
parent.setLeftChild(null);
else
parent.setRightChild(null);
}
else if (current.getRightChild() == null) {
if (current == root)
root = current.getLeftChild();
else if (isLeftChild)
parent.setLeftChild(current.getLeftChild());
else
current.setRightChild(current.getLeftChild());
} else if (current.getLeftChild() == null) {
if (current == root)
root = current.getRightChild();
else if (isLeftChild)
parent.setLeftChild(current.getRightChild());
else
parent.setRightChild(current.getRightChild());
}
else {
Node<T> successor = getSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.setLeftChild(successor);
else
parent.setRightChild(successor);
}
return true;
}
public void inOrder(Node<T> current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");
inOrder(current.getRightChild());
}
}
}
P.S.
Langenemine O(n) tasemeni
Paljud teist on vĂ”ib-olla mĂ€rganud, et mis juhtub, kui puud pole tasakaalus? NĂ€iteks, kui panna puusse jĂ€rjestikuseid sĂ”lmi: 1, 2, 3, 4, 5, 6⊠Siis meenutab puu pigem ĂŒhendatud loendit. Ja jah, puu kaotab oma puulise struktuuri, seega ka andmete juurdepÀÀsu efektiivsuse. Otsingu, sisestamise ja kustutamise keerukus muutub sarnaseks ĂŒhendatud loendi omadega: O(n). See on ĂŒks olulisemaid, minu arvates, kahjusid, mis tulenevad binaarsetest puudest.
Ainult registreeritud kasutajad saavad kĂŒsitluses osaleda. , palun.
Ma olen hubastes hiljuti ning sooviksin teada, milliseid teemasid sooviksite rohkem nÀha?
Andmestruktuurid
Algoritmid (DĂŒnaamiline programmeerimine, rekurssioon, andmete tihendamine jne.)
Andmestruktuuride ja algoritmide rakendamine reaalses elus
Android-rakenduste programmeerimine Java-s
Veebirakenduste programmeerimine Java-s
HÀÀletas 2 kasutajat. 1 kasutaja hoidub.
Allikas: habr.com
