Implementarea unui iterator peste un arbore binar de căutare

voturi
30

Am fost de codificare o grămadă de diferite implementări binare de arbori de căutare recent (AVL, splay, treap) și sunt curios dacă există un deosebit „bun“ mod de a scrie un iterator pentru a traversa aceste structuri. L-am folosit chiar acum soluție este de a avea fiecare nod în magazin BST pointerii elementele următoare și anterioare din copac, care reduce iterație la un standard de listă legate de iterație. Cu toate acestea, eu nu sunt foarte mulțumit cu acest răspuns. Aceasta crește utilizarea spațiului fiecărui nod prin două pointeri (următoare și anterioare), și într-un sens este doar inselat.

Știu o modalitate de a construi un iterator arbore binar de căutare care utilizează O (h) spațiu de stocare auxiliar (unde h este înălțimea de copac), folosind o stivă pentru a ține evidența nodurilor de frontieră pentru a explora mai târziu, dar eu am rezistat codificare asta din cauza utilizarea memoriei. Am fost în speranța există o modalitate de a construi un iterator care utilizează spațiu numai constantă.

Întrebarea mea este aceasta - există o modalitate de a proiecta un iterator peste un arbore binar de căutare cu următoarele proprietăți?

  1. Elementele sunt vizitate în ordine crescătoare (adică o parcurgeri inordine)
  2. next()și hasNext()interogări rula în O (1) timp.
  3. Utilizarea memoriei este O (1)

Pentru a face mai ușor, este bine dacă presupuneți că structura arborescentă nu se schimbă forma în timpul iterație (adică nu deleții sau rotatii), dar ar fi foarte cool dacă a existat o soluție care ar putea ocupa într-adevăr, acest lucru.

Întrebat 03/01/2011 la 02:54
sursa de către utilizator
În alte limbi...                            


8 răspunsuri

voturi
1

Arborele parcurgeri , de la Wikipedia:

Toate implementarile de probă va necesita spațiu de apel stiva proporțională cu înălțimea copacului. Într-un copac prost echilibrat, acest lucru poate fi destul de considerabile.

Putem elimina cerința stivei prin menținerea indicii-mamă în fiecare nod, sau prin filetare copac. În cazul utilizării fire, acest lucru va permite mult îmbunătățit inordine parcurgeri, cu toate că recuperarea nodului părinte necesar pentru precomandă și postordine parcurgeri va fi mai lentă decât un algoritm bazat stiva simplu.

În articol există unele pseudocod pentru iterația cu O (1) de stat, care poate fi ușor adaptat la un iterator.

Publicat 03/01/2011 la 03:09
sursa de către utilizator

voturi
30

Cele mai simple magazine posibil iterator ultima cheie de văzut, și apoi pe următoarea iterație, caută copac pentru cel puțin limita superioară pentru acea cheie. Iteratiile este O (log n). Acest lucru are avantajul de a fi foarte simplu. În cazul în care cheile sunt mici, atunci iterators sunt, de asemenea, mici. desigur, are dezavantajul de a fi un mod relativ lent de iterarea prin copac. De asemenea, nu va funcționa pentru secvențele non-unice.

Unii copaci folosesc exact punerea în aplicare utilizați deja, pentru că este important pentru utilizarea lor specifică faptul că scanarea este foarte rapid. În cazul în care numărul de chei în fiecare nod este mare, atunci pedeapsa de a stoca indicii frate nu este prea oneroase. Cele mai multe B-Copacii folosesc această metodă.

multe implementări de căutare arbori păstrează un indicator părinte pe fiecare nod pentru a simplifica alte operațiuni. Dacă aveți că, atunci puteți utiliza un indicator simplu pentru ultimul nod văzut ca starea iterator tale. la fiecare iterație, te uiti pentru următorul copil în părinte ultimul nod văzut lui. în cazul în care nu mai sunt frați, atunci du-te în sus un nivel mai mult.

Dacă nici unul dintre aceste tehnici vi se potrivesc, puteți utiliza o stivă de noduri, stocate în iterator. Aceasta servește aceeași funcție ca și stiva de apel funcția atunci când iterarea prin arborele de căutare în mod normal, dar în loc de looping prin frați și recursiune pe copii, ai împinge copii pe stiva și să se întoarcă fiecare frate succesive.

Publicat 03/01/2011 la 03:25
sursa de către utilizator

voturi
3

Ok, știu că acest lucru este vechi, dar am fost întrebat acest lucru într-un interviu cu Microsoft un timp înapoi și am decis să lucreze pe ea un pic. Am testat acest lucru și funcționează destul de bine.

template <typename E>
class BSTIterator
{  
  BSTNode<E> * m_curNode;
  std::stack<BSTNode<E>*> m_recurseIter;

public:
    BSTIterator( BSTNode<E> * binTree )
    {       
        BSTNode<E>* root = binTree;

        while(root != NULL)
        {
            m_recurseIter.push(root);
            root = root->GetLeft();
        }

        if(m_recurseIter.size() > 0)
        {
            m_curNode = m_recurseIter.top();
            m_recurseIter.pop();
        }
        else
            m_curNode = NULL;
    }

    BSTNode<E> & operator*() { return *m_curNode; }

    bool operator==(const BSTIterator<E>& other)
    {
        return m_curNode == other.m_curNode;
    }

    bool operator!=(const BSTIterator<E>& other)
    {
        return !(*this == other);
    }

    BSTIterator<E> & operator++() 
    { 
        if(m_curNode->GetRight())
        {
            m_recurseIter.push(m_curNode->GetRight());

            if(m_curNode->GetRight()->GetLeft())
                m_recurseIter.push(m_curNode->GetRight()->GetLeft());
        }

        if( m_recurseIter.size() == 0)
        {
            m_curNode = NULL;
            return *this;
        }       

        m_curNode = m_recurseIter.top();
        m_recurseIter.pop();

        return *this;       
    }

    BSTIterator<E> operator++ ( int )
    {
        BSTIterator<E> cpy = *this;     

        if(m_curNode->GetRight())
        {
            m_recurseIter.push(m_curNode->GetRight());

            if(m_curNode->GetRight()->GetLeft())
                m_recurseIter.push(m_curNode->GetRight()->GetLeft());
        }

        if( m_recurseIter.size() == 0)
        {
            m_curNode = NULL;
            return *this;
        }       

        m_curNode = m_recurseIter.top();
        m_recurseIter.pop();

        return cpy;
    }

};
Publicat 20/10/2012 la 05:53
sursa de către utilizator

voturi
15

După cum sa menționat TokenMacGuy puteți utiliza o stivă stocată în iterator. Iată o implementare rapidă a testat acest lucru în Java:

/**
 * An iterator that iterates through a tree using in-order tree traversal
 * allowing a sorted sequence.
 *
 */
public class Iterator {

    private Stack<Node> stack = new Stack<>();
    private Node current;

    private Iterator(Node argRoot) {
        current = argRoot;
    }

    public Node next() {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();
        Node node = current;
        current = current.right;

        return node;
    }

    public boolean hasNext() {
        return (!stack.isEmpty() || current != null);
    }

    public static Iterator iterator(Node root) {
        return new Iterator(root);
    }
}

Alte variații ar fi să traverseze copac la timpul de construcție și a salva traversal într-o listă. Puteți utiliza lista iterator după aceea.

Publicat 31/07/2013 la 00:21
sursa de către utilizator

voturi
0

Ce zici, folosind o primă metodă de căutare adâncime. Obiectul iterator doar trebuie să aibă un teanc de noduri deja vizitate.

Publicat 21/05/2014 la 22:02
sursa de către utilizator

voturi
0

Dacă utilizați stivă, veți obține doar „utilizarea memoriei Extra O (h), h este înălțimea copacului“. Cu toate acestea, dacă doriți să utilizați numai O (1) memorie suplimentară, aveți nevoie pentru a înregistra Iată analiza: - În cazul în care nodul curent are copil drept: găsi min de sub-copac dreapta - Este nodul curent nu are nici un copil drept, aveți nevoie să-l caute de la rădăcină și să păstreze actualizarea este cel mai mic strămoș, care este cel mai mic nodul următor

public class Solution {
           //@param root: The root of binary tree.

           TreeNode current;
           TreeNode root;
           TreeNode rightMost;
           public Solution(TreeNode root) {

               if(root==null) return;
                this.root = root;
                current = findMin(root);
                rightMost = findMax(root);
           }

           //@return: True if there has next node, or false
           public boolean hasNext() {

           if(current!=null && rightMost!=null && current.val<=rightMost.val)    return true; 
        else return false;
           }
           //O(1) memory.
           public TreeNode next() {
                //1. if current has right child: find min of right sub tree
                TreeNode tep = current;
                current = updateNext();
                return tep;
            }
            public TreeNode updateNext(){
                if(!hasNext()) return null;
                 if(current.right!=null) return findMin(current.right);
                //2. current has no right child
                //if cur < root , go left; otherwise, go right

                    int curVal = current.val;
                    TreeNode post = null;
                    TreeNode tepRoot = root;
                    while(tepRoot!=null){
                      if(curVal<tepRoot.val){
                          post = tepRoot;
                          tepRoot = tepRoot.left;
                      }else if(curVal>tepRoot.val){
                          tepRoot = tepRoot.right;
                      }else {
                          current = post;
                          break;
                      }
                    }
                    return post;

            }

           public TreeNode findMin(TreeNode node){
               while(node.left!=null){
                   node = node.left;
               }
               return node;
           }

            public TreeNode findMax(TreeNode node){
               while(node.right!=null){
                   node = node.right;
               }
               return node;
           }
       }
Publicat 24/04/2015 la 23:41
sursa de către utilizator

voturi
0

Utilizați O (1) spațiu, ceea ce înseamnă că nu vom folosi O (h) stivă.

A incepe:

  1. hasNext ()? current.val <= endNode.val pentru a verifica dacă arborele este complet traversată.

  2. Găsiți min prin cele mai din stânga: Ne putem uita alwasy pentru stanga cel mai mult pentru a găsi următoarea valoare minimă.

  3. După ce a lăsat-min mai este verificat (denumiți - o current). Următoarea min va fi de 2 cazuri: Dacă current.right = nul, putem păstra în căutarea pentru cel mai din stânga a copilului current.right lui, ca viitoare min!. Sau, trebuie să privim înapoi pentru părinte. Utilizați arbore binar de căutare pentru a găsi nodul curent părinte.

Notă : atunci când faci binar de căutare pentru părinte, asigurați - vă că acesta îndeplinește parent.left = curent.

Deoarece: Dacă parent.right == curent, care trebuie să-mamă a fost vizitat înainte. În arbore binar de căutare, știm că parent.val <parent.right.val. Trebuie să săriți peste acest caz special, deoarece aceasta conduce la ifinite bucla.

public class BSTIterator {
    public TreeNode root;
    public TreeNode current;
    public TreeNode endNode;
    //@param root: The root of binary tree.
    public BSTIterator(TreeNode root) {
        if (root == null) {
            return;
        }
        this.root = root;
        this.current = root;
        this.endNode = root;

        while (endNode != null && endNode.right != null) {
            endNode = endNode.right;
        }
        while (current != null && current.left != null) {
            current = current.left;
        }
    }

    //@return: True if there has next node, or false
    public boolean hasNext() {
        return current != null && current.val <= endNode.val;
    }

    //@return: return next node
    public TreeNode next() {
        TreeNode rst = current;
        //current node has right child
        if (current.right != null) {
            current = current.right;
            while (current.left != null) {
                current = current.left;
            }
        } else {//Current node does not have right child.
            current = findParent();
        }
        return rst;
    }

    //Find current's parent, where parent.left == current.
    public TreeNode findParent(){
        TreeNode node = root;
        TreeNode parent = null;
        int val = current.val;
        if (val == endNode.val) {
            return null;
        }
        while (node != null) {
            if (val < node.val) {
                parent = node;
                node = node.left;
            } else if (val > node.val) {
                node = node.right;
            } else {//node.val == current.val
                break;
            }
        }
        return parent;
    }
}
Publicat 27/01/2016 la 16:42
sursa de către utilizator

voturi
0

Prin definiție, nu este posibil pentru următorul () și hasNext () pentru a rula în O (1) timp. Când sunteți în căutarea la un anumit nod într-un BST, nu ai nici o idee despre înălțimea și structura celelalte noduri sunt, prin urmare, nu puteți doar „sari“ la nodul corect următor.

Cu toate acestea, complexitatea spațiului poate fi redus la O (1) (cu excepția memoriei pentru BST în sine). Aici este modul în care mi-ar face în C:

struct node{
    int value;
    struct node *left, *right, *parent;
    int visited;
};

struct node* iter_next(struct node* node){
    struct node* rightResult = NULL;

    if(node==NULL)
        return NULL;

    while(node->left && !(node->left->visited))
        node = node->left;

    if(!(node->visited))
        return node;

    //move right
    rightResult = iter_next(node->right);

    if(rightResult)
        return rightResult;

    while(node && node->visited)
        node = node->parent;

    return node;
}

Trucul este de a avea atât o legătură părinte, și un steag vizitat pentru fiecare nod. În opinia mea, putem argumenta că acest lucru nu este utilizarea spațiului suplimentar, aceasta este pur și simplu o parte a structurii nodului. Și, evident, iter_next () trebuie să fie numit fără starea structurii arborelui în schimbare (desigur), dar, de asemenea, că „vizitat“ steaguri nu se schimbă valorile.

Aici este funcția tester care iter_next () apeluri și imprimă valoarea de fiecare dată pentru acest copac:

                  27
               /      \
              20      62
             /  \    /  \
            15  25  40  71
             \  /
             16 21

int main(){

    //right root subtree
    struct node node40 = {40, NULL, NULL, NULL, 0};
    struct node node71 = {71, NULL, NULL, NULL, 0};
    struct node node62 = {62, &node40, &node71, NULL, 0};

    //left root subtree
    struct node node16 = {16, NULL, NULL, NULL, 0};
    struct node node21 = {21, NULL, NULL, NULL, 0};
    struct node node15 = {15, NULL, &node16, NULL, 0};
    struct node node25 = {25, &node21, NULL, NULL, 0};
    struct node node20 = {20, &node15, &node25, NULL, 0};

    //root
    struct node node27 = {27, &node20, &node62, NULL, 0};

    //set parents
    node16.parent = &node15;
    node21.parent = &node25;
    node15.parent = &node20;
    node25.parent = &node20;
    node20.parent = &node27;
    node40.parent = &node62;
    node71.parent = &node62;
    node62.parent = &node27;

    struct node *iter_node = &node27;

    while((iter_node = iter_next(iter_node)) != NULL){
        printf("%d ", iter_node->value);
        iter_node->visited = 1;
    }
    printf("\n");
    return 1;
}

Care va imprima valorile în ordine sortata:

15 16 20 21 25 27 40 62 71 
Publicat 13/02/2016 la 06:56
sursa de către utilizator

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more