Η εφαρμογή ενός iterator πάνω από ένα δυαδικό δένδρο αναζήτησης

ψήφοι
30

Έχω κωδικοποίησης μια δέσμη των διαφορετικών εφαρμογών δυαδικό δέντρο αναζήτησης πρόσφατα (AVL, άτεχνος, treap) και είμαι περίεργος αν υπάρχει μια ιδιαίτερα «καλή» τρόπος για να γράψει ένα iterator να διασχίσει αυτές τις δομές. Η λύση που έχω χρησιμοποιήσει αυτή τη στιγμή είναι να έχουμε σε κάθε κόμβο τους δείκτες κατάστημα BST για τα επόμενα και τα προηγούμενα στοιχεία στο δέντρο, το οποίο μειώνει την επανάληψη σε ένα πρότυπο που συνδέονται λίστα επανάληψη. Ωστόσο, δεν είμαι πραγματικά ικανοποιημένος με την απάντηση αυτή. Αυξάνει το χώρο χρήσης του κάθε κόμβου με δύο δείκτες (επόμενο και προηγούμενο), και κατά κάποιο τρόπο είναι απλά εξαπάτηση.

Ξέρω έναν τρόπο οικοδόμησης μιας δυαδικής αναζήτησης δέντρο iterator που χρησιμοποιεί O (h) βοηθητικός χώρος αποθήκευσης (όπου h είναι το ύψος του δέντρου), χρησιμοποιώντας μια στοίβα για να παρακολουθείτε τα σύνορα κόμβων για να εξερευνήσετε αργότερα, αλλά εγώ ve αντιστάθηκαν κωδικοποίησης αυτό επάνω, λόγω της χρήσης της μνήμης. Ήλπιζα ότι υπάρχει κάποιος τρόπος για να οικοδομήσουμε ένα iterator που χρησιμοποιεί μόνο σταθερό χώρο.

Το ερώτημά μου είναι το εξής - είναι ένας τρόπος για να σχεδιάσουμε ένα iterator πάνω από ένα δυαδικό δέντρο αναζήτησης με τις ακόλουθες ιδιότητες εκεί;

  1. Τα στοιχεία επισκέφθηκε με αύξουσα σειρά (δηλαδή inorder διάσχισης)
  2. next()και hasNext()ερωτήματα τρέχει σε O (1) ώρα.
  3. χρήση μνήμης είναι Ο (1)

Για να καταστεί ευκολότερη, είναι μια χαρά, αν υποθέσουμε ότι η δομή του δέντρου δεν αλλάζει σχήμα κατά τη διάρκεια της επανάληψης (δηλαδή χωρίς παρεμβολές, διαγραφές, ή περιστροφές), αλλά θα ήταν πολύ καλό αν υπήρχε μια λύση που θα μπορούσε πραγματικά να χειριστεί αυτό.

Δημοσιεύθηκε 03/01/2011 στις 02:54
πηγή χρήστη
Σε άλλες γλώσσες...                            


8 απαντήσεις

ψήφοι
1

Διάσχιση δέντρου , από τη Wikipedia:

Όλες οι εφαρμογές του δείγματος θα απαιτήσει χώρο στοίβα κλήσεων ανάλογη με το ύψος του δέντρου. Σε μια κακώς ισορροπημένο δέντρο, αυτό μπορεί να είναι αρκετά σημαντική.

Μπορούμε να καταργηθεί η απαίτηση στοίβα με τη διατήρηση της μητρικής δείκτες σε κάθε κόμβο, ή περνώντας το δέντρο. Σε περίπτωση χρήσης θέματα, αυτό θα επιτρέψει βελτιωθεί σημαντικά inorder διάσχισης, αν και την ανάκτηση του γονέα κόμβο που απαιτείται για προ-παραγγελία και postorder διάσχιση θα είναι πιο αργή από ό, τι έναν αλγόριθμο απλή στοίβα βάση.

Στο άρθρο υπάρχει κάποια ψευδοκώδικα για την επανάληψη με O (1) κατάσταση, η οποία μπορεί εύκολα να προσαρμοστεί σε ένα επαναλήπτη.

Απαντήθηκε 03/01/2011 στις 03:09
πηγή χρήστη

ψήφοι
30

Η απλούστερη καταστήματα δυνατόν iterator η τελευταία θεωρείται κλειδί, και στη συνέχεια, στην επόμενη επανάληψη, αναζητά το δέντρο για τις λιγότερο ανώτερο όριο για αυτό το κλειδί. Επανάληψη είναι Ο (log n). Αυτό έχει το πλεονέκτημα ότι είναι πολύ απλό. Αν τα πλήκτρα είναι μικρά, στη συνέχεια, οι επαναλήπτες είναι επίσης μικρή. Φυσικά αυτό έχει το μειονέκτημα ότι είναι μια σχετικά αργή τρόπος για την επανάληψη μέσα από το δέντρο. Επίσης, δεν θα λειτουργήσει για μη μοναδικές ακολουθίες.

Μερικά δέντρα χρησιμοποιούν ακριβώς την εφαρμογή που χρησιμοποιείτε ήδη, γιατί είναι σημαντικό για συγκεκριμένη χρήση τους, ότι η σάρωση είναι πολύ γρήγορη. Εάν ο αριθμός των πλήκτρων σε κάθε κόμβο είναι μεγάλος, τότε η ποινή της αποθήκευσης αδελφικών δείκτες δεν είναι πολύ δαπανηρή. Τα περισσότερα Β-Δέντρα χρησιμοποιούν αυτή τη μέθοδο.

πολλές εφαρμογές δέντρο αναζήτησης διατηρούν ένα γονέα δείκτη σε κάθε κόμβο για την απλούστευση άλλες λειτουργίες. Εάν έχετε αυτό, τότε μπορείτε να χρησιμοποιήσετε ένα απλό δείκτη για την τελευταία φορά κόμβο ως κρατική επαναλήπτη σας. σε κάθε επανάληψη, μπορείτε να αναζητήσετε το επόμενο παιδί στο γονέα του για τελευταία φορά κόμβου. αν δεν υπάρχουν άλλα αδέλφια, τότε θα ανεβείτε ένα ακόμα επίπεδο.

Εάν καμία από αυτές τις τεχνικές σας ταιριάζει, μπορείτε να χρησιμοποιήσετε μια στοίβα των κόμβων, αποθηκεύονται στο iterator. Αυτό εξυπηρετεί την ίδια λειτουργία με τη στοίβα κλήση της συνάρτησης όταν επανάληψη μέσα στο δέντρο αναζήτησης ως συνήθως, αλλά αντί looping μέσα από τα αδέλφια και την αναδρομική για τα παιδιά, που ωθεί τα παιδιά στη στοίβα και να επιστρέψει κάθε διαδοχική αδελφό.

Απαντήθηκε 03/01/2011 στις 03:25
πηγή χρήστη

ψήφοι
3

Εντάξει, ξέρω ότι αυτό είναι παλιό, αλλά μου ζητήθηκε αυτό σε μια συνέντευξη με τη Microsoft λίγο πίσω και αποφάσισα να εργάζονται σε αυτό το κομμάτι. Δοκίμασα αυτό και λειτουργεί αρκετά καλά.

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

};
Απαντήθηκε 20/10/2012 στις 05:53
πηγή χρήστη

ψήφοι
15

Όπως αναφέρθηκε TokenMacGuy μπορείτε να χρησιμοποιήσετε μια στοίβα αποθηκεύονται στο iterator. Εδώ είναι μια γρήγορη δοκιμαστεί εφαρμογή αυτού σε 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);
    }
}

Άλλες παραλλαγή θα ήταν να διασχίσει το δέντρο σε χρόνο κατασκευής και να σώσει τη διάσχιση σε μια λίστα. Μπορείτε να χρησιμοποιήσετε τη λίστα iterator στη συνέχεια.

Απαντήθηκε 31/07/2013 στις 00:21
πηγή χρήστη

ψήφοι
0

Τι γίνεται με τη χρήση ενός βάθους πρώτη τεχνική αναζήτησης. Το αντικείμενο iterator απλά πρέπει να έχει μια στοίβα των ήδη επισκεφθεί κόμβους.

Απαντήθηκε 21/05/2014 στις 22:02
πηγή χρήστη

ψήφοι
0

Εάν χρησιμοποιείτε στοίβα, το μόνο που επιτυγχάνουν «Extra χρήση μνήμης O (h), h είναι το ύψος του δέντρου». Ωστόσο, εάν θέλετε να χρησιμοποιήσετε μόνο O (1) επιπλέον μνήμη, θα πρέπει να καταγράψει το Εδώ είναι η ανάλυση: - Εάν το τρέχον κόμβος έχει το δικαίωμα του παιδιού: βρείτε λεπτά του δικαιώματος υπο δέντρου - Το τρέχον κόμβος δεν έχει το δικαίωμα του παιδιού, θα πρέπει να έχετε να ψάξουν για αυτό από τη ρίζα, και να κρατήσει την ενημέρωση είναι χαμηλότερη πρόγονο, το οποίο είναι χαμηλότερο επόμενο κόμβο της

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;
           }
       }
Απαντήθηκε 24/04/2015 στις 23:41
πηγή χρήστη

ψήφοι
0

Χρησιμοποιήστε O (1) χώρο, η οποία σημαίνει ότι δεν θα χρησιμοποιήσει O (h) στοίβα.

Να ξεκινήσω:

  1. hasNext (); current.val <= endNode.val για να ελέγξετε αν το δέντρο είναι πλήρως διασχίζεται.

  2. Βρείτε λεπτό με αριστερό περισσότερα: Μπορούμε να alwasy αναζητήσουμε πιο αριστερά για να βρείτε την επόμενη ελάχιστη τιμή.

  3. Εφόσον αφεθεί-πιο min ελέγχεται (όνομα αυτό current). Επόμενο λεπτά θα είναι 2 περιπτώσεις: Αν current.right = null, μπορούμε να συνεχίσετε να ψάχνετε για το πιο αριστερό παιδί current.right, καθώς το επόμενο λεπτό!. Ή, θα πρέπει να δούμε προς τα πίσω για τη μητρική. Χρησιμοποιήστε δυαδικό δέντρο αναζήτησης για να βρείτε μητρικό κόμβο σημερινή του.

Σημείωση : όταν κάνει δυαδική αναζήτηση για γονέα, βεβαιωθείτε ότι πληροί parent.left = ρεύμα.

Επειδή: Αν parent.right == ρεύμα, ο γονέας πρέπει να έχει επισκεφτεί στο παρελθόν. Στο δυαδικό δένδρο αναζήτησης, γνωρίζουμε ότι parent.val <parent.right.val. Πρέπει να παραλείψετε αυτή την ειδική περίπτωση, δεδομένου ότι οδηγεί σε 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;
    }
}
Απαντήθηκε 27/01/2016 στις 16:42
πηγή χρήστη

ψήφοι
0

Εξ ορισμού, δεν είναι δυνατόν για την επόμενη () και hasNext () για να τρέξει σε χρόνο O (1) χρόνο. Όταν ψάχνετε σε ένα συγκεκριμένο κόμβο σε ένα BST, δεν έχετε ιδέα το ύψος και την δομή των άλλων κόμβων είναι, ως εκ τούτου, δεν μπορείτε απλά «άλμα» προς τη σωστή επόμενο κόμβο.

Ωστόσο, ο χώρος πολυπλοκότητα μπορεί να μειωθεί σε O (1) (εκτός από την μνήμη για την ίδια την BST). Εδώ είναι ο τρόπος που θα το κάνετε σε 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;
}

Το κόλπο είναι να έχουμε τόσο η μητρική σύνδεσμο, και επισκέφθηκε τη σημαία για κάθε κόμβο. Κατά τη γνώμη μου, μπορούμε να υποστηρίζουν ότι αυτό δεν είναι πρόσθετη χρήση του χώρου, είναι απλά μέρος της δομής του κόμβου. Και προφανώς, iter_next () πρέπει να κληθεί χωρίς την κρατική της δομή δέντρου αλλαγή (φυσικά), αλλά και ότι η «επισκέφθηκε» σημαίες δεν αλλάζουν τις τιμές.

Εδώ είναι η λειτουργία δοκιμής που iter_next () καλεί και εκτυπώνει την τιμή κάθε φορά για αυτό το δέντρο:

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

Ποια θα εκτυπώσει τις τιμές σε ταξινομημένη σειρά:

15 16 20 21 25 27 40 62 71 
Απαντήθηκε 13/02/2016 στις 06:56
πηγή χρήστη

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