La implementación de un iterador sobre un árbol de búsqueda binaria

votos
30

He estado codificar hasta un montón de diferentes implementaciones de búsqueda binaria de árboles recientemente (AVL, ensanchamiento, Treap) y estoy ansioso por ver si hay una manera particular buena para escribir un iterador para recorrer estas estructuras. La solución que he usado en este momento es que cada nodo en los almacenan punteros BST a los elementos anterior y siguiente en el árbol, lo que reduce la iteración a un estándar-lista enlazada iteración. Sin embargo, no estoy realmente satisfecho con esta respuesta. Aumenta el uso de espacio de cada nodo por dos punteros (anterior y siguiente), y en cierto sentido es sólo hacer trampa.

Yo sé de una manera de construir un iterador árbol binario de búsqueda que utiliza O (h) el espacio de almacenamiento auxiliar (donde h es la altura del árbol) mediante el uso de una pila para realizar un seguimiento de los nodos frontera para explorar más adelante, pero yo he resistido a la codificación de esto, debido a la utilización de la memoria. Tenía la esperanza de que hay alguna manera de construir un iterador que utiliza el espacio único constante.

Mi pregunta es la siguiente - ¿hay alguna manera de diseñar un iterador sobre un árbol binario de búsqueda con las siguientes propiedades?

  1. Los elementos se visitaron en orden ascendente (es decir, un recorrido en orden)
  2. next()y hasNext()consultas se ejecutan en O (1) tiempo.
  3. El uso de memoria es O (1)

Para hacerlo más fácil, está bien si se asume que la estructura de árbol no está cambiando de forma durante la iteración (es decir, sin inserciones, deleciones o rotaciones), pero sería genial si había una solución que, efectivamente, podría manejar esto.

Publicado el 03/01/2011 a las 02:54
fuente por usuario
En otros idiomas...                            


8 respuestas

votos
1

El recorrido del árbol , de Wikipedia:

Todas las implementaciones de muestra requerirán espacio de pila de llamadas proporcional a la altura del árbol. En un árbol mal balanceada, esto puede ser bastante considerable.

Podemos eliminar el requisito de la pila mediante el mantenimiento de los punteros de padres en cada nodo, o enroscando el árbol. En el caso de usar hilos, esto permitirá mejorado en gran medida a finde traversal, aunque recuperar el nodo padre requerido para preorden y postorden traversal será más lento que un algoritmo basado pila simple.

En el artículo hay algo de pseudocódigo para la iteración con O (1) de estado, que se puede adaptar fácilmente a un iterador.

Respondida el 03/01/2011 a las 03:09
fuente por usuario

votos
30

Los más simples posibles tiendas iterador la última tecla visto, y luego en la siguiente iteración, busca en el árbol para el extremo superior de esa tecla. Iteración es O (log n). Esto tiene la ventaja de ser muy simple. Si las teclas son pequeñas, los iteradores son también pequeños. por supuesto, tiene la desventaja de ser una forma relativamente lenta de la iteración a través del árbol. También no funcionará para las secuencias no únicos.

Algunos árboles usan exactamente la aplicación que ya utiliza, porque es importante para su uso específico que la exploración es muy rápido. Si el número de claves en cada nodo es grande, entonces la pena de almacenar punteros de hermanos no es demasiado oneroso. La mayoría de los árboles B utilizan este método.

muchas implementaciones árbol de búsqueda mantienen un puntero de los padres en cada nodo para simplificar otras operaciones. Si usted tiene que, a continuación, se puede utilizar un simple puntero al último nodo visto como el estado de su iterador. en cada iteración, se mira para el siguiente niño en el padre del último nodo visto. si no hay más hermanos, a continuación, se sube un nivel más.

Si ninguna de estas técnicas que conviene, se puede utilizar una pila de nodos, almacenados en el repetidor. Esto sirve la misma función que la pila de llamadas función cuando la iteración a través del árbol de búsqueda de forma normal, pero en lugar de bucle a través de los hermanos y de manera recursiva en los niños, que empujan a los niños en la pila y volver cada hermano sucesiva.

Respondida el 03/01/2011 a las 03:25
fuente por usuario

votos
3

Ok, sé que esto es viejo, pero me preguntaron en una entrevista con Microsoft hace un tiempo y decidí trabajar en él un poco. He probado esto y funciona bastante 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;
    }

};
Respondida el 20/10/2012 a las 05:53
fuente por usuario

votos
15

Como se ha mencionado TokenMacGuy puede utilizar una pila almacenada en el repetidor. He aquí una rápida implementación de esta prueba 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);
    }
}

Otra variación sería la de recorrer el árbol en el tiempo de construcción y guardar el recorrido en una lista. Se puede utilizar el iterador de la lista después.

Respondida el 31/07/2013 a las 00:21
fuente por usuario

votos
0

¿Qué pasa con el uso de una primera técnica de búsqueda en profundidad. El objeto iterador simplemente debe tener una pila de los nodos ya visitados.

Respondida el 21/05/2014 a las 22:02
fuente por usuario

votos
0

Si utiliza la pila, es lograr "el uso de memoria adicional O (h), h es la altura del árbol". Sin embargo, si desea utilizar solamente O (1) de memoria adicional, es necesario registrar el Éstos son el análisis: - Si el nodo actual tiene hijo derecho: encontrar min de la sub árbol de la derecha - Se nodo actual no tiene hijo derecho, es necesario para buscar desde la raíz, y mantener la actualización de su ancestro más bajo, que es su nivel más bajo siguiente nodo

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;
           }
       }
Respondida el 24/04/2015 a las 23:41
fuente por usuario

votos
0

Utilice O (1) el espacio, lo que significa que no vamos a utilizar O (h) de la pila.

Empezar:

  1. hasNext ()? current.val <= endNode.val para comprobar si el árbol está totalmente atravesada.

  2. Encuentra min a través de más a la izquierda: Podemos mirar alwasy de valor mínimo al lado izquierdo de encontrar.

  3. Una vez más a la izquierda está marcada min (nombrarlo current). Siguiente min será de 2 casos: Si current.right = null, podemos seguir buscando más a la izquierda del niño current.right, como el próximo min!. O bien, tenemos que mirar hacia atrás para padres. Utilice árbol binario de búsqueda para encontrar nodo padre del actual.

Nota : cuando se hace una búsqueda binaria para los padres, asegúrese de que satisface parent.left = corriente.

Debido: Si parent.right == actual, que debe padre ha sido visitado antes. En árbol binario de búsqueda, sabemos que parent.val <parent.right.val. Tenemos que saltar este caso especial, ya que conduce a ifinite bucle.

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;
    }
}
Respondida el 27/01/2016 a las 13:42
fuente por usuario

votos
0

Por definición, no es posible para el próximo () y hasNext () para ejecutar en O (1) tiempo. Cuando usted está buscando en un nodo particular en un BST, no tiene idea de la altura y la estructura de los otros nodos son, por lo tanto, no se puede simplemente "salto" al siguiente nodo correcto.

Sin embargo, la complejidad de espacio se puede reducir a O (1) (a excepción de la memoria para el BST sí mismo). Esta es la forma en que lo haría 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;
}

El truco es tener tanto un vínculo padre, y una bandera para cada nodo visitado. En mi opinión, se puede argumentar que este no es el uso de espacio adicional, es simplemente parte de la estructura de nodos. Y, obviamente, iter_next () debe ser llamado sin el estado de la estructura de árbol cambiando (por supuesto), sino también que el "visitada" banderas no cambian los valores.

Esta es la función de probador que iter_next () llama e imprime el valor de cada hora de este árbol:

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

Que imprimirá los valores en el orden establecido:

15 16 20 21 25 27 40 62 71 
Respondida el 13/02/2016 a las 03:56
fuente por usuario

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