Implementazione di un iteratore su un albero binario di ricerca

voti
30

Ho codifica un mazzo di diverse implementazioni ricerca binaria albero di recente (AVL, splay, treap) e sono curioso di sapere se c'è un modo particolarmente buona di scrivere un iteratore per attraversare queste strutture. La soluzione che ho usato in questo momento è avere ogni nodo i puntatori negozio BST agli elementi precedente e successivo nella struttura, che riduce iterazione ad uno standard lista concatenata iterazione. Tuttavia, io non sono davvero soddisfatto di questa risposta. Aumenta l'utilizzo dello spazio di ciascun nodo da due puntatori (Avanti e Indietro), e in un certo senso è solo barare.

So di un modo di costruire un albero di iteratore binario di ricerca che utilizza O (h) lo spazio di archiviazione ausiliario (dove h è l'altezza dell'albero) utilizzando uno stack per tenere traccia dei nodi di frontiera da esplorare in seguito, ma io' ve resistito codifica questo a causa del l'utilizzo della memoria. Speravo c'è qualche modo per costruire un iteratore che utilizza lo spazio unica costante.

La mia domanda è questa - c'è un modo per progettare un iteratore su un albero binario di ricerca con le seguenti proprietà?

  1. Gli elementi sono visitati in ordine ascendente (cioè un attraversamento in ordine simmetrico)
  2. next()e hasNext()query eseguite nel tempo O (1) tempo.
  3. L'utilizzo della memoria è O (1)

Per rendere più facile, va bene se si assume che la struttura ad albero non cambia forma durante l'iterazione (cioè senza inserzioni, delezioni, o rotazioni), ma sarebbe davvero bello se ci fosse una soluzione che potrebbe effettivamente gestire questa situazione.

È pubblicato 03/01/2011 alle 02:54
fonte dall'utente
In altre lingue...                            


8 risposte

voti
1

Attraversamento Albero , da Wikipedia:

Tutte le implementazioni di esempio richiederanno chiamata spazio di stack proporzionale all'altezza dell'albero. In un albero poco equilibrata, questo può essere abbastanza considerevole.

Siamo in grado di eliminare l'obbligo di stack, mantenendo puntatori madri in ogni nodo, o infilando l'albero. Nel caso di utilizzo di fili, ciò permetterà notevolmente migliorata ordine simmetrico, anche se recuperato il nodo genitore richiesto per anticipato e posticipato attraversamento sarà più lento di un semplice algoritmo basato pila.

Nell'articolo c'è qualche pseudocodice per l'iterazione con O (1) dello stato, che può essere facilmente adattato a un iteratore.

Risposto il 03/01/2011 a 03:09
fonte dall'utente

voti
30

I più semplici negozi possibile iteratore l'ultimo tasto visto, e poi sulla prossima iterazione, cerca l'albero per l'estremo superiore per quella chiave. Iterazione è O (log n). Questo ha il vantaggio di essere molto semplice. Se i tasti sono piccoli quindi le iteratori sono anche piccole. naturalmente ha lo svantaggio di essere un modo relativamente lento iterazione attraverso l'albero. Inoltre, non funzionerà per le sequenze non univoci.

Alcuni alberi usano esattamente l'attuazione si utilizza già, perché è importante per il loro uso specifico che la scansione è molto veloce. Se il numero di chiavi in ​​ciascun nodo è grande, allora la penalità di memorizzare i puntatori di pari livello non è troppo onerosa. La maggior parte dei B-Alberi utilizzano questo metodo.

molte implementazioni di ricerca albero mantengono un puntatore genitore in ogni nodo per semplificare le altre operazioni. Se si dispone che, quindi è possibile utilizzare un semplice puntatore per l'ultimo nodo visto come lo stato del vostro iteratore. ad ogni iterazione, si guarda per il prossimo bambino nel genitore l'ultimo nodo visto. se non ci sono più fratelli, poi si sale di un livello di più.

Se nessuna di queste tecniche si adattano, è possibile utilizzare una pila di nodi, memorizzato nella iteratore. Questo serve alla stessa funzione lo stack di chiamate funzione quando l'iterazione attraverso l'albero di ricerca come normale, ma invece di loop attraverso i fratelli e recursing sui bambini, è spingere i bambini nello stack e restituire ogni fratello successivo.

Risposto il 03/01/2011 a 03:25
fonte dall'utente

voti
3

Ok, so che questo è vecchio, ma mi è stato chiesto in un'intervista con Microsoft un po 'indietro e ho deciso di lavorare su di esso un po'. Ho testato questa e funziona abbastanza bene.

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

};
Risposto il 20/10/2012 a 05:53
fonte dall'utente

voti
15

Come accennato TokenMacGuy è possibile utilizzare una pila memorizzato nel iteratore. Ecco una rapida attuazione testato di questo 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);
    }
}

Altra variante potrebbe essere quella di attraversare l'albero in fase di costruzione e salvare l'attraversamento in una lista. È possibile utilizzare l'elenco iteratore dopo.

Risposto il 31/07/2013 a 00:21
fonte dall'utente

voti
0

Cosa succede ad usare una prima tecnica di ricerca di profondità. L'oggetto iteratore solo deve avere una pila di nodi già visitati.

Risposto il 21/05/2014 a 22:02
fonte dall'utente

voti
0

Se si utilizza pila, è solo raggiungere "l'utilizzo della memoria Extra O (h), h è l'altezza dell'albero". Tuttavia, se si desidera utilizzare solo O (1) memoria aggiuntiva, è necessario registrare il Ecco l'analisi: - Se il nodo corrente ha figlio destro: trovare min di albero sotto a destra - E 'nodo corrente ha nessun figlio giusto, è necessario a cercarlo dalla radice, e mantenere l'aggiornamento è antenato più basso, che è il suo nodo più basso successivo

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;
           }
       }
Risposto il 24/04/2015 a 23:41
fonte dall'utente

voti
0

Utilizzare O (1) spazio, il che significa che non useremo O (h) stack.

Iniziare:

  1. hasNext ()? current.val <= endNode.val per verificare se l'albero è completamente attraversato.

  2. Trova min via più a sinistra: Possiamo alwasy cercare per il prossimo valore minimo più a sinistra da trovare.

  3. Una volta più a sinistra min è selezionata (chiamarlo current). Successivo min sarà di 2 casi: se current.right = null, siamo in grado di continuare a cercare più a sinistra il figlio di current.right, dal prossimo min!. Oppure, abbiamo bisogno di guardare indietro per genitore. Utilizzare albero binario di ricerca per trovare nodo padre di corrente.

Nota : quando si fa ricerca binaria per il genitore, assicurarsi che soddisfi parent.left = corrente.

Perché: Se la corrente parent.right ==, che deve genitore è stato visitato prima. In albero binario di ricerca, sappiamo che parent.val <parent.right.val. Abbiamo bisogno di saltare questo caso particolare, in quanto porta a ifinite loop.

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;
    }
}
Risposto il 27/01/2016 a 16:42
fonte dall'utente

voti
0

Per definizione, non è possibile per il prossimo () e hasNext () per l'esecuzione in O (1) tempo. Quando si è alla ricerca di un particolare nodo in un BST, non avete idea di l'altezza e la struttura degli altri nodi sono, pertanto, è possibile non solo "salto" per il prossimo nodo corretto.

Tuttavia, la complessità spazio può essere ridotto a O (1) (eccetto per la memoria per la BST stesso). Ecco il modo in cui lo farei in 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;
}

Il trucco è quello di avere sia un legame genitore, e una bandiera visitato per ogni nodo. A mio parere, si può sostenere che questo non è l'utilizzo dello spazio aggiuntivo, è semplicemente parte della struttura del nodo. E ovviamente, iter_next () deve essere chiamato senza lo stato della struttura ad albero che cambia (ovviamente), ma anche che il "visitato" le bandiere non cambiano i valori.

Ecco la funzione di tester che iter_next () chiama e stampa il valore ogni volta per questo albero:

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

Che stamperà i valori in modo ordinato:

15 16 20 21 25 27 40 62 71 
Risposto il 13/02/2016 a 06:56
fonte dall'utente

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