किसी भी द्विआधारी वृक्ष में दो नोड्स के सबसे कम सामान्य पूर्वजों को कैसे ढूंढें?


187

बाइनरी ट्री यहां जरूरी नहीं कि बाइनरी सर्च ट्री हो।
संरचना के रूप में लिया जा सकता है -

struct node {
    int data;
    struct node *left;
    struct node *right;
};

अधिकतम समाधान जो मैं एक दोस्त के साथ काम कर सकता था, वह इस प्रकार का था - इस बाइनरी ट्री
पर विचार करें :

बाइनरी ट्री

इनवर्टर ट्रैवर्सल पैदावार - 8, 4, 9, 2, 5, 1, 6, 3, 7

और पोस्टऑर्डर ट्रैवर्सल पैदावार - 8, 9, 4, 5, 2, 6, 7, 3, 1

उदाहरण के लिए, यदि हम नोड्स 8 और 5 के सामान्य पूर्वजों को ढूंढना चाहते हैं, तो हम उन सभी नोड्स की एक सूची बनाते हैं जो 8 और 5 के बीच के इनवर्टर ट्री ट्रैवर्सल हैं, जो इस मामले में होता है [4, 9] , २]। फिर हम जांचते हैं कि इस सूची में कौन सा नोड पोस्टऑर्डर ट्रैवर्सल में अंतिम है, जो कि 2 है। इसलिए 8 और 5 के लिए सामान्य पूर्वज 2 है।

इस एल्गोरिथ्म की जटिलता, मेरा मानना ​​है कि इनवर्टर / पोस्टऑर्डर ट्रैवर्सल्स के लिए O (n) (O (n) है, बाकी चरण फिर से O (n) हैं क्योंकि वे सरणियों में सरल पुनरावृत्तियों से अधिक कुछ नहीं हैं)। लेकिन एक मजबूत मौका है कि यह गलत है। :-)

लेकिन यह एक बहुत ही क्रूड दृष्टिकोण है, और मुझे यकीन नहीं है कि यह किसी मामले के लिए टूट जाता है। क्या इस समस्या का कोई और (संभवतः अधिक इष्टतम) समाधान है?


6
जिज्ञासा से बाहर, इसका व्यावहारिक उपयोग क्या है?
डेविड ब्रूनेल

19
@ डेविड: एलसीए क्वेरी का उत्तर देना बहुत उपयोगी है। LCA + प्रत्यय वृक्ष = शक्तिशाली स्ट्रिंग संबंधी एल्गोरिदम।

44
और जब मैंने एक समान प्रश्न पूछा, तो यह उसके साक्षात्कार प्रश्न जैसी टिप्पणियों के साथ मतदान हो गया। एसओ का द्वंद्व? :(
some_other_guy

5
@Siddant +1 प्रश्न में दिए गए विवरण के लिए। :)
आमोद

5
@DavidBrunelle LCA के अभिकलन का एक व्यावहारिक अनुप्रयोग: यह वेब पृष्ठों को रेंडर करते समय एक आवश्यक गणना है, विशेष रूप से कैस्केडिंग स्टाइल शीट्स (सीएसएस) की गणना करते समय जो एक विशेष DOM तत्व पर लागू होती है।
zc22

जवाबों:


74

निक जॉनसन सही है कि एक ओ (एन) समय जटिलता एल्गोरिथ्म सबसे अच्छा है यदि आप कोई अभिभावक नहीं हैं तो आप कर सकते हैं।) उस एल्गोरिथ्म के सरल पुनरावर्ती संस्करण के लिए कोडिंग पोस्ट में कोड देखें जो ओ (एन) समय में चलता है। ।

लेकिन ध्यान रखें कि यदि आपके नोड्स में मूल बिंदु हैं, तो एक बेहतर एल्गोरिथ्म संभव है। विचाराधीन दोनों नोड्स के लिए नोड से आरंभ करके रूट से नोड तक एक सूची बनाते हैं, और सामने अभिभावक को सम्मिलित करते हैं।

तो आपके उदाहरण में 8 के लिए, आपको मिलता है (कदम दिखाते हुए): {4}, {2, 4}, {1, 2, 4}

प्रश्न में अपने अन्य नोड के लिए भी ऐसा ही करें, जिसके परिणामस्वरूप (चरण नहीं दिखाए गए): {1, 2}

अब आप उन दो सूचियों की तुलना करें जो आपने पहले तत्व की तलाश में हैं जहाँ सूची अलग है, या सूची में से किसी एक का अंतिम तत्व, जो भी पहले आता है।

इस एल्गोरिथ्म में ओ (एच) समय की आवश्यकता होती है जहां एच पेड़ की ऊंचाई है। सबसे खराब स्थिति में ओ (एच) ओ (एन) के बराबर है, लेकिन अगर पेड़ संतुलित है, तो वह केवल ओ (लॉग (एन)) है। इसमें O (h) स्पेस की भी आवश्यकता होती है। एक बेहतर संस्करण संभव है जो केवल निरंतर स्थान का उपयोग करता है, जिसमें CEGRD के पोस्ट में दिखाया गया कोड है


भले ही पेड़ का निर्माण कैसे किया जाता है, अगर यह एक ऐसा ऑपरेशन होगा जो आप पेड़ पर कई बार करते हैं, तो इसे बीच में बदले बिना, ऐसे अन्य एल्गोरिदम हैं जिनका उपयोग आप ओ (n) [रैखिक] समय की तैयारी के लिए कर सकते हैं, लेकिन फिर कोई भी ढूंढ रहा है जोड़ी केवल O (1) [स्थिर] समय लेती है। इन एल्गोरिदम के संदर्भ के लिए, विकिपीडिया पर सबसे कम सामान्य पूर्वज समस्या पृष्ठ देखें । (मूल रूप से इस लिंक को पोस्ट करने के लिए जेसन को श्रेय)


1
यदि माता-पिता सूचक दिया जाता है तो वह काम करता है। वृक्ष में नोड्स मेरे प्रश्न में दी गई संरचना के रूप में हैं - बस बाएं / दाएं बच्चे के संकेत, कोई माता-पिता सूचक नहीं। क्या कोई ओ (लॉग (एन)) समाधान है अगर कोई माता-पिता सूचक उपलब्ध नहीं है, और पेड़ एक द्विआधारी खोज पेड़ नहीं है, और क्या केवल एक द्विआधारी पेड़ है?
सिद्दांत

2
यदि आपके पास माता-पिता और दिए गए नोड के बीच का रास्ता खोजने का कोई विशेष तरीका नहीं है, तो उसे खोजने के लिए औसतन O (n) समय लगेगा। इससे O (लॉग (n)) समय होना असंभव हो जाएगा। हालाँकि, O (n) एक समय की लागत, O (1) जोड़ी खोजने के साथ आपकी सबसे अच्छी शर्त हो सकती है, यदि आप इस ऑपरेशन को बीच में पेड़ को बदले बिना कई बार करने जा रहे थे। अन्यथा, यदि संभव हो तो आपको पेरेंट पॉइंटर जोड़ना चाहिए। यह काफी कुछ संभावित एल्गोरिदम को तेज कर सकता है, फिर भी मुझे पूरा यकीन है कि यह किसी भी मौजूदा एल्गोरिदम के क्रम को नहीं बदलता है। उम्मीद है की यह मदद करेगा।
केविन कैथार्ट

1
इस दृष्टिकोण को O (1) मेमोरी का उपयोग करके किया जा सकता है - देखें Artelius (और अन्य) का समाधान stackoverflow.com/questions/1594061/… पर
टॉम सिरगेड

@ टोम: वास्तव में, यह स्मृति आधारितता को सूची आधारित एल्गोरिथ्म के लिए O (1) तक सीमित करने के लिए काम करेगा। जाहिर है कि नोड्स की गहराई प्राप्त करने के लिए प्रत्येक पक्ष के लिए एक बार खुद पेड़ के माध्यम से पुनरावृत्ति करने का मतलब है, और फिर आम पूर्वज को खोजने के लिए दूसरी बार (आंशिक)। O (h) का समय और O (1) स्पेस स्पष्ट रूप से पेरेंट पॉइंटर्स होने के मामले में इष्टतम है, और O (n) प्रीकम्प्यूटेशन नहीं कर रहा है।
केविन कैथार्ट

1
@ALBI O(h)तभी है O(log(n))जब पेड़ संतुलित हो। किसी भी पेड़ के लिए, यह द्विआधारी हो या न हो, यदि आपके पास माता-पिता के संकेत हैं, तो आप O(h)समय में मूल पत्ती सूचक का पालन करके एक पत्ती से रूट तक का समय निर्धारित कर सकते हैं h। जो आपको पत्ती से जड़ तक का रास्ता देता है। यदि पथ को एक स्टैक के रूप में संग्रहीत किया जाता है, तो स्टैक को पुनरावृत्त करने से आपको जड़ से पत्ती तक का मार्ग मिल जाता है। यदि आपके पास माता-पिता की कमी है, और पेड़ के लिए कोई विशेष संरचना नहीं है, तो जड़ से पत्ती तक का रास्ता खोजने में O(n)समय लगता है।
केविन कैथार्ट

108

rootनोड से शुरू करना और नीचे की ओर बढ़ना यदि आपको कोई ऐसा नोड मिलता है जो pया तो या qउसके सीधे बच्चे के रूप में है तो यह एलसीए है। (संपादित करें - यह होना चाहिए pया यदि qनोड का मान है, तो इसे वापस कर दें। अन्यथा यह तब विफल हो जाएगा जब एक pया qदूसरे का प्रत्यक्ष बच्चा हो।)

और यदि आपको pइसके दाएं (या बाएं) सबट्री में नोड मिलता है और qइसके लेफ्ट (या राइट) सबट्री में है तो यह LCA है।

निश्चित कोड जैसा दिखता है:

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {

        // no root no LCA.
        if(!root) {
                return NULL;
        }

        // if either p or q is the root then root is LCA.
        if(root==p || root==q) {
                return root;
        } else {
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) {
                        return root;
                }
                // else if l is not null, l is LCA.
                else if(l) {
                        return l;
                } else {
                        return r;
                }
        }
}

नीचे का कोड तब विफल होता है जब या तो दूसरे का सीधा बच्चा होता है।

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {

        // no root no LCA.
        if(!root) {
                return NULL;
        }

        // if either p or q is direct child of root then root is LCA.
        if(root->left==p || root->left==q || 
           root->right ==p || root->right ==q) {
                return root;
        } else {
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) {
                        return root;
                }
                // else if l is not null, l is LCA.
                else if(l) {
                        return l;
                } else {
                        return r;
                }
        }
}

कार्रवाई में कोड


2
सुरुचिपूर्ण समाधान, लेकिन जड़ == पी || root == q => वापसी रूट बिट overoptimistic लगता है। क्या होगा अगर यह पता चला कि जड़ पी / क्यू है, लेकिन अन्य मांग के लिए नोड वास्तव में पेड़ में नहीं है?
इयान दुर्कन

15
मुझे लगता है कि यह कोड तब विफल हो जाता है जब p या q एक मान होता है जो बाइनरी ट्री में नहीं होता है। क्या मैं सही हू? उदाहरण के लिए LCA (8,20)। ur code रिटर्न 8. लेकिन 20 बाइनरी ट्री में मौजूद नहीं है
javaMan

3
इस समाधान की लागत क्या है? क्या यह कुशल है? ऐसा प्रतीत होता है कि पी और q दोनों के मिल जाने के बाद भी यह खोज जारी है। क्या इस संभावना के कारण कि पी और क्यू पेड़ में अद्वितीय नहीं हो सकता है क्योंकि यह एक बीएसटी नहीं है और इसमें डुप्लिकेट हो सकते हैं?
14

3
@ माइक, यह समाधान निश्चित रूप से ओ (एन) है, क्योंकि आप प्रत्येक नोड को सबसे खराब स्थिति में केवल एक बार पार करते हैं। पीटर ली, यह सबसे कुशल है जिसे आप मूल बिंदुओं का उपयोग किए बिना बना सकते हैं। क्या आपके पास एक बेहतर समाधान है?
gsingh2011

8
पहले अपूर्ण समाधान को हटा दिया जाना चाहिए ताकि यह विचलित न हो
ज़िनन ज़िंग

50

यहाँ JAVA में काम करने वाला कोड है

public static Node LCA(Node root, Node a, Node b) {
   if (root == null) {
       return null;
   }

   // If the root is one of a or b, then it is the LCA
   if (root == a || root == b) {
       return root;
   }

   Node left = LCA(root.left, a, b);
   Node right = LCA(root.right, a, b);

   // If both nodes lie in left or right then their LCA is in left or right,
   // Otherwise root is their LCA
   if (left != null && right != null) {
      return root;
   }

   return (left != null) ? left : right; 
}

4
जब पेड़ में एक नोड मौजूद नहीं होता है तो यह काम नहीं करता है।
प्रतिक खडलो

यदि आपका पेड़ एक BST है तो क्या आप अपना कोड ऑप्टिमाइज़ करेंगे?
मोना जलाल

1
"यदि जड़ एक या बी में से एक है, तो यह एलसीए है।" यह सच नहीं हो सकता है। इस बिंदु पर आप जो जानते हैं वह यह है कि एलसीए को खोजने के लिए आपको इसके किसी भी बच्चे की जांच करने की आवश्यकता नहीं है। ऐसा इसलिए होता है क्योंकि हम बाद में जांच कर सकते हैं कि क्या मूल के माता-पिता के लिए, दोनों शाखाओं (LCA माता-पिता) पर मैच थे या उनमें से सिर्फ एक (जिस स्थिति में LCA हो सकता है, या उससे भी बड़ा पूर्वज LCA हो सकता है )।
andresp

28

अब तक दिए गए उत्तर पुनरावृत्ति या दुकानों का उपयोग करते हैं, उदाहरण के लिए, स्मृति में एक पथ।

यदि आपके पास बहुत गहरा पेड़ है तो ये दोनों दृष्टिकोण विफल हो सकते हैं।

यहाँ इस प्रश्न पर मेरा विचार है। जब हम दोनों नोड्स की गहराई (जड़ से दूरी) की जांच करते हैं, यदि वे समान हैं, तो हम दोनों नोड्स से आम पूर्वज की ओर सुरक्षित रूप से ऊपर की ओर बढ़ सकते हैं। यदि गहराई में से एक बड़ा है तो हमें दूसरे में रहते हुए गहरी नोड से ऊपर की ओर जाना चाहिए।

यहाँ कोड है:

findLowestCommonAncestor(v,w):
  depth_vv = depth(v);
  depth_ww = depth(w);

  vv = v; 
  ww = w;

  while( depth_vv != depth_ww ) {
    if ( depth_vv > depth_ww ) {
      vv = parent(vv);
      depth_vv--;
    else {
      ww = parent(ww);
      depth_ww--;
    }
  }

  while( vv != ww ) {
    vv = parent(vv);
    ww = parent(ww);
  }

  return vv;    

इस एल्गोरिथ्म की समय जटिलता है: O (n)। इस एल्गोरिथ्म की अंतरिक्ष जटिलता है: O (1)।

गहराई की गणना के बारे में, हम पहले परिभाषा को याद कर सकते हैं: यदि v जड़ है, तो गहराई (v) = 0; अन्यथा, गहराई (v) = गहराई (माता-पिता (v)) + 1. हम गहराई की गणना निम्नानुसार कर सकते हैं:

depth(v):
  int d = 0;
  vv = v;
  while ( vv is not root ) {
    vv = parent(vv);
    d++;
  }
  return d;

6
बाइनरी पेड़ों में मूल तत्व का संदर्भ नहीं होता है, आमतौर पर। माता-पिता का संदर्भ जोड़ना बिना किसी समस्या के किया जा सकता है, लेकिन मैं उस O (n) सहायक स्थान पर विचार करूंगा।
जॉन कुरलक

इस समाधान में एक सूक्ष्म धारणा है। यदि एक नोड दूसरे का प्रत्यक्ष या अप्रत्यक्ष माता-पिता है (यानी, गहरी नोड उथले नोड में निहित एक पेड़ में है), तो यह समाधान परिणाम के रूप में उथले नोड के माता-पिता को वापस कर देता है। आप सबसे कम सामान्य पूर्वज को कैसे परिभाषित करते हैं, इस पर निर्भर करते हुए, यह वह नहीं हो सकता जो आप चाहते हैं। कुछ परिभाषाओं को मूल माता-पिता होने के लिए उथले नोड की आवश्यकता होगी। इस मामले में, आपको यह ट्रैक करने की आवश्यकता होगी कि कौन सी उथली नोड है और उसे वापस करें।
श्रीकांत

8

खैर, इस तरह का निर्भर करता है कि आपका बाइनरी ट्री कैसे संरचित है। संभवतः आपके पास पेड़ की जड़ को वांछित पत्ती के नोड को खोजने का कोई रास्ता है - बस इसे उन दोनों मूल्यों पर लागू करें जब तक कि आप शाखाएं नहीं चुनते।

यदि आपके पास रूट दिए गए वांछित पत्ती को खोजने का कोई तरीका नहीं है, तो आपका एकमात्र समाधान - सामान्य ऑपरेशन में और अंतिम आम नोड को खोजने के लिए - पेड़ की एक क्रूर-बल खोज है।


8

इस पर पाया जा सकता है: - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html

 tree_node_type *LowestCommonAncestor(
 tree_node_type *root , tree_node_type *p , tree_node_type *q)
 {
     tree_node_type *l , *r , *temp;
     if(root==NULL)
     {
        return NULL;
     }

    if(root->left==p || root->left==q || root->right ==p || root->right ==q)
    {
        return root;
    }
    else
    {
        l=LowestCommonAncestor(root->left , p , q);
        r=LowestCommonAncestor(root->right , p, q);

        if(l!=NULL && r!=NULL)
        {
            return root;
        }
        else
        {
        temp = (l!=NULL)?l:r;
        return temp;
        }
    }
}

क्या आप मुझे बता सकते हैं कि यदि पी मौजूद है तो आपका कोड कैसे व्यवहार करेगा लेकिन पेड़ में क्यू बिल्कुल भी मौजूद नहीं है? इसी तरह p और q दोनों मौजूद नहीं हैं। धन्यवाद!!!
कोशिश कर रहा

समय के मामले में बड़ा ओ क्या है? मुझे लगता है कि यह ओ (एन * लॉग (एन)) है, दो धीमे।
पीटर ली


6

दो नोड के सामान्य पूर्वज का पता लगाने के लिए: -

  • पेड़ में दिए गए नोड Node1 को बाइनरी खोज का उपयोग करके ढूंढें और इस प्रक्रिया में आने वाले सभी नोड्स को ए 1 ए 1 में कहें। समय - O (logn), अंतरिक्ष - O (logn)
  • बाइनरी खोज का उपयोग करके पेड़ में दिए गए Node2 को ढूंढें और इस प्रक्रिया में विज़िट किए गए सभी नोड्स को ए 2 कहकर सहेजें। समय - O (logn), अंतरिक्ष - O (logn)
  • यदि A1 सूची या A2 सूची खाली है, तो एक नोड मौजूद नहीं है, इसलिए कोई सामान्य पूर्वज नहीं है।
  • यदि A1 सूची और A2 सूची गैर-रिक्त हैं, तो सूची में तब तक देखें जब तक आप गैर-मिलान नोड नहीं पाते। जैसे ही आपको ऐसा नोड मिलता है, उससे पहले नोड सामान्य पूर्वज है।

यह बाइनरी सर्च ट्री के लिए काम करेगा।


2
उन्होंने स्पष्ट रूप से कहा कि पेड़ जरूरी नहीं कि एक BST हो।
पीटर ली

@Peter ली - उपरोक्त तर्क किसी भी बाइनरी ट्री के लिए एक साधारण बदलाव के साथ भी काम करेगा। दिए गए नोड्स की द्विआधारी खोज के अनुरूप, रैखिक खोज लागू करें (अर्थात किसी भी ट्रैवर्सल लेकिन दोनों मामलों के लिए समान होना चाहिए)। ऑफ कोर्स रनटाइम O (logn) के बजाय O (n) होगा। वास्तव में यह अहंकार सबसे मजबूत है जब माता-पिता सूचक उपलब्ध नहीं है। कई द्वारा दिए गए रूचिकर एल्गोरिथम (अर्थात 'कोडेडडिक्ट') तब काम नहीं करेंगे जब दिए गए नोड में से कोई एक पेड़ से संबंधित न हो)
KGhatak


3

नीचे दिए गए पुनरावर्ती एल्गोरिदम संतुलित बाइनरी ट्री के लिए O (लॉग एन) में चलेंगे। यदि दोनों में से कोई भी नोड getLCA () फ़ंक्शन में उत्तीर्ण होता है, तो रूट LCA होगा और इसके लिए किसी भी पुनरावर्तन की आवश्यकता नहीं होगी।

परीक्षण के मामलों। [१] दोनों नोड्स एन १ और एन २ पेड़ में हैं और अपने मूल नोड के दोनों ओर रहते हैं। [२] या तो नोड n1 या n2 रूट है, LCA रूट है। [३] पेड़ में केवल n1 या n2 है, LCA या तो पेड़ की जड़ के बाईं उप-प्रजाति का मूल नोड होगा, या LCA, पेड़ की जड़ के दाएं उप-वर्ग का मूल नोड होगा।

[४] न तो n1 या n2 पेड़ में है, कोई LCA नहीं है। [५] एन १ और एन २ दोनों एक दूसरे के बगल में एक सीधी रेखा में हैं, एलसीए या तो एन १ या एन २ होगा जो कभी भी पेड़ की जड़ तक बंद हो जाता है।

//find the search node below root
bool findNode(node* root, node* search)
{
    //base case
    if(root == NULL)
        return false;

    if(root->val == search->val)
        return true;

    //search for the node in the left and right subtrees, if found in either return true
    return (findNode(root->left, search) || findNode(root->right, search));
}

//returns the LCA, n1 & n2 are the 2 nodes for which we are
//establishing the LCA for
node* getLCA(node* root, node* n1, node* n2)
{
    //base case
    if(root == NULL)
        return NULL;

    //If 1 of the nodes is the root then the root is the LCA
    //no need to recurse.
    if(n1 == root || n2 == root)
        return root;

    //check on which side of the root n1 and n2 reside
    bool n1OnLeft = findNode(root->left, n1);
    bool n2OnLeft = findNode(root->left, n2);

    //n1 & n2 are on different sides of the root, so root is the LCA
    if(n1OnLeft != n2OnLeft)
        return root;

    //if both n1 & n2 are on the left of the root traverse left sub tree only
    //to find the node where n1 & n2 diverge otherwise traverse right subtree
    if(n1OnLeft)
        return getLCA(root->left, n1, n2);
    else
        return getLCA(root->right, n1, n2);
}

3

बस पूरे पेड़ से नीचे चलें rootजब तक कि दोनों ने नोड्स न दिया हो, कहो pऔरq , जिसके लिए पूर्वज को ढूंढना है, एक ही उप-पेड़ में हैं (जिसका अर्थ है कि उनके मूल्य दोनों छोटे हैं या दोनों जड़ से बड़े हैं)।

यह पेड़ के बाकी हिस्सों की ओर न देखते हुए, जड़ से सीधे Least Common Ancestor तक जाती है, इसलिए यह जितनी तेजी से उतरती है उतनी ही तेजी से चलती है। इसे करने के कुछ तरीके।

Iterative, O (1) स्थान

अजगर

def lowestCommonAncestor(self, root, p, q):
    while (root.val - p.val) * (root.val - q.val) > 0:
        root = (root.left, root.right)[p.val > root.val]
    return root

जावा

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    while ((root.val - p.val) * (root.val - q.val) > 0)
        root = p.val < root.val ? root.left : root.right;
    return root;
}

अतिप्रवाह के मामले में, मैं (root.val - (long) p.val) * (root.val - (long) p.val) करूँगा

पुनरावर्ती

अजगर

def lowestCommonAncestor(self, root, p, q):
    next = p.val < root.val > q.val and root.left or \
           p.val > root.val < q.val and root.right
    return self.lowestCommonAncestor(next, p, q) if next else root

जावा

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    return (root.val - p.val) * (root.val - q.val) < 1 ? root :
           lowestCommonAncestor(p.val < root.val ? root.left : root.right, p, q);
}

2
Node *LCA(Node *root, Node *p, Node *q) {
  if (!root) return NULL;
  if (root == p || root == q) return root;
  Node *L = LCA(root->left, p, q);
  Node *R = LCA(root->right, p, q);
  if (L && R) return root;  // if p and q are on both sides
  return L ? L : R;  // either one of p,q is on one side OR p,q is not in L&R subtrees
}

2

इस पेड़ पर विचार करें यहां छवि विवरण दर्ज करें

यदि हम पोस्टऑर्डर और प्रीऑर्डर ट्रैवर्सल करते हैं और पहले वाले सामान्य पूर्ववर्ती और उत्तराधिकारी को ढूंढते हैं, तो हम सामान्य पूर्वजों को प्राप्त करते हैं।

पोस्टऑर्डर => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 प्रीऑर्डर => 7,3,1,0,2,6,6 , 5,12,9,8,11,10,13,15,14

  • जैसे: १

8,11 का सामान्य पूर्वज

पोस्टऑर्डर में हमारे पास => 9,14,15,13,12,7 8 और 11 के बाद प्रीऑर्डर में हमारे पास => 7,3,1,0,2,6,6,5,5,12,9 8 और 11 से पहले है

9 वह पहली सामान्य संख्या है जो पोस्टऑर्डर में 8 और 11 के बाद होती है और प्रीऑर्डर में 8 और 11 से पहले होती है, इसलिए 9 का उत्तर है

  • जैसे: २

कम से कम 5,10 का सामान्य पूर्वज

11,9,14,15,13,12,7 पोस्टऑर्डर 7,3,1,0,2,6,4 प्रीऑर्डर में

7 वह पहली संख्या है जो पोस्टऑर्डर में 5,10 के बाद और प्रीऑर्डर में 5,10 से पहले होती है, इसलिए 7 का उत्तर है


2

यदि यह पूर्ण द्विआधारी वृक्ष है जिसमें नोड x के बच्चे 2 * x और 2 * x + 1 हैं, तो यह करने का एक तेज़ तरीका है

int get_bits(unsigned int x) {
  int high = 31;
  int low = 0,mid;
  while(high>=low) {
    mid = (high+low)/2;
    if(1<<mid==x)
      return mid+1;
    if(1<<mid<x) {
      low = mid+1;
    }
    else {
      high = mid-1;
    }
  }
  if(1<<mid>x)
    return mid;
  return mid+1;
}

unsigned int Common_Ancestor(unsigned int x,unsigned int y) {

  int xbits = get_bits(x);
  int ybits = get_bits(y);
  int diff,kbits;
  unsigned int k;
  if(xbits>ybits) {
    diff = xbits-ybits;
    x = x >> diff;
  }
  else if(xbits<ybits) {
    diff = ybits-xbits;
    y = y >> diff;
  }
  k = x^y;
  kbits = get_bits(k);
  return y>>kbits;  
}

यह कैसे काम करता है

  1. द्विआधारी खोज का उपयोग करने के लिए आवश्यक बिट्स प्राप्त करें जो कि द्विआधारी खोज का उपयोग हे (लॉग (32))
  2. x और y के बाइनरी नोटेशन का सामान्य उपसर्ग सामान्य पूर्वज है
  3. जो भी बिट्स का बड़ा प्रतिनिधित्व करता है, उसी बिट को k >> diff द्वारा लाया जाता है
  4. k = x ^ y x और y के सामान्य उपसर्ग मिटाता है
  5. शेष प्रत्यय का प्रतिनिधित्व करने वाले बिट्स खोजें
  6. सामान्य प्रत्यय जो सामान्य पूर्वज है पाने के लिए प्रत्यय बिट्स द्वारा x या y पारी।

यह काम करता है क्योंकि मूल रूप से बड़ी संख्या को दो पुनरावर्ती रूप से विभाजित करते हैं जब तक कि दोनों संख्याएं समान न हों। वह संख्या सामान्य पूर्वज है। डिवाइडिंग प्रभावी रूप से सही बदलाव का विकल्प है। इसलिए हमें निकटतम पूर्वज को खोजने के लिए दो संख्याओं के सामान्य उपसर्ग खोजने की आवश्यकता है


2

स्काला में, आप कर सकते हैं:

  abstract class Tree
  case class Node(a:Int, left:Tree, right:Tree) extends Tree
  case class Leaf(a:Int) extends Tree

  def lca(tree:Tree, a:Int, b:Int):Tree = {
    tree match {
      case Node(ab,l,r) => {
        if(ab==a || ab ==b) tree else {
          val temp = lca(l,a,b);
          val temp2 = lca(r,a,b);
          if(temp!=null && temp2 !=null)
            tree
          else if (temp==null && temp2==null)
            null
          else if (temp==null) r else l
        }

      }
      case Leaf(ab) => if(ab==a || ab ==b) tree else null
    }
  }

1
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null || root == p || root == q){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        return left == null ? right : right == null ? left : root;
    }

0

यहाँ यह करने का C ++ तरीका है। एल्गोरिथ्म को समझने के लिए यथासंभव आसान रखने की कोशिश की है:

// Assuming that `BinaryNode_t` has `getData()`, `getLeft()` and `getRight()`
class LowestCommonAncestor
{
  typedef char type;    
  // Data members which would behave as place holders
  const BinaryNode_t* m_pLCA;
  type m_Node1, m_Node2;

  static const unsigned int TOTAL_NODES = 2;

  // The core function which actually finds the LCA; It returns the number of nodes found
  // At any point of time if the number of nodes found are 2, then it updates the `m_pLCA` and once updated, we have found it!
  unsigned int Search (const BinaryNode_t* const pNode)
  {
    if(pNode == 0)
      return 0;

    unsigned int found = 0;

    found += (pNode->getData() == m_Node1);
    found += (pNode->getData() == m_Node2);

    found += Search(pNode->getLeft()); // below condition can be after this as well
    found += Search(pNode->getRight());

    if(found == TOTAL_NODES && m_pLCA == 0)
      m_pLCA = pNode;  // found !

    return found;
  }

public:
  // Interface method which will be called externally by the client
  const BinaryNode_t* Search (const BinaryNode_t* const pHead,
                              const type node1,
                              const type node2)
  {
    // Initialize the data members of the class
    m_Node1 = node1;
    m_Node2 = node2;
    m_pLCA = 0;

    // Find the LCA, populate to `m_pLCANode` and return
    (void) Search(pHead);
    return m_pLCA;
  }
};

इसे कैसे उपयोग करे:

LowestCommonAncestor lca;
BinaryNode_t* pNode = lca.Search(pWhateverBinaryTreeNodeToBeginWith);
if(pNode != 0)
  ...

0

निम्न एल्गोरिथम का उपयोग करने का सबसे आसान तरीका निम्न एल्गोरिदम है:

रूट नोड की जांच करें

अगर value1 और value2 सख्ती से कम हैं तो रूट नोड पर मान 
    बाएँ उपप्रकार की जाँच करें
अन्यथा अगर value1 और value2 सख्ती से अधिक है कि रूट नोड पर मान 
    सही उपशीर्षक की जाँच करें
अन्य
    वापसी रूट
public int LCA(TreeNode root, int value 1, int value 2) {
    while (root != null) {
       if (value1 < root.data && value2 < root.data)
           return LCA(root.left, value1, value2);
       else if (value2 > root.data && value2 2 root.data)
           return LCA(root.right, value1, value2);
       else
           return root
    }

    return null;
} 

6
यह एक BST नहीं है!
पीटर ली

0

मुझे एक उपाय मिला

  1. इनवर्टर लें
  2. प्रस्तावना लें
  3. पोस्टकार्ड लें

3 ट्रैवर्सल्स के आधार पर, आप यह तय कर सकते हैं कि एलसीए कौन है। LCA से दोनों नोड्स की दूरी ज्ञात करें। इन दो दूरियों को जोड़ें, जो उत्तर है।


0

यहाँ मुझे लगता है,

  1. मुट्ठी नोड के लिए मार्ग खोजें, इसे arr1 पर संग्रहीत करें।
  2. 2 नोड के लिए मार्ग ढूंढना शुरू करें, ऐसा करते समय रूट से arr1 तक हर मान की जांच करें।
  3. समय जब मूल्य भिन्न होता है, बाहर निकलें। पुराना मिलान मूल्य LCA है।

जटिलता: चरण 1: O (n), चरण 2 = ~ O (n), कुल = ~ O (n)।


0

यहाँ संदर्भ के लिए c # (.net) (ऊपर चर्चा की गई दोनों) में दो दृष्टिकोण हैं:

  1. द्विआधारी वृक्ष (O (N) में LCA खोजने का पुनरावर्ती संस्करण - जैसा कि प्रत्येक नोड पर देखा जाता है) (समाधान का मुख्य बिंदु LCA है (a) केवल बाइनरी ट्री में नोड है जहां दोनों तत्व उपप्रकार के दोनों ओर रहते हैं (बाएं और दायां) LCA है। (b) और यह भी मायने नहीं रखता कि कौन सा नोड दोनों तरफ मौजूद है - शुरू में मैंने उस जानकारी को रखने की कोशिश की, और जाहिर है कि पुनरावर्ती कार्य इतना भ्रामक हो गया। एक बार जब मुझे यह एहसास हुआ, तो यह बहुत ही सुंदर हो गया।

  2. दोनों नोड्स (O (N)) की खोज करना, और रास्तों पर नज़र रखना (अतिरिक्त स्थान का उपयोग करता है - इसलिए, # 1 शायद यह भी श्रेष्ठ है कि अंतरिक्ष शायद नगण्य है यदि बाइनरी ट्री अच्छी तरह से संतुलित है तो अतिरिक्त मेमोरी की खपत बस में होगी हे (लॉग (एन))।

    इसलिए कि रास्तों की तुलना की जाती है (स्पष्ट रूप से स्वीकृत उत्तर के समान है - लेकिन पाइंटर नोड मानकर पथों की गणना बाइनरी ट्री नोड में मौजूद नहीं है)

  3. बस पूरा करने के लिए ( प्रश्न से संबंधित नहीं ), BST में LCA (O (लॉग (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);
            }
        }

0

यदि कोई छद्म कोड (विश्वविद्यालय के घर के कामों के लिए) में रुचि रखता है तो यहाँ एक है।

GETLCA(BINARYTREE BT, NODE A, NODE  B)
IF Root==NIL
    return NIL
ENDIF

IF Root==A OR root==B
    return Root
ENDIF

Left = GETLCA (Root.Left, A, B)
Right = GETLCA (Root.Right, A, B)

IF Left! = NIL AND Right! = NIL
    return root
ELSEIF Left! = NIL
    Return Left
ELSE
    Return Right
ENDIF

0

हालाँकि इसका उत्तर पहले ही दिया जा चुका है, लेकिन C प्रोग्रामिंग लैंग्वेज का उपयोग करके इस समस्या के बारे में मेरा दृष्टिकोण है। यद्यपि कोड एक बाइनरी खोज ट्री दिखाता है (जहाँ तक सम्मिलित है) (संबंधित है), लेकिन एल्गोरिथ्म बाइनरी ट्री के लिए भी काम करता है। यह विचार सभी नोड्स पर जाने का है जो कि नोड ए से नोड बी तक इनवर्टर ट्रावेलर पर स्थित हैं, पोस्ट ऑर्डर ट्रैवर्सल में इन के लिए इंडेक्स को देखते हैं। पोस्ट ऑर्डर ट्रैवर्सल में अधिकतम इंडेक्स वाला नोड सबसे कम सामान्य पूर्वज है।

बाइनरी ट्री में सबसे कम सामान्य पूर्वजों को खोजने के लिए एक फ़ंक्शन को लागू करने के लिए यह एक कार्यशील सी कोड है। मैं सभी उपयोगिता फ़ंक्शंस आदि प्रदान कर रहा हूं, लेकिन त्वरित समझ के लिए कॉमनऑनस्टर () में जाएं।

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>

static inline int min (int a, int b)
{
    return ((a < b) ? a : b);
}
static inline int max (int a, int b)
{
    return ((a > b) ? a : b);
}

typedef struct node_ {
    int value;
    struct node_ * left;
    struct node_ * right;
} node;

#define MAX 12

int IN_ORDER[MAX] = {0};
int POST_ORDER[MAX] = {0};

createNode(int value) 
{
    node * temp_node = (node *)malloc(sizeof(node));
    temp_node->left = temp_node->right = NULL;
    temp_node->value = value;
    return temp_node;
}

node *
insert(node * root, int value)
{
    if (!root) {
        return createNode(value);
    }

    if (root->value > value) {
        root->left = insert(root->left, value);
    } else {
        root->right = insert(root->right, value);
    }

    return root;
}


/* Builds inorder traversal path in the IN array */
void
inorder(node * root, int * IN)
{
    static int i = 0;

    if (!root) return;

    inorder(root->left, IN);
    IN[i] = root->value;
    i++;
    inorder(root->right, IN);
}

/* Builds post traversal path in the POST array */

void
postorder (node * root, int * POST)
{
    static int i = 0;

    if (!root) return;

    postorder(root->left, POST);
    postorder(root->right, POST);
    POST[i] = root->value;
    i++;
}


int
findIndex(int * A, int value)
{
    int i = 0;
    for(i = 0; i< MAX; i++) {
        if(A[i] == value) return i;
    }
}
int
CommonAncestor(int val1, int val2)
{
    int in_val1, in_val2;
    int post_val1, post_val2;
    int j=0, i = 0; int max_index = -1;

    in_val1 = findIndex(IN_ORDER, val1);
    in_val2 = findIndex(IN_ORDER, val2);
    post_val1 = findIndex(POST_ORDER, val1);
    post_val2 = findIndex(POST_ORDER, val2);

    for (i = min(in_val1, in_val2); i<= max(in_val1, in_val2); i++) {
        for(j = 0; j < MAX; j++) {
            if (IN_ORDER[i] == POST_ORDER[j]) {
                if (j > max_index) {
                    max_index = j;
                }
            }
        }
    }
    printf("\ncommon ancestor of %d and %d is %d\n", val1, val2, POST_ORDER[max_index]);
    return max_index;
}
int main()
{
    node * root = NULL; 

    /* Build a tree with following values */
    //40, 20, 10, 30, 5, 15, 25, 35, 1, 80, 60, 100
    root = insert(root, 40);
    insert(root, 20);
    insert(root, 10);
    insert(root, 30);
    insert(root, 5);
    insert(root, 15);
    insert(root, 25);
    insert(root, 35);
    insert(root, 1);
    insert(root, 80);
    insert(root, 60);
    insert(root, 100);

    /* Get IN_ORDER traversal in the array */
    inorder(root, IN_ORDER);

    /* Get post order traversal in the array */
    postorder(root, POST_ORDER);

    CommonAncestor(1, 100);


}

0

एक और दृष्टिकोण हो सकता है। हालांकि यह उतना कुशल नहीं है जितना पहले से ही उत्तर में सुझाया गया।

  • नोड n1 के लिए एक पथ वेक्टर बनाएं।

  • नोड n2 के लिए दूसरा पथ वेक्टर बनाएं।

  • पथ वेक्टर सेट नोड्स से है कि एक प्रश्न में नोड तक पहुँचने के लिए होगा।

  • दोनों पथ वैक्टर की तुलना करें। जिस सूचकांक में वे बेमेल हैं, उस सूचकांक पर नोड लौटाएं - 1. यह एलसीए देगा।

इस दृष्टिकोण के लिए विपक्ष:

पथ वैक्टर की गणना के लिए पेड़ को दो बार पार करने की आवश्यकता है। पथ वैक्टर को संग्रहीत करने के लिए addtional O (h) स्थान की आवश्यकता होती है।

हालांकि इसे लागू करना और समझना भी आसान है।

पथ वेक्टर की गणना के लिए कोड:

private boolean findPathVector (TreeNode treeNode, int key, int pathVector[], int index) {

        if (treeNode == null) {
            return false;
        }

        pathVector [index++] = treeNode.getKey ();

        if (treeNode.getKey () == key) {
            return true;
        }
        if (findPathVector (treeNode.getLeftChild (), key, pathVector, index) || 
            findPathVector (treeNode.getRightChild(), key, pathVector, index)) {

            return true;        
        }

        pathVector [--index] = 0;
        return false;       
    }

0

ऐसे में कोशिश करें

node * lca(node * root, int v1,int v2)
{

if(!root) {
            return NULL;
    }
    if(root->data == v1 || root->data == v2) {
        return root;}
    else
    {
        if((v1 > root->data && v2 < root->data) || (v1 < root->data && v2 > root->data))
        {
            return root;
        }

        if(v1 < root->data && v2 < root->data)
        {
            root = lca(root->left, v1, v2);
        }

        if(v1 > root->data && v2 > root->data)
        {
            root = lca(root->right, v1, v2);
        }
    }
return root;
}

0

क्रूड तरीका:

  • हर नोड पर
    • X = खोजें यदि n1 में से कोई भी, n2 नोड के बाईं ओर मौजूद है
    • Y = पाते हैं कि क्या n1 में से कोई भी n2 दाईं ओर मौजूद है
      • अगर नोड स्वयं n1 है || n2, हम इसे सामान्यीकरण के प्रयोजनों के लिए बाईं या दाईं ओर पाए गए कॉल कर सकते हैं।
    • यदि X और Y दोनों सत्य हैं, तो Node CA है

उपरोक्त विधि के साथ समस्या यह है कि हम कई बार "खोज" कर रहे हैं, अर्थात प्रत्येक नोड के कई बार ट्रैवर्स प्राप्त होने की संभावना है। हम इस समस्या को दूर कर सकते हैं यदि हम जानकारी को रिकॉर्ड कर सकते हैं ताकि इसे फिर से संसाधित न करें (गतिशील प्रोग्रामिंग सोचें)।

इसलिए हर नोड को खोजने के बजाय, हम रिकॉर्ड करते हैं कि पहले से ही क्या पाया गया है।

बेहतर तरीका:

  • हम यह देखने के लिए जांचते हैं कि क्या दिए गए नोड के लिए अगर left_set (जिसका अर्थ n1 है। n2 बाएं सबट्री में पाया गया है) या right_set गहराई से पहले फैशन में। (नोट: हम रूट को स्वयं बचे होने का गुण दे रहे हैं यदि यह या तो n1 है। n2 | n2)
  • यदि बाएँ और बाएँ दोनों दाएँ हैं तो नोड LCA है।

कोड:

struct Node *
findCA(struct Node *root, struct Node *n1, struct Node *n2, int *set) {
   int left_set, right_set;
   left_set = right_set = 0;
   struct Node *leftCA, *rightCA;
   leftCA = rightCA = NULL;

   if (root == NULL) {
      return NULL;
   }
   if (root == n1 || root == n2) {
      left_set = 1;
      if (n1 == n2) {
         right_set = 1;
      }
   }

   if(!left_set) {
      leftCA = findCA(root->left, n1, n2, &left_set);
      if (leftCA) {
         return leftCA;
      }
   }
   if (!right_set) {
      rightCA= findCA(root->right, n1, n2, &right_set);
      if(rightCA) {
         return rightCA;
      }
   }

   if (left_set && right_set) {
      return root;
   } else {
      *set = (left_set || right_set);
      return NULL;
   }
}

0

एक चौड़ाई के लिए कोड यह सुनिश्चित करने के लिए कि पेड़ में दोनों नोड्स हैं। तभी LCA खोज के साथ आगे बढ़ें। यदि आपके पास सुधार करने के लिए कोई सुझाव है तो कृपया टिप्पणी करें। मुझे लगता है कि हम शायद उन्हें एक निश्चित बिंदु पर खोज को फिर से शुरू कर सकते हैं और खोज को फिर से शुरू कर सकते हैं जहां हमने दूसरे नोड के लिए सुधार करने के लिए छोड़ दिया है (यदि यह दृश्यमान नहीं है)

public class searchTree {
    static boolean v1=false,v2=false;
    public static boolean bfs(Treenode root, int value){
         if(root==null){
           return false;
     }
    Queue<Treenode> q1 = new LinkedList<Treenode>();

    q1.add(root);
    while(!q1.isEmpty())
    {
        Treenode temp = q1.peek();

        if(temp!=null) {
            q1.remove();
            if (temp.value == value) return true;
            if (temp.left != null) q1.add(temp.left);
            if (temp.right != null) q1.add(temp.right);
        }
    }
    return false;

}
public static Treenode lcaHelper(Treenode head, int x,int y){

    if(head==null){
        return null;
    }

    if(head.value == x || head.value ==y){
        if (head.value == y){
            v2 = true;
            return head;
        }
        else {
            v1 = true;
            return head;
        }
    }

    Treenode left = lcaHelper(head.left, x, y);
    Treenode right = lcaHelper(head.right,x,y);

    if(left!=null && right!=null){
        return head;
    }
    return (left!=null) ? left:right;
}

public static int lca(Treenode head, int h1, int h2) {
    v1 = bfs(head,h1);
    v2 = bfs(head,h2);
    if(v1 && v2){
        Treenode lca = lcaHelper(head,h1,h2);
        return lca.value;
    }
    return -1;
}
}

0

आप सही हैं कि जनक नोड के बिना, ट्रैवर्सल के साथ समाधान आपको ओ (एन) समय की जटिलता देगा।

ट्रैवर्सल दृष्टिकोण मान लीजिए कि आप नोड ए और बी के लिए एलसीए पा रहे हैं, सबसे सीधा तरीका यह है कि पहले रूट से ए तक का मार्ग प्राप्त करें और फिर रूट से बी तक का रास्ता प्राप्त करें। एक बार जब आपके पास ये दो रास्ते हो जाते हैं, तो आप आसानी से उन पर पुनरावृत्ति कर सकते हैं। और अंतिम आम नोड को खोजें, जो ए और बी का सबसे कम सामान्य पूर्वज है।

पुनरावर्ती समाधान पुनरावृत्ति का उपयोग करने के लिए एक और तरीका है। सबसे पहले, हम बाएं पेड़ और दाएं पेड़ (यदि मौजूद है) दोनों से एलसीए प्राप्त कर सकते हैं। यदि A या B में से कोई एक रूट नोड है, तो रूट LCA है और हम रूट को वापस करते हैं, जो कि रिकर्सन का अंतिम बिंदु है। जैसा कि हम पेड़ को उप-पेड़ों में विभाजित करते हैं, आखिरकार, हम ए और बी को मार देंगे।

उप-समस्या समाधानों को संयोजित करने के लिए, यदि LCA (बाएं पेड़) एक नोड लौटाता है, तो हम जानते हैं कि A और B दोनों बाएं पेड़ का पता लगाते हैं और लौटा नोड अंतिम परिणाम है। यदि LCA (बाएं) और LCA (दाएं) दोनों गैर-रिक्त नोड्स लौटाते हैं, तो इसका मतलब है कि ए और बी क्रमशः बाएं और दाएं पेड़ में हैं। इस स्थिति में, रूट नोड सबसे कम सामान्य नोड है।

विस्तृत विश्लेषण और समाधान के लिए सबसे कम सामान्य पूर्वज की जाँच करें ।


0

यहाँ कुछ समाधान मानते हैं कि रूट नोड का संदर्भ है, कुछ मानते हैं कि पेड़ एक BST है। हैशमैप का उपयोग करके मेरे समाधान को साझा करना, rootनोड और पेड़ के संदर्भ के बिना BST या गैर-BST हो सकता है:

    var leftParent : Node? = left
    var rightParent : Node? = right
    var map = [data : Node?]()

    while leftParent != nil {
        map[(leftParent?.data)!] = leftParent
        leftParent = leftParent?.parent
    }

    while rightParent != nil {
        if let common = map[(rightParent?.data)!] {
            return common
        }
        rightParent = rightParent?.parent
    }

0

समाधान 1: पुनरावर्ती - तेज़

  • जड़ से शुरू होने वाले पेड़ को पार करने का विचार है। यदि दी गई कोई भी कुंजी p और q रूट से मेल खाती है, तो रूट LCA है, यह मानते हुए कि दोनों कुंजी मौजूद हैं। यदि रूट किसी भी कुंजी के साथ मेल नहीं खाता है, तो हम बाएं और दाएं सबट्री के लिए पुनरावृत्ति करते हैं।
  • वह नोड जिसकी बाईं उपरी में एक कुंजी मौजूद होती है और दाएं सबट्री में मौजूद दूसरी कुंजी LCA होती है। यदि दोनों कुंजियाँ बाएं सबट्री में स्थित हैं, तो लेफ्ट सबट्री में LCA भी है, अन्यथा LCA राइट सबट्री में निहित है।
  • समय जटिलता: O (n)
  • अंतरिक्ष जटिलता: ओ (एच) - पुनरावर्ती कॉल स्टैक के लिए
class Solution 
{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    {
        if(root == null || root == p  || root == q)
            return root;

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if(left == null)
            return right;
        else if(right == null)
            return left;
        else
            return root;    // If(left != null && right != null)
    }
}

समाधान 2: Iterative - मूल बिंदुओं का उपयोग करना - धीमा

  • एक खाली हैश तालिका बनाएँ।
  • हैश तालिका में p और उसके सभी पूर्वजों को सम्मिलित करें।
  • जाँच करें कि क्या हैश तालिका में क्यू या उसके पूर्वजों में से कोई मौजूद है, यदि हाँ तो पहले मौजूदा पूर्वज को वापस लौटाएं।
  • समय जटिलता: O (n) - सबसे खराब स्थिति में हम बाइनरी ट्री के सभी नोड्स पर जा सकते हैं।
  • अंतरिक्ष जटिलता: O (n) - अंतरिक्ष ने माता-पिता सूचक हैश-टेबल, पूर्वज_सेट और कतार का उपयोग किया, प्रत्येक O (n) होगा।
class Solution
{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    {
        HashMap<TreeNode, TreeNode> parent_map = new HashMap<>();
        HashSet<TreeNode> ancestors_set = new HashSet<>();
        Queue<TreeNode> queue = new LinkedList<>();

        parent_map.put(root, null);
        queue.add(root);

        while(!parent_map.containsKey(p) || !parent_map.containsKey(q))
        {
            TreeNode node = queue.poll();

            if(node.left != null)
            {
                parent_map.put(node.left, node);
                queue.add(node.left);
            }
            if(node.right != null)
            {
                parent_map.put(node.right, node);
                queue.add(node.right);
            }
        }

        while(p != null)
        {
            ancestors_set.add(p);
            p = parent_map.get(p);
        }

        while(!ancestors_set.contains(q))
            q = parent_map.get(q);

        return q;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.