Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Preliwd

Mae'r erthygl hon yn ymwneud Γ’ choed chwilio deuaidd. Yn ddiweddar gwnes i erthygl am cywasgu data gan ddefnyddio dull Huffman. Yno ni wnes i dalu llawer o sylw i goed deuaidd, oherwydd nid oedd y dulliau chwilio, mewnosod a dileu yn berthnasol. Nawr penderfynais ysgrifennu erthygl am goed. Gadewch i ni ddechrau.

Mae coeden yn strwythur data sy'n cynnwys nodau wedi'u cysylltu gan ymylon. Gallwn ddweud bod coeden yn achos arbennig o graff. Dyma goeden enghreifftiol:

Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Nid coeden chwilio ddeuaidd yw hon! Mae popeth wedi'i dorri!

Terminoleg

Root

Gwreiddyn coed β€” dyma ei nΓ΄d uchaf. Yn yr enghraifft, dyma nod A. Mewn coeden, dim ond un llwybr all arwain o'r gwreiddyn i unrhyw nod arall! Mewn gwirionedd, gellir ystyried unrhyw nod fel gwraidd yr is-goeden sy'n cyfateb i'r nod hwn.

Rhieni/disgynyddion

Mae gan bob nod ac eithrio'r gwraidd un ymyl yn union yn arwain at nod arall. Gelwir y nod sydd wedi'i leoli uwchben yr un presennol rhiant y nod hwn. Gelwir nod sydd wedi'i leoli o dan yr un presennol ac sy'n gysylltiedig ag ef disgynnydd y nod hwn. Gadewch i ni ddefnyddio enghraifft. Gadewch i ni gymryd nod B, yna bydd ei riant yn nod A, a'i blant fydd nodau D, E ac F.

Лист

Bydd nod heb blant yn cael ei alw'n ddeilen y goeden. Yn yr enghraifft, nodau D, E, F, G, I, J, K fydd y dail.

Dyma'r derminoleg sylfaenol. Bydd cysyniadau eraill yn cael eu trafod ymhellach. Felly, coeden ddeuaidd yw coeden lle na fydd gan bob nod fwy na dau o blant. Fel y gwnaethoch chi ddyfalu, ni fydd y goeden o'r enghraifft yn ddeuaidd, oherwydd mae gan nodau B a H fwy na dau o blant. Dyma enghraifft o goeden ddeuaidd:

Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Gall nodau'r goeden gynnwys unrhyw wybodaeth. Mae coeden chwilio ddeuaidd yn goeden ddeuaidd sydd Γ’'r priodweddau canlynol:

  1. Mae is-goed chwith a dde yn goed chwilio deuaidd.
  2. Mae gan bob nod o is-goeden chwith nod mympwyol X werthoedd allwedd data sy'n llai na gwerth allwedd data nod X ei hun.
  3. Mae gan bob nod yn is-goeden dde nod mympwyol X werthoedd allwedd data sy'n fwy neu'n hafal i werth allwedd data nod X ei hun.

Allwedd β€” unrhyw nodwedd o'r nod (er enghraifft, rhif). Mae angen yr allwedd fel y gallwch ddod o hyd i'r elfen goeden y mae'r allwedd hon yn cyfateb iddi. Enghraifft o goeden chwilio ddeuaidd:

Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Golygfa coed

Wrth i ni symud ymlaen, byddaf yn darparu rhai darnau o god (anghyflawn o bosibl) i wella'ch dealltwriaeth. Bydd y cod llawn ar ddiwedd yr erthygl.

Mae coeden yn cynnwys nodau. Strwythur 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;
    }
//...ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΡƒΠ·Π»Π°
}

Mae gan bob nod ddau o blant (mae'n ddigon posibl y bydd y plentyn chwith a/neu'r plant Plentyn ar y dde yn cynnwys gwerth null). Mae'n debyg eich bod wedi sylweddoli yn yr achos hwn mai'r data rhif yw'r data sydd wedi'i storio yn y nod; allwedd β€” allwedd nod.

Rydyn ni wedi datrys y cwlwm, nawr gadewch i ni siarad am broblemau dybryd am goed. O hyn ymlaen, wrth y gair β€œcoeden” byddaf yn golygu'r cysyniad o goeden chwilio ddeuaidd. Strwythur coed deuaidd:

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

    //ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Π΄Π΅Ρ€Π΅Π²Π°
}

Dim ond gwraidd y goeden sydd ei angen arnom fel maes dosbarth, oherwydd o'r gwraidd, gan ddefnyddio'r dulliau getLeftChild() a getRightChild(), gallwch gyrraedd unrhyw nod yn y goeden.

Algorithmau mewn coeden

Chwilio

Gadewch i ni ddweud bod gennych goeden wedi'i hadeiladu. Sut i ddod o hyd i elfen gyda'r allwedd allweddol? Mae angen i chi symud yn ddilyniannol o'r gwreiddyn i lawr y goeden a chymharu gwerth y cywair ag allwedd y nod nesaf: os yw'r allwedd yn llai nag allwedd y nod nesaf, yna ewch i ddisgynnydd chwith y nod, os yw mwy, i'r un iawn, os yw'r allweddi yn gyfartal, mae'r nod dymunol yn dod o hyd! Cod perthnasol:

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

Os yw'r cerrynt yn dod yn null, yna mae'r chwiliad wedi cyrraedd diwedd y goeden (ar lefel gysyniadol, rydych chi mewn man nad yw'n bodoli yn y goeden - disgynnydd deilen).

Gadewch i ni ystyried effeithiolrwydd yr algorithm chwilio ar goeden gytbwys (coeden lle mae nodau'n cael eu dosbarthu'n fwy neu'n llai cyfartal). Yna bydd yr effeithlonrwydd chwilio yn O(log(n)), a'r logarithm yw sylfaen 2. Edrychwch: os oes n elfen mewn coeden gytbwys, mae hyn yn golygu y bydd log(n) i sylfaen 2 lefel y coeden. Ac wrth chwilio, mewn un cam o'r cylch, rydych chi'n mynd i lawr un lefel.

mewnosod

Os ydych chi'n deall hanfod y chwiliad, yna ni fydd deall y mewnosodiad yn anodd i chi. Does ond angen i chi fynd i lawr i ddeilen y goeden (yn Γ΄l y rheolau disgyniad a ddisgrifir yn y chwiliad) a dod yn ddisgynnydd iddo - i'r chwith neu'r dde, yn dibynnu ar yr allwedd. Gweithredu:

   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 yr achos hwn, yn ychwanegol at y nod cyfredol, mae angen storio gwybodaeth am riant y nod cyfredol. Pan ddaw cerrynt yn hafal i nwl, bydd y newidyn rhiant yn cynnwys y ddalen sydd ei hangen arnom.
Mae'n amlwg y bydd effeithlonrwydd mewnosod yr un fath ag effeithlonrwydd chwilio - O(log(n)).

Tynnu

Tynnu yw'r llawdriniaeth anoddaf y bydd angen ei chyflawni ar goeden. Mae’n amlwg yn gyntaf y bydd angen inni ddod o hyd i’r elfen yr ydym yn mynd i’w dileu. Ond beth felly? Os byddwn yn syml yn gosod ei gyfeiriad at nwl, byddwn yn colli gwybodaeth am yr is-goeden y mae'r nod hwn yn wraidd iddi. Rhennir dulliau tynnu coed yn dri achos.

Achos cyntaf. Nid oes gan y nod sy'n cael ei ddileu unrhyw blant

Os nad oes gan y nod sy'n cael ei ddileu unrhyw blant, yna mae hyn yn golygu ei fod yn ddeilen. Felly, gallwch chi osod meysydd leftChild neu rightChild ei riant i null.

Ail achos. Mae gan y nod sydd i'w ddileu un plentyn

Nid yw'r achos hwn yn gymhleth iawn ychwaith. Gadewch i ni ddychwelyd at ein hesiampl. Gadewch i ni ddweud bod angen i ni ddileu elfen gydag allwedd 14. Cytuno, gan ei fod yn ddisgynnydd cywir nod ag allwedd 10, y bydd gan unrhyw un o'i ddisgynyddion (yr un iawn yn yr achos hwn) allwedd sy'n fwy na 10, felly chi yn gallu ei β€œdorri” allan o’r goeden yn hawdd, a chysylltu’r rhiant yn uniongyrchol Γ’ phlentyn y nod sy’n cael ei ddileu, h.y. cysylltu'r nod ag allwedd 10 i nod 13. Byddai'r sefyllfa'n debyg pe bai angen dileu nod sy'n blentyn chwith i'w riant. Meddyliwch am y peth eich hun - cyfatebiaeth union.

Trydydd achos. Mae gan nod ddau o blant

Yr achos anoddaf. Gadewch i ni edrych ar enghraifft newydd.

Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Chwilio am olynydd

Gadewch i ni ddweud bod angen i ni ddileu nod ag allwedd 25. Pwy ddylem ni ei roi yn ei le? Rhaid i un o'i ddilynwyr (disgynyddion neu ddisgynyddion disgynyddion) ddod olynydd(yr un a gymmer le y nΓ΄d yn cael ei symud).

Sut i ddeall pwy ddylai ddod yn olynydd? Yn reddfol, mae hwn yn nod yn y goeden a'i allwedd yw'r mwyaf nesaf o'r nod sy'n cael ei ddileu. Mae'r algorithm fel a ganlyn. Mae angen i chi fynd at ei ddisgynnydd dde (bob amser i'r un dde, oherwydd dywedwyd eisoes bod allwedd yr olynydd yn fwy na'r allwedd y nod sy'n cael ei ddileu), ac yna mynd trwy'r gadwyn o ddisgynyddion chwith y disgynnydd dde hwn . Yn yr enghraifft, byddem yn mynd i'r nod gydag allwedd 35, ac yna'n mynd i lawr i'r ddeilen trwy gadwyn ei blant chwith - yn yr achos hwn, mae'r gadwyn hon yn cynnwys y nod gydag allwedd 30 yn unig. A siarad yn fanwl, rydym yn edrych ar gyfer y nod lleiaf yn y set o nodau sy'n fwy na'r un yr ydym yn chwilio amdano.

Coeden Ddeuaidd neu sut i baratoi coeden chwilio ddeuaidd

Cod dull chwilio olynol:

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

Cod llawn ar gyfer y dull dileu:

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

Gellir brasamcanu'r cymhlethdod i O(log(n)).

Dod o hyd i uchafswm/lleiafswm mewn coeden

Mae'n amlwg sut i ddarganfod y gwerth lleiaf/uchaf mewn coeden - mae angen i chi symud yn ddilyniannol ar hyd y gadwyn o elfennau chwith/dde y goeden, yn y drefn honno; pan fyddwch chi'n cyrraedd y ddalen, dyma fydd yr elfen leiaf/uchaf.

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

Cymhlethdod - O(log(n))

Ffordd osgoi cymesur

Mae Traversal yn ymweld Γ’ phob nod o'r goeden er mwyn gwneud rhywfaint o weithredu ag ef.

Algorithm croesi cymesurol dychweliadol:

  1. Gwnewch weithred ar y plentyn chwith
  2. Gwnewch weithred gyda chi'ch hun
  3. Gwnewch weithred ar y plentyn iawn

CΓ΄d:

    public void inOrder(Node<T> current) {
        if (current != null) {
            inOrder(current.getLeftChild());
            System.out.println(current.getData() + " ");//Π—Π΄Π΅ΡΡŒ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ всС, Ρ‡Ρ‚ΠΎ ΡƒΠ³ΠΎΠ΄Π½ΠΎ
            inOrder(current.getRightChild());
        }
    }

Casgliad

O'r diwedd! Os nad wyf wedi egluro unrhyw beth neu os oes gennyf unrhyw sylwadau, gadewch nhw yn y sylwadau. Fel yr addawyd, rwy'n darparu'r cod cyflawn.

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

Dirywiad i O(n)

Efallai bod llawer ohonoch wedi sylwi: beth os gwnewch y goeden yn anghytbwys? Er enghraifft, rhowch nodau ag allweddi cynyddol mewn coeden: 1,2,3,4,5,6... Yna bydd y goeden braidd yn debyg i restr gysylltiedig. Ac ie, bydd y goeden yn colli ei strwythur coed, ac felly effeithlonrwydd mynediad data. Bydd cymhlethdod gweithrediadau chwilio, mewnosod a dileu yn dod yr un fath Γ’ rhai rhestr gysylltiedig: O(n). Dyma un o'r anfanteision pwysicaf, yn fy marn i, o goed deuaidd.

Dim ond defnyddwyr cofrestredig all gymryd rhan yn yr arolwg. Mewngofnodios gwelwch yn dda.

Nid wyf wedi bod ar y canolbwynt ers amser maith, a hoffwn wybod pa erthyglau ar ba bynciau yr hoffech chi weld mwy ohonynt?

  • Strwythurau Data

  • Algorithmau (DP, dychweliad, cywasgu data, ac ati)

  • Cymhwyso strwythurau data ac algorithmau mewn bywyd go iawn

  • Rhaglennu Cymwysiadau Android yn Java

  • Rhaglennu cymwysiadau gwe yn Java

Pleidleisiodd 2 ddefnyddiwr. Ymatalodd 1 defnyddiwr.

Ffynhonnell: www.habr.com

Ychwanegu sylw