Inledningen
Den hÀr artikeln handlar om binÀra söktrÀd. Nyligen skrev jag en artikel om DÀr lade jag inte mycket vikt vid binÀra trÀd, eftersom metoder för sökning, insÀttning och borttagning inte var aktuella. Nu har jag bestÀmt mig för att skriva en artikel just om trÀd. LÄt oss börja.
Ett trÀd Àr en datstruktur som bestÄr av noder kopplade med kanter. Man kan sÀga att ett trÀd Àr ett specialfall av en graf. HÀr Àr ett exempel pÄ ett trÀd:

Detta Àr inte ett binÀrt söktrÀd! Allt under kat!
Terminologi
Rot
Roten av ett trÀd Àr dess översta nod. I exemplet Àr detta nod A. I trÀdet finns det endast en vÀg frÄn roten till vilken annan nod som helst! I sjÀlva verket kan varje nod ses som roten av det undertrÀd som motsvarar denna nod.
FörÀldrar/avkommor
Alla noder, förutom roten, har exakt en kant som leder uppÄt till en annan nod. Noden som ligger över den aktuella noden kallas förÀlder till denna nod. Noden som ligger under den aktuella noden och Àr kopplad till den kallas avkomma till denna nod. LÄt oss ta ett exempel. Vi tar nod B, dÄ blir dess förÀlder nod A, och dess avkommor blir noderna D, E och F.
Blad
En nod som inte har nÄgra avkommor kallas ett blad av trÀdet. I exemplet Àr noderna D, E, F, G, I, J, K blad.
Detta Àr den grundlÀggande terminologin. Andra begrepp kommer att diskuteras senare. SÄ, ett binÀrt trÀd Àr ett trÀd dÀr varje nod har högst tvÄ avkommor. Som ni gissat kommer trÀdet i exemplet inte att vara binÀrt, eftersom noderna B och H har mer Àn tvÄ avkommor. HÀr Àr ett exempel pÄ ett binÀrt trÀd:

I noderna av trÀdet kan vilken information som helst finnas. Ett binÀrt söktrÀd Àr ett binÀrt trÀd som kÀnnetecknas av följande egenskaper:
- BĂ„da undertrĂ€den â vĂ€nster och höger â Ă€r binĂ€ra söktrĂ€d.
- I alla noder i det vÀnstra undertrÀdet till en godtycklig nod X Àr datanycklarna mindre Àn vÀrdet pÄ datanyckeln hos nod X.
- I alla noder i det högra undertrÀdet till en godtycklig nod X Àr datanycklarna större eller lika med vÀrdet pÄ datanyckeln hos nod X.
Nyckeln â en egenskap hos noden (till exempel ett nummer). Nyckeln behövs för att man ska kunna hitta elementet i trĂ€det som motsvarar denna nyckel. Exempel pĂ„ ett binĂ€rt söktrĂ€d:

TrÀdets representation
Allteftersom vi gÄr framÄt kommer jag att ge vissa (kanske ofullstÀndiga) kodstycken för att förbÀttra din förstÄelse. Den fullstÀndiga koden kommer att finnas i slutet av artikeln.
TrÀdet bestÄr av noder. Struktur för en nod:
public class Node<T> {
private T data;
private int key;
private Node<T> leftChild;
private Node<T> rightChild;
public Node(T data, int key) {
this.data = data;
this.key = key;
}
public Node<T> getLeftChild() {
return leftChild;
}
public Node<T> getRightChild() {
return rightChild;
}
//...övriga metoder för noden
}
Varje nod har tvĂ„ barn (det Ă€r möjligt att barnen leftChild och/eller rightChild kommer att ha vĂ€rdet null). Du har nog förstĂ„tt att i detta fall Ă€r data â de uppgifter som lagras i noden; key â nodens nyckel.
Nu har vi gÄtt igenom noder, sÄ lÄt oss prata om aktuella problem relaterade till trÀd. HÀr och framÄt kommer jag att anvÀnda termen "trÀd" för att referera till konceptet av ett binÀrt söktrÀd. Strukturen för ett binÀrt trÀd:
public class BinaryTree<T> {
private Node<T> root;
//metoder för trÀdet
}
Som klassfÀlt behöver vi endast roten av trÀdet, eftersom vi frÄn roten med metoderna getLeftChild() och getRightChild() kan nÄ vilken nod som helst i trÀdet.
Algoritmer i trÀdet
Sökandet
Anta att du har ett byggt trĂ€d. Hur hittar man elementet med nyckeln key? Du mĂ„ste systematiskt röra dig nedĂ„t frĂ„n roten genom trĂ€det och jĂ€mföra vĂ€rdet av key med nyckeln av aktuell nod: om key Ă€r mindre Ă€n nyckeln för den aktuella noden, gĂ„ till vĂ€nstra barnet av noden, om större â till högra, om nycklarna Ă€r lika â den sökta noden Ă€r funnen! Den relevanta koden:
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;
}
Om current blir lika med null, betyder det att sökningen har nĂ„tt slutet av trĂ€det (pĂ„ konceptuell nivĂ„ befinner du dig pĂ„ en icke-existerande plats i trĂ€det â barnet av ett blad).
LÄt oss granska effektiviteten av sökalgoritmen pÄ ett balanserat trÀd (ett trÀd dÀr noderna Àr distribuera mer eller mindre jÀmnt). Vidare kommer effektiviteten i sökningen att vara O(log(n)), dÀr logaritmen Àr bas 2. Se: om det finns n element i det balanserade trÀdet, innebÀr det att det kommer att finnas log(n) nivÄer i trÀdet. Och i sökningen, i ett steg av cykeln, gÄr du ner en nivÄ.
Infoga
Om du har förstĂ„tt kĂ€rnan i sökningen, kommer det inte att vara svĂ„rt för dig att förstĂ„ insĂ€ttningen. Du behöver bara gĂ„ ner till bladet av trĂ€det (enligt nedstigningsreglerna som beskrivs i sökningen) och bli dess avkomma â vĂ€nster eller höger, beroende pĂ„ nyckeln. Implementering:
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;
}
}
}
}
}
I det hÀr fallet behöver vi förutom den aktuella noden lagra information om förÀldern till den aktuella noden. NÀr current blir null kommer variabeln parent att innehÄlla det blad vi behöver.
Effektiviteten för insĂ€ttning kommer uppenbarligen att vara densamma som för sökningen â O(log(n)).
Ta bort
Borttagning Àr den mest komplicerade operationen som mÄste utföras med trÀdet. Det Àr uppenbart att vi först mÄste hitta det element som vi planerar att ta bort. Men vad hÀnder sedan? Om vi bara tilldelar dess referens vÀrdet null, kommer vi att förlora information om deltrÀdet vars rot Àr denna nod. Borttagningstekniker delas in i tre fall.
Första fallet. Den borttagna noden har inga avkommor
Om den borttagna noden inte har nÄgra avkommor betyder det att den Àr ett blad. DÀrför kan vi helt enkelt tilldela leftChild eller rightChild i dess förÀlder vÀrdet null.
Andra fallet. Den borttagna noden har en avkomma
Detta fall Ă€r inte heller sĂ€rskilt svĂ„rt. LĂ„t oss Ă„tervĂ€nda till vĂ„rt exempel. Anta att vi mĂ„ste ta bort elementet med nyckeln 14. HĂ„ll med om att eftersom den Ă€r en höger avkomma till noden med nyckeln 10, kommer vilken avkomma som helst (i det hĂ€r fallet höger) att ha en nyckel som Ă€r större Ă€n 10, sĂ„ vi kan enkelt âklippa utâ den frĂ„n trĂ€det och direkt förknippa förĂ€ldern med avkomman till den borttagna noden, dvs. noden med nyckeln 10 kopplas till noden 13. En liknande situation skulle vara om vi behövde ta bort en nod som Ă€r en vĂ€nster avkomma till sin förĂ€lder. TĂ€nk pĂ„ detta sjĂ€lv â exakt analogi.
Tredje fallet. Noden har tvÄ avkommor
Det mest komplicerade fallet. Vi analyserar det med ett nytt exempel.

Sökning av eftertrÀdare
Anta att vi behöver ta bort noden med nyckeln 25. Vem sÀtter vi i dess stÀlle? NÄgon av dess efterföljare (avkommor eller avkommor till avkommor) mÄste bli eftertrÀdare(den som ska ta platsen för den borttagna noden).
Hur förstÄr vi vem som ska bli eftertrÀdare? Det Àr intuitivt klart att det Àr noden i trÀdet vars nyckel Àr den nÀsta största efter den borttagna noden. Algoritmen innebÀr följande. Vi mÄste gÄ till dess högra avkomma (alltid till höger, eftersom det redan har sagts att nyckeln för eftertrÀdaren Àr större Àn nyckeln för den borttagna noden), och sedan gÄ ner genom kedjan av vÀnstra avkommor till denna högra avkomma. I exemplet mÄste vi gÄ till noden med nyckeln 35 och sedan gÄ ner till bladet genom kedjan av dess vÀnstra avkommor - i detta fall bestÄr denna kedja endast av noden med nyckel 30. Strikt talat letar vi efter den minsta noden i uppsÀttningen av noder som Àr större Àn den sökta noden.

Kod för metoden för att hitta eftertrÀdaren:
public Node<T> getSuccessor(Node<T> deleteNode) {
Node<T> parentSuccessor = deleteNode; // förÀlder till eftertrÀdaren
Node<T> successor = deleteNode; // eftertrÀdare
Node<T> current = successor.getRightChild(); // bara en "genomgÄende" nod
while (current != null) {
parentSuccessor = successor;
successor = current;
current = current.getLeftChild();
}
// vid utgÄngen av loopen har vi eftertrÀdaren och förÀldern till eftertrÀdaren
if (successor != deleteNode.getRightChild()) { // om eftertrÀdaren inte Àr densamma som den högra avkomman till den borttagna noden
parentSuccessor.setLeftChild(successor.getRightChild()); // tar förÀldern över eftertrÀdarens avkomma för att inte förlora den
successor.setRightChild(deleteNode.getRightChild()); // kopplar eftertrÀdaren till den borttagna nodens högra avkomma
}
return successor;
}
Den fullstÀndiga koden för metoden delete:
public boolean delete(int deleteKey) {
Node current = root;
Node parent = current;
boolean isLeftChild = false; // Beroende pÄ om den borttagna noden Àr ett vÀnster- eller högra barn till sin förÀlder, kommer den booleska variabeln isLeftChild att ta vÀrdet true eller 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) { // första fallet
if (current == root)
current = null;
else if (isLeftChild)
parent.setLeftChild(null);
else
parent.setRightChild(null);
}
else if (current.getRightChild() == null) { // andra fallet
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 { // tredje fallet
Node successor = getSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.setLeftChild(successor);
else
parent.setRightChild(successor);
}
return true;
}
Tidskomplexiteten kan approximativt anges till O(log(n)).
Sökning av maximum/minimum i trÀdet
Det Ă€r uppenbart hur man hittar det minimi/maksimala vĂ€rdet i trĂ€det â man mĂ„ste sekventiellt gĂ„ lĂ€ngs kedjan av vĂ€nster/höger element i trĂ€det; nĂ€r man nĂ„r ett blad, Ă€r det minimala/maximala elementet.
public Node getMinimum(Node startPoint) {
Node current = startPoint;
Node parent = current;
while (current != null) {
parent = current;
current = current.getLeftChild();
}
return parent;
}
public Node getMaximum(Node startPoint) {
Node current = startPoint;
Node parent = current;
while (current != null) {
parent = current;
current = current.getRightChild();
}
return parent;
}
Tidskomplexiteten Àr O(log(n))
Symmetrisk genomgÄng
GenomgĂ„ng â att besöka varje nod i trĂ€det för att utföra en Ă„tgĂ€rd med den.
Algoritm för rekursiv symmetrisk genomgÄng:
- Utför ÄtgÀrd med vÀnster barn
- Utför ÄtgÀrd med sig sjÀlv
- Utför ÄtgÀrd med högra barn
Kod:
public void inOrder(Node current) {
if (current != null) {
inOrder(current.getLeftChild());
System.out.println(current.getData() + " ");// HĂ€r kan det vara vad som helst
inOrder(current.getRightChild());
}
}
Slutsats
Ăntligen! Om jag har förklarat nĂ„got för lite eller om det finns nĂ„gra kommentarer, ser jag fram emot dem i kommentarerna. Som utlovat, hĂ€r Ă€r hela koden.
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.
Förfalla till O(n)
MÄnga av er kanske har mÀrkt: vad hÀnder om vi gör sÄ att trÀdet blir obalanserat? Till exempel genom att lÀgga in noder med ökande nycklar: 1,2,3,4,5,6⊠DÄ kommer trÀdet att pÄminna om en lÀnkad lista. Och ja, trÀdet kommer att förlora sin trÀstruktur, vilket i sin tur pÄverkar effektiviteten vid datatillgÄng. Tidskomplexiteten för operationer som sökning, insÀttning och borttagning kommer att bli som för en lÀnkad lista: O(n). Det hÀr Àr en av de viktigaste, enligt min Äsikt, nackdelarna med binÀra trÀd.
Endast registrerade anvÀndare kan delta i omröstningen. , tack.
Jag Àr inte sÄ lÀnge pÄ Habré, och jag skulle vilja veta vilka Àmnen ni skulle vilja se fler artiklar om?
Datastrukturer
Algoritmer (DP, rekursion, datakompression osv.)
TillÀmpning av datastrukturer och algoritmer i verkliga livet
Programmering av android-appar i Java
Programmering av web-appar i Java
2 anvÀndare röstade. 1 anvÀndare avstod.
KĂ€lla: habr.com
