Het implementeren van een iterator over een binaire zoekboom

stemmen
30

Ik heb het coderen van een heleboel verschillende binaire zoekboom implementaties onlangs (AVL, splay, treap) en ben benieuwd of er een bijzonder goede manier om een ​​iterator om deze structuren te doorkruisen te schrijven. De oplossing die ik heb op dit moment gebruikt wordt om elk knooppunt in de BST winkel verwijzingen naar de volgende en vorige elementen in de boom, die iteratie reduceert tot een standaard gekoppelde-list iteratie hebben. Echter, ik ben niet echt tevreden met dit antwoord. Het verhoogt de ruimte gebruik van elk knooppunt door twee pointers (volgende en vorige), en in zekere zin is het gewoon bedrog.

Ik weet van een manier van bouwen van een binaire zoekboom iterator dat O (h) extra opslagruimte (waar h is de hoogte van de boom) maakt gebruik van met behulp van een stapel te houden van de grens knooppunten te houden om te ontdekken later, maar ik ve verzette codering dit naar voren omdat het geheugengebruik. Ik hoopte dat er een manier om een ​​iterator die enige constante gebruik van de ruimte te bouwen.

Mijn vraag is - is er een manier om een ​​iterator over een binaire zoekboom met de volgende eigenschappen te ontwerpen?

  1. Elementen worden bezocht in oplopende volgorde (dwz een inorder traversal)
  2. next()en hasNext()query's in O (1) tijd.
  3. Geheugengebruik is O (1)

Om het gemakkelijker te maken, het is prima als je ervan uitgaat dat de boomstructuur niet van vorm verandert tijdens de iteratie (dus geen inserties, deleties, of rotaties), maar het zou wel heel gaaf zijn als er een oplossing die inderdaad dit kon behandelen was.

De vraag is gesteld op 03/01/2011 om 02:54
bron van user
In andere talen...                            


8 antwoorden

stemmen
1

Boom traversal , van Wikipedia:

Alle monster implementaties call stack ruimte evenredig is met de hoogte van de boom nodig. In een slecht gebalanceerde boom, kan dit heel aanzienlijk zijn.

We kunnen de stapel eis door het handhaven van de ouders wijzers in elk knooppunt te verwijderen, of door de boom threading. Bij gebruik van draden, kan dit nieuwe sterk verbeterd inorder traversal, hoewel het ophalen van de bovenliggende knooppunt vereist worden gereserveerd en postorder traversal langzamer dan een simpele stapeling gebaseerde algoritme.

In het artikel er een pseudocode voor iteratie met O (1) staat, die gemakkelijk kan worden aangepast om een ​​repeater.

antwoordde op 03/01/2011 om 03:09
bron van user

stemmen
30

De meest eenvoudige iterator slaat de laatst gezien toets, en dan op de volgende iteratie, zoekt de boom voor de kleinste bovengrens voor die toets. Iteratie is O (log n). Dit heeft het voordeel dat zeer eenvoudig. Als de toetsen zijn klein, dan is de iterators zijn ook klein. natuurlijk heeft het nadeel dat een relatief langzame manier van iteratie door de boom. Het zal ook niet werken voor niet-unieke sequenties.

Sommige bomen gebruiken precies de uitvoering die u al gebruikt, omdat het belangrijk is voor hun specifieke gebruik dat het scannen is erg snel. Als het aantal toetsen elk knooppunt groot is, dan is de straf slaan sibling wijzers niet te zwaar. De meeste B-Trees gebruiken deze methode.

veel zoekboom implementaties houden een ouder aanwijzer op elk knooppunt naar andere activiteiten te vereenvoudigen. Als je dat hebt, dan kunt u een eenvoudige pointer gebruiken om de laatst gezien knooppunt als gesteldheid van uw iterator. bij elke iteratie, kijk je naar het volgende kind in ouder het laatst gezien node. als er geen broers en zussen meer, dan ga je op één niveau.

Als geen van deze technieken bij u past, kunt u een stapel van knooppunten, opgeslagen in de iterator gebruiken. Dit dient een dezelfde functie als de functie call stack wanneer itereren door de zoekboom als normaal, maar in plaats van het doorlopen van broers en zussen en recursief op kinderen, duw je kinderen op de stapel en terug te keren elke opeenvolgende broer of zus.

antwoordde op 03/01/2011 om 03:25
bron van user

stemmen
3

Ok, ik weet dat dit is oud, maar ik werd gevraagd dit in een interview met Microsoft een tijdje terug en ik besloten om te werken aan het een beetje. Ik heb dit getest en het werkt heel goed.

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

};
antwoordde op 20/10/2012 om 05:53
bron van user

stemmen
15

Zoals TokenMacGuy vermeld kun je een stapel opgeslagen in de iterator gebruiken. Hier is een snelle geteste uitvoering van deze in 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);
    }
}

Andere variatie zou zijn om de boom in bouwtijd doorkruisen en sla de traversal in een lijst. U kunt de lijst iterator achteraf te gebruiken.

antwoordde op 31/07/2013 om 00:21
bron van user

stemmen
0

Hoe zit het met het gebruik van een diepte eerste zoektechniek. De iterator object moet het gewoon een stapel van de reeds bezochte knooppunten.

antwoordde op 21/05/2014 om 22:02
bron van user

stemmen
0

Als je stack te gebruiken, alleen jij bereiken "Extra geheugengebruik O (h), h is de hoogte van de boom". Echter, als je wilt alleen O (1) extra geheugen te gebruiken, moet u de hier te noteren zijn de analyse: - Als de huidige knooppunt heeft recht kind: vind min van rechts sub tree - Het huidige knooppunt heeft geen recht kind, wat je nodig hebt te kijken naar het uit de wortel, en een update van het laagste voorvader, dat is het laagste volgende knoop

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;
           }
       }
antwoordde op 24/04/2015 om 23:41
bron van user

stemmen
0

Gebruik O (1) ruimte, waardoor we O (h) stack gebruiken.

Beginnen:

  1. hasNext ()? current.val <= endNode.val om te controleren of de boom volledig is doorlopen.

  2. Vind min via meest linkse: We kunnen alwasy zoeken naar uiterst links te vinden volgende minimale waarde.

  3. Zodra meest linkse min wordt gecontroleerd (noem maar op current). Volgende min zal zijn 2 gevallen: Als current.right = null, kunnen we blijven zoeken naar meest linkse kind current.right's, want volgende min!. Of moeten we naar achteren te kijken naar de ouders. Gebruik binaire zoekboom de huidige moederbedrijf knooppunt vinden.

Let op : bij het doen van binaire zoektocht naar de ouders, zorg ervoor dat het voldoet aan parent.left = stroom.

Want: Als parent.right == huidige, heeft die ouder must eerder bezocht. In binaire zoekboom, weten we dat parent.val <parent.right.val. We moeten dit speciale geval overslaan, omdat het leidt tot loop ifinite.

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;
    }
}
antwoordde op 27/01/2016 om 16:42
bron van user

stemmen
0

Per definitie is het niet mogelijk next () en hasNext () om te draaien in O (1) tijd. Als u op zoek bent naar een bepaald knooppunt in een BST, je hebt geen idee van de hoogte en de structuur van de andere knooppunten zijn, dus je kunt niet zomaar "jump" om de juiste volgende knooppunt.

Echter, de ruimtecomplexiteit verlaagd tot O (1) (met uitzondering van het geheugen voor het BST zelf). Hier is de manier waarop ik zou het in C te doen:

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

De truc is om zowel een ouder koppeling en een bezochte vlag voor elk knooppunt te hebben. Naar mijn mening kunnen we stellen dat dit geen extra ruimte gebruik, het is gewoon een deel van het knooppunt structuur. En natuurlijk, iter_next () moet worden aangeroepen zonder de toestand van de boomstructuur te veranderen (natuurlijk), maar ook dat de "bezocht" vlaggen niet te wijzigen.

Hier is de tester functie die iter_next () oproepen en drukt de waarde telkens voor deze boom:

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

Waarin de waarden in gesorteerde volgorde afgedrukt:

15 16 20 21 25 27 40 62 71 
antwoordde op 13/02/2016 om 06:56
bron van user

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