Βρίσκοντας το κοινό πρόγονο σε ένα δυαδικό δέντρο

ψήφοι
7

Αυτή η ερώτηση τέθηκε σε μένα σε μια συνέντευξη: Έχω ένα δυαδικό δένδρο και πρέπει να βρούμε τον κοινό πρόγονο (μητρική) δίνονται δύο τυχαία κόμβους του δέντρου. Είμαι, επίσης, δίνεται ένα δείκτη στον κόμβο ρίζα.


Η απάντησή μου είναι:

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

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

Δημοσιεύθηκε 30/05/2011 στις 11:18
πηγή χρήστη
Σε άλλες γλώσσες...                            


10 απαντήσεις

ψήφοι
2

Κάντε μια διάσχιση τάξης επίπεδο, καθώς και για κάθε κόμβο που συναντάμε ελέγχουμε τα παιδιά της. Αν είναι οι παρεχόμενες τυχαία κόμβους, τότε ο κόμβος πρόγονος βρίσκεται.

Edit1:

Εδώ είναι μια περίληψη

struct _node {
   my_type data;
   struct _node *left;
   struct _node *right;
}

q = queue_create ();
queue_insert (q, head);
temp = head;
while (!empty (q))
{
    temp = queue_remove (q);
 if (
      (temp->left == my_random_node_1) && (head->right == my_random_node_2) ||
      (temp->left == my_random_node_2) && (head->right == my_random_node_1)
    )
    {
       /* temp is the common parent of the two target notes */
       /* Do stuffs you need to do */
    }

    /* Enqueue the childs, so that in successive iterations we can
     * check them, by taking out from the queue
     */
    push (q, temp->left);
    push (q, temp->right);
}

ΕΚΣΥΓΧΡΟΝΙΖΩ

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

Ο παρακάτω αλγόριθμος θα βρει κοινούς προγόνους και όχι μόνο τους γονείς.

Νομίζω ότι η παρακάτω αλγόριθμος θα λειτουργήσει:

Κάντε μια postorder διάσχιση του δυαδικού δέντρου, και να βρει την τύχη του κόμβου 1 r1, αν το βρείτε στη συνέχεια να το επισημάνετε σε μια κατάσταση μεταβλητή να είναι σε ένα κράτος , και βρίσκουν πάντα για το δεύτερο κόμβο, αν βρεθεί στη συνέχεια να ενημερώσετε την κατάσταση μεταβλητή κατάσταση δύο , και να σταματήσει την αναζήτηση περισσότερο και να επιστρέψει. Η μεταβλητή κατάστασης θα πρέπει να περάσει από κάθε κόμβο προς τους γονείς του (αναδρομικά). Ο πρώτος κόμβος που συναντά την μεταβλητή κατάστασης στην κατάσταση δύο είναι η κοινό πρόγονο.

Η υλοποίηση του αλγορίθμου είναι η εξής:

int postorder (node *p, int r1, int r2)
{
  int x = 0; /* The state variable */
  if (p->data == TERMINAL_VAL)
    return x;

  /* 0x01 | 0x02 = 0x03 threfore 
   * state one is when x = 0x01 or x = 0x02
   * state two is when x = 0x03
   */
  if (p->data == r1)
    x |= 0x01;
  else if (p->data == r2)
    x |= 0x02;

  /* if we have x in state two, no need to search more
   */
  if (x != 0x03)
    x |= postorder (p->left, r1, r2);
  if (x != 0x03)
    x |= postorder (p->right, r1, r2);

  /* In this node we are in state two, print node if this node
   * is not any of the two nodes r1 and r2. This makes sure that
   * is one random node is an ancestor of another random node
   * then it will not be printed instead its parent will be printed
   */
  if ((x == 0x03) && (p->data != r1) && (p->data != r2))
  {
   printf ("[%c] ", p->data);
   /* set state variable to 0 if we do not want to print 
    * the ancestors of the first ancestor 
    */
   x = 0;
  }

  /* return state variable to parent
   */    
  return x;
}

Νομίζω ότι αυτό θα λειτουργήσει σωστά, αν και είμαι ακόμα να αποδείξει την ορθότητα του αλγορίθμου. Υπάρχει ένα μειονέκτημα, το οποίο είναι, αν ένας κόμβος είναι παιδί ενός άλλου κόμβου, τότε θα εκτυπώσει μόνο τον κόμβο που είναι η μητρική του άλλου, αντί για την εκτύπωση του γονέα τους. Εάν ένα από τα τυχαία κόμβος είναι ένας πρόγονος του άλλου τυχαίου κόμβου στη συνέχεια, αντί να την εκτύπωση του προγόνου τυχαίο κόμβο, θα εκτυπώσετε το γονέα του. Στην περίπτωση κατά την οποία ένα από τα τυχαία κόμβος είναι ο κόμβος ρίζα, θα εκτυπωθεί τίποτα, καθώς είναι πάντα ο πρόγονος του άλλου τυχαίου κόμβου, και ως εκ τούτου κοινός πρόγονος τους δεν υπάρχει. Σε αυτή την ειδική περίπτωση η συνάρτηση θα επιστρέψει 0x03στο mainκαι μπορεί να ανιχνευθεί.

Καθώς αυτό αλγόριθμος κάνει μια traversal postorder το λόγο αυτό απαιτείται O (n) χρόνο εκτέλεσης και έτσι O (n) μνήμη. Επίσης, όπως σταματά η αναζήτηση μόλις και οι δύο κόμβοι βρίσκονται, η ρηχή οι κόμβοι τόσο πιο γρήγορα η αναζήτηση τελειώνει.

ΕΚΣΥΓΧΡΟΝΙΖΩ

Εδώ είναι μερικές συζητήσεις λειτουργίας: Πώς να βρείτε το ελάχιστο κοινό πρόγονο των δύο κόμβων σε κάθε δυαδικό δέντρο;

Απαντήθηκε 30/05/2011 στις 11:23
πηγή χρήστη

ψήφοι
0

@Above, αυτό δεν θα λειτουργήσει, επειδή είστε υποθέτοντας ότι και οι δύο κόμβοι είναι άμεση παιδί κάποιου συγκεκριμένο κόμβο ...

            8
     10           12
 7             

και έδωσα τους κόμβους ως 7 και 12, πρέπει να είναι η απάντησή 8. Αφήνει αρέσει αυτό

    find(root, d1, d2, n1=null, n2=null)
     {
          if(n1 && n2) return;
          if(!root) return;

          else  if(root -> d == d1 ) n1 = root;
          else  if(root -> d == d2 ) n2 = root;                     
          find(root->left, d1, d2, n1, n2);
          find(root->right, d1, d2, n1, n2);
     }

     LCA(root, d1, d2)
     {
         node *n1=null, *n2=null;
         find(root, d1, d2, n1, n2);
         if(n1 == null || n2 == null )error 'nodes not present' exit(0);
         findIntersect(n1, n2); 
     }
     findInterSect(node *n1, node *n2)
     {
        l1 = length(n1);
        l2 = length(n2);
        node *g = n2, *l = n1;
        diff = abs(l1 - l2);
        if(l1>l2) g = n1 l =n2 
        while(diff) g = g->parent; diff--;
        // now both nodes are at same level
        while(g != l) g= g->parent, l = l->parent;
     }
Απαντήθηκε 30/08/2011 στις 17:29
πηγή χρήστη

ψήφοι
0

Ψευδοκώδικας:

node *FindCommonAncestor(node *root, node *node1, node *node2) {
  node *current = node1;
  node_list temp_list;
  temp_list.add(current);
  while (current != root) {
    current = current.parent;
    temp_list.add(current);
  }
  current = node2;
  while (current not in temp_list) {
    current = current.parent;
  }
  return current;
}

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

Τα πρώτα τρεξίματα βρόχος n φορές, όπου το η είναι το βάθος του node1, έτσι είναι O (n). Η δεύτερη βρόχος τρέχει φορές m, όπου m στο βάθος της Node2. Η αναζήτηση στον κατάλογο temp είναι (στη χειρότερη περίπτωση) n. Έτσι, το δεύτερο βρόχο είναι Ο (m * n), και κυριαρχεί, έτσι ώστε η λειτουργία εκτελείται σε O (m * n).

Εάν χρησιμοποιείτε μια δομή δεδομένων καλό σύνολο (π.χ., ένας πίνακας κατακερματισμού) για το χώρο temp αντί για μια λίστα, μπορείτε να κόψετε την αναζήτηση σε (συνήθως) Ο (1), χωρίς να αυξηθεί το κόστος της προσθήκης κόμβων σε θερμοκρασία. Αυτό μειώνει το χρόνο λειτουργίας μας να O (m).

Η απαίτηση χώρου είναι O (n) ή τον άλλο τρόπο.

Επειδή δεν γνωρίζουμε n και m μπροστά από το χρόνο, ας το θέσω ως προς το συνολικό αριθμό των κόμβων στο δέντρο: S. Αν το δέντρο είναι ισορροπημένη, τότε η και m είναι το καθένα ορίζεται από log_2 (S), έτσι ο χρόνος λειτουργίας είναι ο (log_2 (S) ^ 2). Log_2 είναι αρκετά ισχυρό, ώστε S θα πρέπει να πάρει αρκετά μεγάλη πριν είχα ανησυχούν για τη δύναμη του 2. Αν το δέντρο δεν είναι ισορροπημένη, τότε χάνουμε την log_2 (το δέντρο θα μπορούσε πράγματι να εκφυλιστεί σε μια συνδεδεμένη λίστα). Έτσι η απόλυτη χειρότερη περίπτωση (όταν ένας κόμβος είναι η ρίζα και το άλλο είναι το φύλλο ενός εντελώς εκφυλισμένη δέντρο) είναι O (S ^ 2).

Απαντήθηκε 30/08/2011 στις 18:15
πηγή χρήστη

ψήφοι
6

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

Με τον όρο «διασχίζουν τη» Θέλω μόνο να πω μετακινήστε το δείκτη στη μητρική του τρέχοντος κόμβου.

Επεξεργασία για να διευκρινίσει: Η βασική ιδέα είναι ότι όταν και οι δύο κόμβοι βρίσκονται στο ίδιο βάθος, μπορείτε να βρείτε την κοινή μητρική πολύ γρήγορα μόνο με την απλή διάσχιση. Έτσι, μπορείτε να ανεβείτε στο χαμηλότερο μέχρι και οι δύο είναι στο ίδιο βάθος, και στη συνέχεια θα διασχίσει πάνω. Δυστυχώς δεν ξέρω πραγματικά C ή είχα γράψει κώδικα, αλλά ότι ο αλγόριθμος θα πρέπει να απαντήσει στην ερώτησή σας.

Επεξεργασία και πάλι: Και η μέθοδος μου τρέχει σε χρόνο O (log (n)) χρόνο και O (1) μνήμη.

Ένα άλλο edit: O (log (n)) σε ένα ισορροπημένο δέντρο. Απόδοση χειρότερης περίπτωσης είναι O (n) για μια μη ισορροπημένη δέντρο. χάρη @DaveCahill

Απαντήθηκε 30/08/2011 στις 20:15
πηγή χρήστη

ψήφοι
1

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

Απαντήθηκε 30/08/2011 στις 20:47
πηγή χρήστη

ψήφοι
7

Ίσως ανόητη προσέγγιση:

Δημιουργήστε το μονοπάτι από κάθε κόμβο στη ρίζα, την αποθήκευσή του ως μια σειρά από «L» και «R».

Αντίστροφη αυτές τις χορδές. Πάρτε το μεγαλύτερο κοινό πρόθεμα - τώρα έχετε τη διαδρομή προς το κοινό πρόγονο.

Απαντήθηκε 30/08/2011 στις 22:21
πηγή χρήστη

ψήφοι
0
  1. Προ διάσχιση σειρά, εκτός αν κάθε 1 του κόμβου πληρούται και να σώσει τους κόμβους επισκέφθηκε uptil τώρα.

  2. Inorder διάσχισης, ξεκινήστε την αποθήκευση των κόμβων όταν κάθε 1 (από τα δύο παρέχονται κόμβοι) κόμβο ικανοποιείται, και να αποθηκεύσετε τη λίστα μέχρι να επιτευχθεί ο επόμενος κόμβος.

  3. μετά την διάσχιση της τάξης, ξεκινήστε την αποθήκευση των κόμβων όταν hav τόσο οι κόμβοι έχουν επισκεφτεί ...
               ΕΝΑ         
      προ ΧΡΙΣΤΟΥ         
  DEFG       
HIJKLMNO     

Ας υποθέσουμε Η και Ε είναι δύο τυχαία κόμβους.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Βρείτε τον πρώτο κόμβο κοινή και στις τρεις ...

Απαντήθηκε 15/01/2012 στις 15:55
πηγή χρήστη

ψήφοι
3

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

commonAncestor tree a b:
  value := <value of node 'tree'>
  if (a < value) && (b < value)
  then commonAncestor (left tree) a b
  else if (a > value) && (b > value)
  then commonAncestor (right tree) a b
  else tree

Είναι ενδιαφέρον ότι η προσέγγιση αυτή θα κλιμακώνεται σε περισσότερα από δύο κόμβους (έλεγχος για όλα αυτά να είναι στην αριστερή πλευρά του tree, κλπ)

Απαντήθηκε 05/02/2012 στις 06:18
πηγή χρήστη

ψήφοι
0

γεια θα επιστρέψει χαμηλότερη τιμή κόμβο πρόγονο, όπου ρίζα του δέντρου και VAL1, Val2 - τιμές> δεδομένα για τους κόμβους είναι να περάσει

int CommonAncestor(node *root, int val1,int val2) 
{

    if(root == NULL || (! root->left && ! root->right  )
        return false;

        while(root)
        {
            if(root->data < val1 && root->data < val2)
             {
                root = root->left;
             }
             else if(root->data > val1 && root->data > val2)
            {
                root= root->right;
            }
            else
              return root->data;      
        }
}
Απαντήθηκε 25/09/2012 στις 11:57
πηγή χρήστη

ψήφοι
0

Εδώ είναι δύο προσεγγίσεις σε C # (.net) (και τα δύο συζητούνται παραπάνω) για την αναφορά:

  1. Αναδρομική εκδοχή της εύρεσης LCA σε δυαδικό δένδρο (O (N) - το περισσότερο το επισκέπτονται κάθε κόμβος) (κύρια σημεία του διαλύματος είναι LCA είναι (α) μόνο κόμβο σε δυαδικό δένδρο όπου αμφότερα τα στοιχεία κατοικούν εκατέρωθεν του υποδένδρων (αριστερά και δεξιά) είναι ΑΚΖ. (β) και επίσης, δεν έχει σημασία ποια κόμβου είναι παρούσα σε κάθε πλευρά - αρχικά προσπάθησαν να κρατήσουν αυτές τις πληροφορίες, και προφανώς η αναδρομική συνάρτηση γίνει τόσο συγκεχυμένη μία φορά το κατάλαβα, έγινε πολύ κομψό..

  2. Ψάχνοντας τους δύο κόμβους (O (N)), καθώς και την παρακολούθηση των διαδρομών (χρησιμοποιεί περισσότερο χώρο - και έτσι, # 1 είναι ίσως ανώτερη παρ 'όλο που ο χώρος είναι μάλλον αμελητέα αν το δυαδικό δέντρο είναι καλά ισορροπημένη και, στη συνέχεια, επιπλέον κατανάλωση μνήμης θα είναι μόνο σε O (log (N)).

    έτσι ώστε οι διαδρομές σύγκριση (essentailly παρόμοια με αποδεκτή απάντηση - αλλά οι διαδρομές υπολογίζονται με την παραδοχή κόμβο δείκτης δεν είναι παρών στο δυαδικό κόμβο του δένδρου)

  3. Ακριβώς για την ολοκλήρωση ( που δεν σχετίζονται με ερώτηση ), ΑΚΖ στο BST (O (log (N))

  4. δοκιμές

Αναδρομική:

private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode, 
            int e1, int e2)
        {
            Debug.Assert(e1 != e2);

            if(treeNode == null)
            {
                return null;
            }
            if((treeNode.Element == e1)
                || (treeNode.Element == e2))
            {
                //we don't care which element is present (e1 or e2), we just need to check 
                //if one of them is there
                return treeNode;
            }
            var nLeft = this.LeastCommonAncestorUsingRecursion(treeNode.Left, e1, e2);
            var nRight = this.LeastCommonAncestorUsingRecursion(treeNode.Right, e1, e2);
            if(nLeft != null && nRight != null)
            {
                //note that this condition will be true only at least common ancestor
                return treeNode;
            }
            else if(nLeft != null)
            {
                return nLeft;
            }
            else if(nRight != null)
            {
                return nRight;
            }
            return null;
        }

όπου πάνω από ιδιωτικό αναδρομική έκδοση επικαλείται ακόλουθες δημόσιες μέθοδο:

public BinaryTreeNode LeastCommonAncestorUsingRecursion(int e1, int e2)
        {
            var n = this.FindNode(this._root, e1);
            if(null == n)
            {
                throw new Exception("Element not found: " + e1);
            }
            if (e1 == e2)
            {   
                return n;
            }
            n = this.FindNode(this._root, e2);
            if (null == n)
            {
                throw new Exception("Element not found: " + e2);
            }
            var node = this.LeastCommonAncestorUsingRecursion(this._root, e1, e2);
            if (null == node)
            {
                throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
            }
            return node;
        }

Λύση με την παρακολούθηση των διαδρομών και των δύο κόμβων:

public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
        {
            var path1 = new List<BinaryTreeNode>();
            var node1 = this.FindNodeAndPath(this._root, e1, path1);
            if(node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e1));
            }
            if(e1 == e2)
            {
                return node1;
            }
            List<BinaryTreeNode> path2 = new List<BinaryTreeNode>();
            var node2 = this.FindNodeAndPath(this._root, e2, path2);
            if (node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e2));
            }
            BinaryTreeNode lca = null;
            Debug.Assert(path1[0] == this._root);
            Debug.Assert(path2[0] == this._root);
            int i = 0;
            while((i < path1.Count)
                && (i < path2.Count)
                && (path2[i] == path1[i]))
            {
                lca = path1[i];
                i++;
            }
            Debug.Assert(null != lca);
            return lca;
        }

όπου FindNodeAndPath ορίζεται ως

private BinaryTreeNode FindNodeAndPath(BinaryTreeNode node, int e, List<BinaryTreeNode> path)
        {
            if(node == null)
            {
                return null;
            }
            if(node.Element == e)
            {
                path.Add(node);
                return node;
            }
            var n = this.FindNodeAndPath(node.Left, e, path);
            if(n == null)
            {
                n = this.FindNodeAndPath(node.Right, e, path);
            }
            if(n != null)
            {
                path.Insert(0, node);
                return n;
            }
            return null;
        }

BST (LCA) - που δεν σχετίζονται (μόνο για την ολοκλήρωση για αναφορά)

public BinaryTreeNode BstLeastCommonAncestor(int e1, int e2)
        {
            //ensure both elements are there in the bst
            var n1 = this.BstFind(e1, throwIfNotFound: true);
            if(e1 == e2)
            {
                return n1;
            }
            this.BstFind(e2, throwIfNotFound: true);
            BinaryTreeNode leastCommonAcncestor = this._root;
            var iterativeNode = this._root;
            while(iterativeNode != null)
            {
                if((iterativeNode.Element > e1 ) && (iterativeNode.Element > e2))
                {
                    iterativeNode = iterativeNode.Left;
                }
                else if((iterativeNode.Element < e1) && (iterativeNode.Element < e2))
                {
                    iterativeNode = iterativeNode.Right;
                }
                else
                {
                    //i.e; either iterative node is equal to e1 or e2 or in between e1 and e2
                    return iterativeNode;
                }
            }
            //control will never come here
            return leastCommonAcncestor;
        }

Δοκιμές μονάδα

[TestMethod]
        public void LeastCommonAncestorTests()
        {
            int[] a = { 13, 2, 18, 1, 5, 17, 20, 3, 6, 16, 21, 4, 14, 15, 25, 22, 24 };
            int[] b = { 13, 13, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 22};
            BinarySearchTree bst = new BinarySearchTree();
            foreach (int e in a)
            {
                bst.Add(e);
                bst.Delete(e);
                bst.Add(e);
            }
            for(int i = 0; i < b.Length; i++)
            {
                var n = bst.BstLeastCommonAncestor(a[i], a[i + 1]);
                Assert.IsTrue(n.Element == b[i]);
                var n1 = bst.LeastCommonAncestorUsingPaths(a[i], a[i + 1]);
                Assert.IsTrue(n1.Element == b[i]);
                Assert.IsTrue(n == n1);
                var n2 = bst.LeastCommonAncestorUsingRecursion(a[i], a[i + 1]);
                Assert.IsTrue(n2.Element == b[i]);
                Assert.IsTrue(n2 == n1);
                Assert.IsTrue(n2 == n);
            }
        }
Απαντήθηκε 14/07/2014 στις 14:02
πηγή χρήστη

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