Arbulu binariu o cumu preparà un arbulu di ricerca binariu

Prelude

Questu articulu hè nantu à l'arburi di ricerca binari. Recentemente aghju scrittu un articulu circa cumpressione di dati da u metudu Huffman. Quì ùn aghju micca veramente attentu à l'arbureti binari, perchè i metudi di ricerca, inserimentu, eliminazione ùn eranu micca pertinenti. Avà decisu di scrive un articulu nantu à l'arburi. Forse avemu da principià.

Un arbre hè una struttura di dati custituita da nodi cunnessi da bordi. Pudemu dì chì un arbre hè un casu particulari di un gràficu. Eccu un arbre di esempiu:

Arbulu binariu o cumu preparà un arbulu di ricerca binariu

Questu ùn hè micca un arbre di ricerca binariu! Tuttu hè sottu à u tagliu!

Terminologia

Root

Radice d'arburu hè u nodu più altu. In l'esempiu, questu hè u node A. In l'arbulu, solu una strada pò guidà da a radica à qualsiasi altru nodu! In fatti, ogni node pò esse cunsideratu cum'è a radica di u subtree chì currisponde à questu node.

Parenti / prole

Tutti i nodi eccettu a radica anu esattamente un bordu chì porta à un altru nodu. U node sopra u node attuale hè chjamatu parenti stu nodu. Un node situatu sottu à l'attuale è cunnessu à questu hè chjamatu discendente stu nodu. Pigliemu un esempiu. Pigliate u nodu B, allora u so parente serà u nodu A, è i so figlioli seranu i nodi D, E è F.

Leaf

Un node chì ùn hà micca figlioli hè chjamatu foglia di l'arbulu. In l'esempiu, i nodi D, E, F, G, I, J, K seranu foglie.

Questa hè a terminologia basica. Altri cuncetti seranu discututi dopu. Dunque, un arbulu binariu hè un arbulu in quale ogni node ùn hà micca più di dui figlioli. Comu avete capitu, l'arbulu da l'esempiu ùn serà micca binariu, perchè i nodi B è H anu più di dui figlioli. Eccu un esempiu di un arbre binariu:

Arbulu binariu o cumu preparà un arbulu di ricerca binariu

I nodi di l'arbulu ponu cuntene ogni infurmazione. Un arbre di ricerca binariu hè un arbre binariu chì hà e seguenti proprietà:

  1. Tramindui subtrees left è right sò arburi di ricerca binari.
  2. Tutti i nodi di u subarbre di manca di un node arbitrariu X anu valori di chjave di dati menu di u valore di chjave di dati di u node X stessu.
  3. Tutti i nodi di u subtree right di un node arbitrariu X anu valori di chjave di dati più grande o uguali à u valore di a chjave di dati di u node X stessu.

Chjave - qualchi caratteristiche di u node (per esempiu, un numeru). A chjave hè necessaria per pudè truvà l'elementu di l'arburu à quale sta chjave currisponde. Esempiu di l'arburu di ricerca binariu:

Arbulu binariu o cumu preparà un arbulu di ricerca binariu

vista d'arburu

Cum'è anderaghju, includeraghju alcuni pezzi di codice (forse incomplete) per migliurà a vostra intelligenza. U codice sanu sarà à a fine di l'articulu.

L'arburu hè custituitu di nodi. Struttura di u nodu:

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;
    }
//...остальные методы узла
}

Ogni node hà dui figlioli (hè abbastanza pussibule chì i zitelli leftChild è / o rightChild seranu nulli). Probabilmente avete capitu chì in questu casu i dati numerichi sò i dati guardati in u node; chjave - chjave node.

Avemu capitu u nodo, avà parlemu di i prublemi pressanti nantu à l'arburi. In seguitu, a parolla "arbre" significarà u cuncettu di un arbre di ricerca binariu. Struttura di l'arburu binariu:

public class BinaryTree<T> {
     private Node<T> root;

    //методы дерева
}

Cum'è un campu di classi, avemu bisognu solu a radica di l'arbulu, perchè da a radica, utilizendu i metudi getLeftChild () è getRightChild (), pudete ghjunghje à qualsiasi node di l'arbulu.

Algoritmi di l'arburu

Поиск

Dicemu chì avete un arbre custruitu. Cumu truvà l'elementu cù chjave chjave? Avete bisognu di trasfurmà in sequenza da a radica in l'arbulu è paragunate u valore di a chjave cù a chjave di u prossimu node: se a chjave hè menu menu di a chjave di u prossimu node, andate à u discendenti di manca di u node, se più - à a diritta, se i chjavi sò uguali - u node desideratu hè truvatu! Codice pertinente:

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;
}

Se u currente diventa nulu, allura l'iterazione hà righjuntu a fine di l'arbulu (à un livellu cuncettuale, site in un locu inesistente in l'arbulu - un zitellu di una foglia).

Cunsiderate l'efficienza di l'algoritmu di ricerca nantu à un arbulu equilibratu (un arbulu in quale i nodi sò distribuiti più o menu uniformi). Allora l'efficienza di ricerca serà O(log(n)), è u logaritmu di basa 2. Vede: se ci sò n elementi in un arbulu equilibratu, allora questu significa chì ci sarà log (n) basi 2 livelli di l'arbulu. È in a ricerca, per un passu di u ciculu, falà un livellu.

inserisci

Se avete capitu l'essenza di a ricerca, ùn serà micca difficiule per voi per capiscenu l'inserzione. Basta à falà à a foglia di l'arbulu (sicondu e regule di discendenza descritte in a ricerca) è diventà u so discendenti - left or right, secondu a chjave. Implementazione:

   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;
                    }
                }
            }
        }
    }

In questu casu, in più di u node attuale, hè necessariu di almacenà infurmazione nantu à u parent di u node attuale. Quandu u currente diventa nulu, a variabile parente cuntene a foglia chì avemu bisognu.
L'efficienza di inserimentu serà ovviamente uguale à quella di a ricerca - O(log(n)).

Delete

L'eliminazione hè l'operazione più cumplessa chì deve esse fatta cù un arbre. Hè chjaru chì prima serà necessariu di truvà l'elementu chì avemu da sguassà. Ma allora chì ? Se simpricimenti settemu a so riferenza à null, allora perdemu l'infurmazioni nantu à u subtree chì a radica hè questu node. I metudi di eliminazione di l'arburu sò spartuti in trè casi.

Primu casu. U node per esse eliminatu ùn hà micca figlioli.

Se u node per esse eliminatu ùn hà micca figlioli, significa chì hè una foglia. Dunque, pudete simpricimenti stabilisce i campi leftChild o rightChild di u so parente à null.

Secondu casu. U node per esse eliminatu hà un figliolu

Stu casu ùn hè ancu assai difficiule. Riturnemu à u nostru esempiu. Eppo supponi chì avemu bisognu di sguassà un elementu cù a chjave 14. Accettate chì, postu chì hè u figliolu ghjustu di un node cù a chjave 10, allora qualcunu di i so discendenti (in questu casu, u dirittu) avarà una chjave più grande di 10, perchè tù pò facirmenti "tagliatu" da l'arbulu, è cunnette u genitore direttamente à u zitellu di u node esse sguassatu, i.e. cunnette u node cù a chjave 10 à u node 13. A situazione seria simile s'ellu ci vole à sguassà un node chì hè u zitellu manca di u so parente. Pensate per voi stessu - una analogia esatta.

Terzu casu. Node hà dui figlioli

U casu più difficiule. Fighjemu un novu esempiu.

Arbulu binariu o cumu preparà un arbulu di ricerca binariu

Cerca un successore

Dicemu chì avemu bisognu di caccià u node cù a chjave 25. Quale avemu da mette in u so locu ? Unu di i so seguitori (discendenti o discendenti di discendenti) deve diventà successore(quellu chì pigliarà u locu di u node sguassatu).

Cumu sapete quale deve esse u successore ? Intuitivamente, questu hè u node in l'arbulu chì a chjave hè u prossimu più grande da u node chì hè eliminatu. L'algoritmu hè u seguitu. Avete bisognu à andà à u so figliolu ghjustu (sempre à u dirittu, perchè era digià dettu chì a chjave di u successore hè più grande ch'è a chjave di u node chì hè sguassatu), è dopu passà per a catena di i zitelli di manca di questu dirittu. zitellu. In l'esempiu, duvemu andà à u node cù a chjave 35, è poi falà a catena di i so figlioli manca à a foglia - in questu casu, sta catena hè custituita solu da u node cù a chjave 30. Strictly speaking, avemu cercatu. u node più chjucu in u settore di nodi più grande di u node desideratu.

Arbulu binariu o cumu preparà un arbulu di ricerca binariu

Codice di u metudu di ricerca di successore:

    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;
    }

U codice cumpletu di u metudu di sguassà:

public boolean delete(int deleteKey) {
        Node<T> current = root;
        Node<T> parent = current;
        boolean isLeftChild = false;//В зависимости от того, является ли  удаляемый узел левым или правым потомком своего родителя, булевская переменная isLeftChild будет принимать значение true или 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;
    }

A cumplessità pò esse approssimata à O(log(n)).

Truvà u massimu / minimu in un arbre

Ovviamente, cumu truvà u valore minimu / massimu in l'arbulu - avete bisognu à passà in sequenza per a catena di elementi left / right di l'arbulu, rispettivamente; quandu avete ghjunghje à a foglia, serà l'elementu minimu / massimu.

    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;
    }

Cumplessità - O(log(n))

Bypass simmetricu

Traversal hè una visita à ogni node di l'arbulu per fà qualcosa cun ellu.

Algoritmu trasversale simmetricu recursive:

  1. Fate una azzione nantu à u zitellu manca
  2. Fate una azzione cun voi stessu
  3. Fate una azzione nantu à u zitellu ghjustu

Code:

    public void inOrder(Node<T> current) {
        if (current != null) {
            inOrder(current.getLeftChild());
            System.out.println(current.getData() + " ");//Здесь может быть все, что угодно
            inOrder(current.getRightChild());
        }
    }

cunchiusioni

Finalmente! Se ùn aghju micca spiegatu qualcosa o avè qualchì cumentu, allora aghju aspittendu in i cumenti. Cum'è prumessu, quì hè u codice cumpletu.

Node.java:

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 void setLeftChild(Node<T> newNode) {
        leftChild = newNode;
    }

    public void setRightChild(Node<T> newNode) {
        rightChild = newNode;
    }

    public Node<T> getLeftChild() {
        return leftChild;
    }

    public Node<T> 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());
        }
    }
}

PS

Degenerazione a O(n)

Parechje di voi avete nutatu: chì si fate l'arbulu sbilanciatu? Per esempiu, mette nodi in l'arbulu cù chjavi crescente: 1,2,3,4,5,6 ... Allora l'arbulu serà un pocu reminiscente di una lista ligata. È iè, l'arbulu perderà a so struttura d'arburu, è da quì l'efficienza di l'accessu di dati. A cumplessità di l'operazioni di ricerca, inserimentu è eliminazione diventerà a stessa cum'è per una lista ligata: O (n). Questu hè unu di i più impurtanti, in my opinion, disadvantages di l'arburi binari.

Solu l'utilizatori registrati ponu participà à l'indagine. Firmà lu, per piacè.

Ùn sò micca statu nantu à Habré per un bellu pezzu, è mi piacerebbe sapè chì articuli nantu à quali temi vulete vede più ?

  • Strutture di dati

  • Algoritmi (DP, recursione, cumpressione di dati, etc.)

  • Applicazione di strutture di dati è algoritmi in a vita reale

  • Programmazione di applicazioni Android in Java

  • Programmazione di l'applicazioni Web Java

2 utilizatori anu vutatu. 1 utilizatore s'hè astenutu.

Fonte: www.habr.com

Add a comment