Implementierung einen Iterator über einen binären Suchbaum

stimmen
30

Ich habe vor kurzem eine Reihe von verschiedenen binären Suchbaum Implementierungen Codierung bis (AVL, spreizen, Treap) und bin neugierig, ob es eine besonders „gute“ Weg, einen Iterator zu schreiben, diese Strukturen zu durchqueren. Die Lösung, die ich jetzt verwendet habe, ist jeder Knoten in den BST speichern Zeiger auf die nächsten und vorherigen Elemente in dem Baum zu haben, die Iteration zu einer Standard-linked-Liste Iteration reduziert. Allerdings bin ich nicht wirklich mit dieser Antwort zufrieden. Es erhöht die Raumnutzung jedes Knotens durch zwei Zeiger (nächste und vorherige), und in gewissem Sinne betrügen es ist einfach.

Ich kenne einen Weg, um einen binären Suchbaum Iterator zu bauen, die O (h) Hilfsspeicherraum verwendet (wobei h die Höhe des Baumes ist) durch einen Stapel unter Verwendung Spur des Grenzknoten zu halten später zu erforschen, aber ich ve widerstanden Codierung dies wegen der Speichernutzung auf. Ich habe gehofft, es gibt einen Weg einen Iterator zu bauen, die nur konstanten Raum verwendet.

Meine Frage ist - ist es eine Möglichkeit, einen Iterator über einen binären Suchbaum mit den folgenden Eigenschaften zu entwerfen?

  1. Die Elemente werden in aufsteigender Reihenfolge besucht (dh eine Inorder Traversal)
  2. next()und hasNext()Abfragen ausgeführt in O (1) Zeit.
  3. Speichernutzung ist O (1)

Um es einfacher zu machen, dann ist es in Ordnung, wenn Sie davon ausgehen, dass die Baumstruktur nicht ihre Form verändern während der Iteration (dh keine Insertionen, Deletionen oder Drehungen), aber es wäre wirklich cool, wenn es eine Lösung, die dies in der Tat umgehen konnte.

Veröffentlicht am 03/01/2011 um 02:54
quelle vom benutzer
In anderen Sprachen...                            


8 antworten

stimmen
1

Baum - Traversal , aus Wikipedia:

Alle Beispielimplementierungen werden Call-Stack Platzbedarf proportional zur Höhe des Baumes. In einem schlecht ausgeglichenen Baum kann dies ganz erheblich sein.

Wir können durch Aufrechterhalten Abstammungszeiger in jedem Knoten oder durch Einfädeln des Baumes um den Stapel Anforderung entfernen. Im Fall von Fäden verwendet wird, wird dieser Inorder traversal für stark verbessert ermöglichen, auch wenn die Eltern-Knoten für preorder und Nachordnungsdurchquerung erforderlich Abrufen wird als ein einfacher Stapel basierten Algorithmus langsamer sein.

In dem Artikel gibt es einige Pseudocode für Iteration mit O (1) Zustand, der leicht an einem Iterator angepasst werden kann.

Beantwortet am 03/01/2011 um 03:09
quelle vom benutzer

stimmen
30

Die einfachsten möglich Iterator speichert die zuletzt Schlüssel, und dann auf der nächsten Iteration, suchen den Baum für die obere Grenze für diesen Schlüssel. Iteration ist O (log n). Dies hat den Vorteil, sehr einfach zu sein. Wenn Tasten klein sind, dann sind die Iteratoren auch klein. natürlich hat es den Nachteil, dass ein relativ langsamer Weg, um durch den Baum von iterieren. Es wird auch nicht für nicht-einzigartige Sequenzen arbeiten.

Einige Bäume verwenden, um genau die Implementierung Sie bereits verwenden, weil sie für ihre spezifische Anwendung wichtig ist, dass das Scannen sehr schnell ist. Wenn die Anzahl der Schlüssel in jedem Knoten groß ist, dann ist die Strafe von Geschwisterzeiger zu speichern ist nicht zu beschwerlich. Die meisten B-Trees verwenden diese Methode.

viele Suchbaum Implementierungen halten einen Abstammungszeiger auf jedem Knoten andere Operationen zu vereinfachen. Falls Sie Fragen haben, dann können Sie einen einfachen Zeiger auf den zuletzt Knoten als Iterators Zustand verwenden. bei jeder Iteration, suchen Sie das nächste Kind in den letzten Eltern gesehen Knoten. wenn es keine weiteren Geschwister sind, dann gehen Sie eine weitere Ebene.

Wenn keine dieser Techniken zu Ihnen passen, können Sie einen Stapel von Knoten verwenden, im Iterator gespeichert. Dies dient dazu, eine die gleiche Funktion wie der Stapelfunktionsaufruf, wenn sie durch den Suchbaum als normal laufen, sondern durch Geschwister von Schleifen und Rekursion auf Kinder, Sie schieben die Kinder auf den Stapel und zurück jeden aufeinanderfolgenden Geschwister.

Beantwortet am 03/01/2011 um 03:25
quelle vom benutzer

stimmen
3

Ok, ich weiß, das ist alt, aber ich war eine Weile zurück, dies in einem Interview mit Microsoft gebeten, und ich beschloß, auf es ein bisschen zu arbeiten. Ich habe dies getestet und es funktioniert ganz gut.

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

};
Beantwortet am 20/10/2012 um 05:53
quelle vom benutzer

stimmen
15

Wie TokenMacGuy erwähnt können Sie einen Stapel im Iterator gespeichert sind. Hier ist eine schnelle getestet Umsetzung dieser 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 Variante wäre, den Baum an Bauzeit zu durchqueren und die Traversal in einer Liste speichern. Sie können danach die Liste Iterator verwenden.

Beantwortet am 31/07/2013 um 00:21
quelle vom benutzer

stimmen
0

Was ist eine Tiefensuche-Technik. Das Iterator-Objekt hat nur muss einen Stapel der bereits besuchten Knoten.

Beantwortet am 21/05/2014 um 22:02
quelle vom benutzer

stimmen
0

Wenn Sie Stack verwenden, erreichen Sie nur „Extra Speichernutzung O (h), h die Höhe des Baumes ist“. Wenn Sie jedoch verwenden möchten, nur O (1) zusätzlichen Speicher, müssen Sie das Hier sind die Analyse erfassen: - Wenn die aktuellen Knoten rechts Kind hat: Finden Sie min rechten Unterbaum - Es aktuelle Knoten kein Recht Kind hat, was Sie brauchen denn sie von der Wurzel zu suchen, und hält es auf dem tiefstenen Vorfahren Aktualisierung, die der niedrigsten nächster Knoten ist

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;
           }
       }
Beantwortet am 24/04/2015 um 23:41
quelle vom benutzer

stimmen
0

Verwenden O (1) Platz, was bedeutet, dass wir nicht O (h) Stapel verwendet werden.

Beginnen:

  1. (HasNext)? current.val <= endNode.val überprüfen, ob der Baum vollständig durchlaufen wird.

  2. Finden min über am weitesten links: Wir alwasy für am weitesten links aussehen kann neben Minimalwert zu finden.

  3. Sobald am weitesten links min geprüft (name it current). Weiter min wird 2 Fälle: Wenn current.right = null, können wir halten für current.right des am weitesten links stehenden Kind, als nächster min suchen!. Oder brauchen wir für Eltern suchen nach hinten. Verwenden Baum binären Suchstrom übergeordnete Knoten zu finden.

Hinweis : Wenn für Eltern binäre Suche zu tun, stellen Sie sicher , dass es parent.left = Strom erfüllt.

Denn: Wenn parent.right == Strom, dass Elternteil muss zuvor besucht wurde. In binärer Suchbaum, wissen wir, dass parent.val <parent.right.val. Wir brauchen diesen speziellen Fall überspringen, da es Schleife ifinite führt.

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;
    }
}
Beantwortet am 27/01/2016 um 16:42
quelle vom benutzer

stimmen
0

Per Definition ist es nicht möglich, für die nächste () und hasNext () in O (1) Zeit zu laufen. Wenn Sie an einem bestimmten Knoten in einem BST suchen, haben Sie keine Ahnung, die Höhe und die Struktur der anderen Knoten sind, Sie können also nicht nur „Sprung“ auf den richtigen nächsten Knoten.

Jedoch kann die Platzkomplexität auf O reduziert werden (1) (mit Ausnahme des Speichers für den BST selbst). Hier ist die Art, wie ich es in C tun würde:

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

Der Trick besteht darin, sowohl eine übergeordnete Verbindung zu haben, und ein besuchtes Flag für jeden Knoten. Meiner Meinung nach können wir argumentieren, dass diese Raumnutzung nicht zusätzlich ist, es ist einfach ein Teil der Knotenstruktur ist. Und natürlich iter_next () muss ohne den Zustand der Baumstruktur aufgerufen werden Ändern (natürlich), sondern auch, dass die „besucht“ Flags keine Werte ändern.

Hier ist die Tester-Funktion, ruft iter_next () und gibt den Wert jedes Mal für diesen Baum:

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

Welche werden die Werte in sortierter Reihenfolge drucken:

15 16 20 21 25 27 40 62 71 
Beantwortet am 13/02/2016 um 06:56
quelle vom benutzer

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