Binary Tree of hoe't jo in binêre sykbeam tariede

Foarútgong

Dit artikel giet oer binêre sykbeammen. Ik koartlyn skreau in artikel oer data kompresje troch de Huffman metoade. Dêr haw ik net echt omtinken foar binêre beammen, om't de metoaden fan sykjen, ynfoegje, wiskje net relevant wiene. No haw ik besletten om in artikel oer beammen te skriuwen. Miskien sille wy begjinne.

In beam is in gegevensstruktuer besteande út knooppunten ferbûn troch rânen. Wy kinne sizze dat in beam in spesjaal gefal is fan in grafyk. Hjir is in foarbyldbeam:

Binary Tree of hoe't jo in binêre sykbeam tariede

Dit is gjin binêre sykbeam! Alles is ûnder de snie!

Terminology

Root

Beam woartel is de boppeste knooppunt. Yn it foarbyld is dit knooppunt A. Yn 'e beam kin mar ien paad fan 'e woartel nei in oare knooppunt liede! Yn feite kin elke knooppunt wurde beskôge as de woartel fan 'e subtree dy't oerienkomt mei dit knooppunt.

Âlden / neiteam

Alle knooppunten útsein de woartel hawwe krekt ien râne dy't liedt nei in oare knooppunt. It knooppunt boppe it aktuele knooppunt wurdt neamd âlder dizze knooppunt. In knooppunt leit ûnder de hjoeddeiske en ferbûn mei it wurdt neamd neikommeling dizze knooppunt. Litte wy in foarbyld nimme. Nim node B, dan sil syn âlder node A wêze, en syn bern sille knopen D, E en F wêze.

Leaf

In knooppunt dat gjin bern hat wurdt in blêd fan 'e beam neamd. Yn it foarbyld sille knopen D, E, F, G, I, J, K blêden wêze.

Dit is de basis terminology. Oare begripen sille letter besprutsen wurde. Dus, in binêre beam is in beam wêryn elke node net mear as twa bern sil hawwe. As jo ​​​​riede, sil de beam út it foarbyld net binêr wêze, om't knooppunten B en H mear as twa bern hawwe. Hjir is in foarbyld fan in binêre beam:

Binary Tree of hoe't jo in binêre sykbeam tariede

De knopen fan 'e beam kinne elke ynformaasje befetsje. In binêre sykbeam is in binêre beam dy't de folgjende eigenskippen hat:

  1. Sawol lofts as rjochts subtrees binne binêre sykbeammen.
  2. Alle knooppunten fan 'e linker subtree fan in willekeurige knooppunt X hawwe gegevenskaaiwearden minder dan de gegevenskaaiwearde fan knooppunt X sels.
  3. Alle knooppunten fan 'e rjochter subtree fan in willekeurige knooppunt X hawwe gegevenskaaiwearden grutter as of gelyk oan de wearde fan' e gegevenskaai fan knooppunt X sels.

Key - wat karakteristyk fan 'e knooppunt (bygelyks in nûmer). De kaai is nedich om it elemint fan 'e beam te finen wêrmei dizze kaai oerienkomt. Foarbyld fan Binary Search Tree:

Binary Tree of hoe't jo in binêre sykbeam tariede

beam view

As ik fierder gean, sil ik wat (miskien ûnfolsleine) stikken koade opnimme om jo begryp te ferbetterjen. De folsleine koade sil oan 'e ein fan it artikel wêze.

De beam is opboud út knopen. Node struktuer:

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

Elk knooppunt hat twa bern (it is goed mooglik dat de leftChild en/of rightChild bern nul binne). Jo hawwe wierskynlik begrepen dat yn dit gefal de nûmergegevens de gegevens binne opslein yn 'e node; kaai ​​- node kaai.

Wy hawwe de knoop útfûn, litte wy no prate oer de driuwende problemen oer beammen. Hjir en hjirûnder sil it wurd "beam" it konsept fan in binêre sykbeam betsjutte. Binêre beamstruktuer:

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

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

As klasse fjild, wy moatte allinnich de woartel fan 'e beam, want fan' e woartel, mei help fan de metoaden getLeftChild () en getRightChild (), kinne jo krije ta eltse knooppunt fan 'e beam.

Tree Algoritmen

Поиск

Litte wy sizze dat jo in boude beam hawwe. Hoe kinne jo elemint fine mei kaaikaai? Jo moatte sequentially ferpleatse fan 'e woartel nei ûnderen de beam en fergelykje de wearde fan kaai mei de kaai fan de folgjende knooppunt: as de kaai is minder as de kaai fan de folgjende knooppunt, gean dan nei de linker neisiet fan 'e knooppunt, as mear - nei rjochts, as de kaaien binne gelyk - de winske knooppunt wurdt fûn! Relevante koade:

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

As stroom nul wurdt, dan hat iteraasje it ein fan 'e beam berikt (op konseptueel nivo binne jo op in net-besteand plak yn' e beam - in bern fan in blêd).

Beskôgje de effisjinsje fan it sykalgoritme op in lykwichtige beam (in beam wêryn knooppunten min of mear gelyk ferdield binne). Dan is de sykeffisjinsje O(log(n)), en de logaritme fan basis 2. Sjoch: as der n eleminten binne yn in lykwichtige beam, dan betsjut dit dat der log(n) basis 2 nivo's fan 'e beam komme. En yn it sykjen, foar ien stap fan 'e syklus, geane jo ien nivo del.

ynfoegje

As jo ​​​​de essinsje fan 'e sykaksje hawwe begrepen, dan sil it jo net dreech wêze om de ynfoegje te begripen. Jo moatte gewoan nei it blêd fan 'e beam gean (neffens de regels fan komôf beskreaun yn' e sykaksje) en wurde syn neiteam - lofts of rjochts, ôfhinklik fan 'e kaai. Implementaasje:

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

Yn dit gefal, neist de aktuele knooppunt, is it nedich om ynformaasje te bewarjen oer de âlder fan 'e aktuele knooppunt. As aktueel nul wurdt, sil de âlderfariabele it blêd befetsje dat wy nedich binne.
De ynfoeffisjinsje sil fansels itselde wêze as dy fan it sykjen - O(log(n)).

Ferwiderje

Wiskje is de meast komplekse operaasje dy't dien wurde sil mei in beam. It is dúdlik dat it earst nedich is om it elemint te finen dat wy sille fuortsmite. Mar wat dan? As wy de ferwizing gewoan op nul sette, dan sille wy ynformaasje ferlieze oer de subtree waans root dizze knooppunt is. Metoaden foar it fuortheljen fan beam binne ferdield yn trije gefallen.

Earste gefal. De te ferwiderjen knoop hat gjin bern.

As it te wiskjen knooppunt gjin bern hat, betsjut dit dat it in blêd is. Dêrom kinne jo de fjilden leftChild of rightChild fan har âlder gewoan op nul sette.

Twadde gefal. De te ferwiderjen knoop hat ien bern

Dit gefal is ek net hiel dreech. Litte wy weromgean nei ús foarbyld. Stel dat wy moatte wiskje in elemint mei kaai 14. Akseptearje dat sûnt it is it rjocht bern fan de knooppunt mei kaai 10, dan ien fan syn neiteam (yn dit gefal, de rjochter ien) sil hawwe in kaai grutter as 10, dus jo kinne maklik "cut" it út 'e beam, en ferbine de âlder direkt oan it bern fan de knooppunt wurdt wiske, d.w.s. ferbine it knooppunt mei kaai 10 oan knooppunt 13. De situaasje soe wêze ferlykber as wy moasten wiskje in knooppunt dat is it linker bern fan syn âlder. Tink der sels oer - in krekte analogy.

Tredde gefal. Node hat twa bern

It dreechste gefal. Litte wy nei in nij foarbyld sjen.

Binary Tree of hoe't jo in binêre sykbeam tariede

Sykje nei in opfolger

Litte wy sizze dat wy it knooppunt fuortsmite moatte mei de kaai 25. Wa sille wy yn it plak sette? Ien fan syn folgelingen (ôfstammelingen of neiteam fan neiteam) moat wurde opfolger(dejinge dy't sil nimme it plak fan de fuorthelle knooppunt).

Hoe witte jo wa't de opfolger wêze moat? Yntuïtyf is dit it knooppunt yn 'e beam wêrfan de kaai de folgjende grutste is fan it knooppunt dat fuorthelle wurdt. It algoritme is as folget. Jo moatte gean nei syn rjochter bern (altyd nei de rjochter, om't it waard al sein dat de kaai fan 'e opfolger is grutter as de kaai fan' e knooppunt wurdt wiske), en dan gean troch de keatling fan lofter bern fan dit rjocht bern. Yn it foarbyld, wy moatte navigearje nei de knooppunt mei kaai 35, en dan rinne del de keatling fan syn linker bern nei it blêd - yn dit gefal, dizze keatling bestiet allinnich út de knooppunt mei kaai 30. Strikt sjoen, wy binne op syk nei de lytste knooppunt yn de set fan knooppunten grutter as de winske knooppunt.

Binary Tree of hoe't jo in binêre sykbeam tariede

Opfolger sykmetoade koade:

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

De folsleine koade fan 'e wiskjemetoade:

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

De kompleksiteit kin benadere wurde oan O(log(n)).

It finen fan it maksimum / minimum yn in beam

Fansels, hoe't jo de minimale / maksimale wearde yn 'e beam fine - jo moatte opfolgjend troch de ketting fan lofts / rjochts eleminten fan' e beam gean, respektivelik; as jo by it blêd komme, sil it minimum / maksimum elemint wêze.

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

Kompleksiteit - O(log(n))

Symmetryske bypass

Traversal is in besite oan elk knooppunt fan de beam om der wat mei te dwaan.

Rekursyf symmetrysk traversalalgoritme:

  1. Meitsje in aksje op it linker bern
  2. Meitsje in aksje mei dysels
  3. Meitsje in aksje op it goede bern

Code:

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

konklúzje

Úteinlik! As ik wat net útlein haw of opmerkings haw, dan wachtsje ik yn 'e kommentaren. Lykas tasein, hjir is de folsleine koade.

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

Degeneraasje nei O(n)

In protte fan jo hawwe miskien opfallen: wat as jo meitsje dat de beam ûnbalâns wurdt? Set bygelyks knopen yn 'e beam mei tanimmende kaaien: 1,2,3,4,5,6 ... Dan sil de beam wat tinke oan in keppele list. En ja, de beam sil syn beamstruktuer ferlieze, en dêrtroch de effisjinsje fan gegevenstagong. De kompleksiteit fan sykopdrachten, ynfoegje en wiskjen sil itselde wurde as dy fan in keppele list: O(n). Dit is ien fan 'e wichtichste, nei myn miening, neidielen fan binêre beammen.

Allinnich registrearre brûkers kinne meidwaan oan 'e enkête. Ynlogge, asjebleaft.

Ik haw noch lang net op Habré west, en ik soe graach witte hokker artikels oer hokker ûnderwerpen jo mear wolle sjen?

  • Data Struktueren

  • Algoritmen (DP, rekursje, datakompresje, ensfh.)

  • Tapassing fan gegevensstruktueren en algoritmen yn it echte libben

  • Programmearje Android-applikaasjes yn Java

  • Java Web Application Programming

2 brûkers stimden. 1 brûker ûnthâlde him.

Boarne: www.habr.com

Add a comment