La mise en œuvre d'un itérateur sur un arbre de recherche binaire

voix
30

J'ai été le codage un tas de différentes implémentations d'arbre de recherche binaire récemment (AVL, évasement, Treap) et je suis curieux de savoir s'il y a un choix particulièrement « bonne » façon d'écrire un itérateur pour traverser ces structures. La solution que je l'ai utilisé est en ce moment d'avoir chaque noeud dans les pointeurs de magasin de BST aux éléments suivants et précédents dans l'arborescence, ce qui réduit l'itération à une norme itération liste chaînée. Cependant, je ne suis pas vraiment satisfait de cette réponse. Il augmente l'utilisation de l'espace de chaque noeud par deux pointeurs (suivant et précédent), et dans un certain sens, il est tout simplement tricher.

Je connais un moyen de construire un iterator arbre de recherche binaire qui utilise O (h) espace de stockage auxiliaire (où h est la hauteur de l'arbre) à l'aide d'une pile pour garder une trace des nœuds frontières à explorer plus tard, mais je ai résisté à coder cette place en raison de l'utilisation de la mémoire. J'espérais il y a un moyen de construire un itérateur qui utilise uniquement l'espace constant.

Ma question est - est-il possible de concevoir un itérateur sur un arbre de recherche binaire avec les propriétés suivantes?

  1. Les éléments sont visités dans l'ordre croissant (soit un parcours infixe)
  2. next()et hasNext()requêtes exécutées en O (1) heure.
  3. Utilisation de la mémoire est O (1)

Pour le rendre plus facile, il est très bien si l'on suppose que la structure de l'arbre ne change pas de forme lors de l'itération (ie pas des insertions, des suppressions ou des rotations), mais ce serait vraiment cool s'il y avait une solution qui pourrait bien gérer cela.

Créé 03/01/2011 à 02:54
source utilisateur
Dans d'autres langues...                            


8 réponses

voix
1

Arbre traversal , de Wikipedia:

Toutes les implémentations d'échantillons requièrent de l'espace de pile d'appel proportionnelle à la hauteur de l'arbre. Dans un arbre mal équilibré, cela peut être tout à fait considérable.

Nous pouvons supprimer l'exigence de la pile en maintenant les pointeurs de parents dans chaque noeud, ou en enfilant l'arbre. Dans le cas de l'utilisation de threads, cela permettra grandement améliorée afinde traversal, bien récupérer le nœud parent requis pour pré-commande et postorder traversal sera plus lent qu'un algorithme simple basé pile.

Dans l'article il y a une certaine pseudo-code pour l'itération avec O (1) l'état, qui peut être facilement adapté à un itérateur.

Créé 03/01/2011 à 03:09
source utilisateur

voix
30

Les plus simples magasins de iterator possible la dernière clé vu, puis sur la prochaine itération, recherche l'arbre pour le majorant de cette touche. L'itération est O (log n). Cela a l'avantage d'être très simple. Si les touches sont petites alors les itérateurs sont petites. Bien sûr, il a l'inconvénient d'être une façon relativement lente de itérer à travers l'arbre. Il a également ne fonctionnera pas pour les séquences non uniques.

Certains arbres utilisent exactement la mise en œuvre que vous utilisez déjà, car il est important pour leur utilisation spécifique que la numérisation est très rapide. Si le nombre de clés dans chaque nœud est grand, alors la peine de stocker des pointeurs de frères et soeurs ne sont pas trop onéreux. La plupart des arbres B utilisent cette méthode.

De nombreuses implémentations arbre de recherche garder un pointeur parent sur chaque nœud pour simplifier d'autres opérations. Si vous avez cela, vous pouvez utiliser un pointeur simple au dernier noeud considéré comme l'état de votre itérateur. à chaque itération, vous recherchez l'enfant suivant dans le parent du dernier nœud vu. s'il n'y a plus de frères et sœurs, vous montez d'un niveau.

Si aucune de ces techniques ne vous convient, vous pouvez utiliser une pile de nœuds, stockés dans l'itérateur. Cela sert la même fonction que la pile d'appel de fonction lorsque vous parcourez à travers l'arbre de recherche comme d'habitude, mais au lieu de boucle à travers les frères et sœurs récursifs sur les enfants, vous pousser les enfants sur la pile et retourner chaque frère ou soeur successives.

Créé 03/01/2011 à 03:25
source utilisateur

voix
3

Ok, je sais que c'est vieux, mais on m'a demandé dans une interview avec Microsoft un certain temps et j'ai décidé de travailler un peu. Je l'ai testé et il fonctionne très bien.

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

};
Créé 20/10/2012 à 05:53
source utilisateur

voix
15

Comme mentionné TokenMacGuy vous pouvez utiliser une pile stockée dans le iterator. Voici une mise en œuvre rapide de ce test en 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);
    }
}

Autre variation serait de traverser l'arbre au moment de la construction et enregistrer le traversal dans une liste. Vous pouvez utiliser la liste iterator par la suite.

Créé 31/07/2013 à 00:21
source utilisateur

voix
0

Qu'en est-il en utilisant une première technique de recherche en profondeur. L'objet iterator juste doit avoir une pile des noeuds déjà visités.

Créé 21/05/2014 à 22:02
source utilisateur

voix
0

Si vous utilisez la pile, vous obtenez seulement « utilisation de la mémoire supplémentaire O (h), h est la hauteur de l'arbre ». Toutefois, si vous souhaitez utiliser uniquement O (1) de la mémoire supplémentaire, vous devez enregistrer la voici l'analyse: - Si le nœud actuel a droit de l'enfant: trouver min d'arbre à droite sous - Il nœud courant n'a pas d'enfant à droite, vous avez besoin à chercher de la racine, et garder à jour son ancêtre le plus bas, ce qui est son plus bas noeud suivant

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;
           }
       }
Créé 24/04/2015 à 23:41
source utilisateur

voix
0

Utiliser O (1) l'espace, ce qui signifie que nous n'utiliserons O (h) empilement.

Pour commencer:

  1. hasNext ()? current.val <= endNode.val pour vérifier si l'arbre est entièrement parcouru.

  2. Trouvez min par les plus à gauche: Nous pouvons alwasy chercher le plus à gauche pour trouver la prochaine valeur minimale.

  3. Une fois est cochée le plus à gauche min (nommez current). Suivant min sera de 2 cas: Si current.right = null, nous pouvons continuer à chercher le plus à gauche de l' enfant current.right, en suivant min. Ou, nous devons regarder en arrière pour les parents. Utilisez arbre binaire de recherche pour trouver le noeud parent de courant.

Remarque : lorsque vous effectuez une recherche binaire pour les parents, assurez - vous qu'il satisfait parent.left = courant.

Parce que: Si parent.right == actuel, qui doit mère a été visité. Dans l'arbre de recherche binaire, nous savons que parent.val <parent.right.val. Nous devons sauter ce cas particulier, car elle conduit à boucle 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;
    }
}
Créé 27/01/2016 à 16:42
source utilisateur

voix
0

Par définition, il est impossible pour le prochain () et hasNext () pour exécuter en O (1) temps. Lorsque vous êtes à la recherche d'un noeud particulier dans un BST, vous avez aucune idée de la hauteur et de la structure des autres noeuds sont donc vous ne pouvez pas « sauter » au noeud suivant correct.

Cependant, la complexité de l'espace peut être réduite à O (1) (à l'exception de la mémoire pour le BST lui-même). Voici la façon dont je le ferais en 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;
}

L'astuce est d'avoir à la fois un lien parent, et un drapeau visité pour chaque noeud. À mon avis, nous pouvons affirmer que ce n'est pas l'utilisation de l'espace supplémentaire, il est tout simplement partie de la structure du noeud. Et évidemment, iter_next () doit être appelée sans l'état de la structure de l'arbre changeant (bien sûr), mais aussi que les drapeaux « visité » ne changent pas les valeurs.

Voici la fonction testeur iter_next appelle () et imprime la valeur à chaque fois pour cet arbre:

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

Ce qui permet d'imprimer les valeurs de l'ordre de tri:

15 16 20 21 25 27 40 62 71 
Créé 13/02/2016 à 06:56
source utilisateur

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