วิธีการค้นหาบรรพบุรุษร่วมที่ต่ำที่สุดของสองโหนดในต้นไม้ไบนารีใด ๆ ?


187

แผนผังไบนารีที่นี่อาจไม่จำเป็นต้องเป็นแผนผังการค้นหาแบบไบนารี
โครงสร้างสามารถนำมาเป็น -

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

วิธีแก้ปัญหาสูงสุดที่ฉันสามารถทำได้กับเพื่อนคืออะไรบางอย่างในประเภทนี้ -
พิจารณาต้นไม้ไบนารีนี้ :

ต้นไม้ไบนารี

อัตราการเคลื่อนที่ผ่านของ inorder - 8, 4, 9, 2, 5, 1, 6, 3, 7

และผลตอบแทนการสำรวจเส้นทาง postorder - 8, 9, 4, 5, 2, 6, 7, 3, 1

ตัวอย่างเช่นถ้าเราต้องการหาบรรพบุรุษร่วมของโหนด 8 และ 5 จากนั้นเราจะทำรายการโหนดทั้งหมดที่อยู่ระหว่าง 8 และ 5 ในการสำรวจต้นไม้ inorder ซึ่งในกรณีนี้เกิดขึ้นเป็น [4, 9 , 2]. จากนั้นเราตรวจสอบว่าโหนดใดในรายการนี้ปรากฏขึ้นครั้งสุดท้ายใน traversal postorder ซึ่งก็คือ 2 ดังนั้นบรรพบุรุษทั่วไปของ 8 และ 5 คือ 2

ความซับซ้อนของอัลกอริทึมนี้ฉันเชื่อว่า O (n) (O (n) สำหรับการสำรวจแบบ inorder / postorder ส่วนที่เหลือของขั้นตอนอีกครั้งเป็น O (n) เนื่องจากไม่มีอะไรมากไปกว่าการทำซ้ำง่ายๆในอาร์เรย์ แต่มีโอกาสที่ดีว่าสิ่งนี้ผิด :-)

แต่นี่เป็นวิธีที่หยาบคายมากและฉันไม่แน่ใจว่ามันจะหยุดลงหรือไม่ในบางกรณี มีวิธีอื่น (อาจจะดีกว่า) ในการแก้ปัญหานี้หรือไม่?


6
จากความอยากรู้การใช้สิ่งนี้ในทางปฏิบัติคืออะไร?
David Brunelle

19
@ David: การตอบแบบสอบถาม LCA นั้นมีประโยชน์ทีเดียว LCA + Suffix tree = อัลกอริธึมที่เกี่ยวข้องกับสตริงที่ทรงพลัง

44
และเมื่อฉันถามคำถามที่คล้ายกันมันได้รับการโหวตด้วยความคิดเห็นเช่นคำถามสัมภาษณ์ ความเป็นคู่ของ SO? :(
some_other_guy

5
@Siddant +1 สำหรับรายละเอียดที่ให้ไว้ในคำถาม :)
amod

5
@DavidBrunelle แอปพลิเคชั่นที่ใช้งานได้จริงในการคำนวณ LCA: เป็นการคำนวณที่สำคัญเมื่อแสดงผลหน้าเว็บโดยเฉพาะเมื่อคำนวณ Cascading Style Sheets (CSS) ที่ใช้กับองค์ประกอบ DOM เฉพาะ
zc22

คำตอบ:


74

นิคจอห์นสันถูกต้องว่าอัลกอริทึม O (n) ความซับซ้อนเวลาที่ดีที่สุดที่คุณสามารถทำได้ถ้าคุณไม่มีตัวชี้ผู้ปกครอง.) สำหรับรุ่น recursive ที่เรียบง่ายของอัลกอริทึมที่เห็นว่ารหัสในการโพสต์ Kinding ของที่ทำงานใน O (n) เวลา .

แต่โปรดจำไว้ว่าหากโหนดของคุณมีพอยน์เตอร์หลัก สำหรับทั้งสองโหนดในคำถามสร้างรายการที่มีเส้นทางจากรูตไปยังโหนดโดยเริ่มต้นที่โหนดและด้านหน้าแทรกพาเรนต์

ดังนั้นสำหรับ 8 ในตัวอย่างของคุณคุณจะได้รับ (แสดงขั้นตอน): {4}, {2, 4}, {1, 2, 4}

ทำเช่นเดียวกันกับโหนดอื่นที่เป็นปัญหาของคุณส่งผลให้ (ไม่ได้แสดงขั้นตอน): {1, 2}

ตอนนี้เปรียบเทียบสองรายการที่คุณค้นหาองค์ประกอบแรกที่รายการแตกต่างกันหรือองค์ประกอบสุดท้ายของรายการใดรายการหนึ่งแล้วแต่ว่าจะถึงอย่างใดก่อน

อัลกอริทึมนี้ต้องใช้เวลา O (h) โดยที่ h คือความสูงของต้นไม้ ในกรณีที่เลวร้ายที่สุด O (h) เทียบเท่ากับ O (n) แต่ถ้าต้นไม้มีความสมดุลนั่นเป็นเพียง O (log (n)) นอกจากนี้ยังต้องการพื้นที่ O (h) รุ่นปรับปรุงเป็นไปได้ที่ใช้เฉพาะพื้นที่คงที่ด้วยรหัสที่แสดงในการโพสต์ของ CEGRD


ไม่ว่าต้นไม้จะถูกสร้างขึ้นมาอย่างไรถ้านี่เป็นการดำเนินการที่คุณดำเนินการหลายครั้งบนต้นไม้โดยไม่มีการเปลี่ยนแปลงในระหว่างนั้นมีอัลกอริทึมอื่น ๆ ที่คุณสามารถใช้ที่ต้องใช้การเตรียมเวลา O (n) [เชิงเส้น] ทั้งคู่ใช้เวลาเพียง O (1) [คงที่] สำหรับการอ้างอิงถึงขั้นตอนวิธีการเหล่านี้ให้ดูหน้าปัญหาบรรพบุรุษร่วมกันต่ำสุดในวิกิพีเดีย (ให้เครดิตกับ Jason สำหรับการโพสต์ลิงค์นี้ แต่เดิม)


1
นั่นจะทำงานถ้าได้รับตัวชี้พาเรนต์ โหนดในต้นไม้เป็นโครงสร้างที่ฉันให้ไว้ในคำถามของฉัน - เพียงแค่ตัวชี้เด็กซ้าย / ขวาไม่มีตัวชี้ผู้ปกครอง มีวิธีการแก้ปัญหา O (log (n)) หรือไม่ถ้าไม่มีตัวชี้พาเรนต์และทรีนั้นไม่ใช่ทรีการค้นหาแบบไบนารีและเป็นเพียงทรีไบนารี
Siddhant

2
หากคุณไม่มีวิธีใดในการค้นหาพา ธ ระหว่างพาเรนต์และโหนดที่ระบุจะใช้เวลาโดยเฉลี่ย O (n) ในการค้นหา ที่จะทำให้เป็นไปไม่ได้ที่จะมีเวลา O (บันทึก (n)) อย่างไรก็ตามค่าใช้จ่าย O (n) เพียงครั้งเดียวการหาคู่ O (1) อาจเป็นทางออกที่ดีที่สุดของคุณอย่างไรก็ตามถ้าคุณจะทำการดำเนินการนี้หลายครั้งโดยไม่ต้องเปลี่ยนต้นไม้ในระหว่าง มิฉะนั้นหากเป็นไปได้คุณควรเพิ่มตัวชี้พาเรนต์ มันสามารถทำให้อัลกอริธึมที่เป็นไปได้ค่อนข้างเร็วขึ้น แต่ฉันค่อนข้างแน่ใจว่าจะไม่เปลี่ยนลำดับของอัลกอริทึมที่มีอยู่ หวังว่านี่จะช่วยได้
Kevin Cathcart

1
วิธีนี้สามารถทำได้โดยใช้หน่วยความจำ O (1) - ดูโซลูชัน (และอื่น ๆ ) ของ Artelius ที่stackoverflow.com/questions/1594061/…
Tom Sirgedas

@ ทอม: แน่นอนว่ามันจะทำงานเพื่อจำกัดความซับซ้อนของหน่วยความจำไว้ที่ O (1) สำหรับอัลกอริทึมที่ใช้ลิสต์ เห็นได้ชัดว่านั่นหมายถึงการวนซ้ำต้นไม้ในแต่ละครั้งเพื่อให้ได้ความลึกของโหนดและจากนั้นเป็นครั้งที่สอง (บางส่วน) เพื่อค้นหาบรรพบุรุษร่วมกัน เวลา O (h) และพื้นที่ O (1) เหมาะสมที่สุดสำหรับกรณีที่มีพอยน์เตอร์พอยน์เตอร์และไม่ทำ O (n) การคำนวณล่วงหน้า
Kevin Cathcart

1
@ALBI O(h)เป็นเพียงO(log(n))ถ้าต้นไม้มีความสมดุล สำหรับต้นไม้ใด ๆ ไม่ว่าจะเป็นไบนารีหรือไม่ถ้าคุณมีคำแนะนำผู้ปกครองคุณสามารถกำหนดเส้นทางจากใบรากในO(h)เวลาเพียงโดยต่อไปนี้ชี้ผู้ปกครองถึงhครั้ง ที่ให้เส้นทางจากใบถึงราก หากพา ธ ถูกเก็บเป็นสแต็กการทำซ้ำสแต็กจะให้พา ธ จากรูทถึงลีฟ หากคุณขาดพอยน์เตอร์พอยน์เตอร์และไม่มีโครงสร้างพิเศษให้กับต้นไม้การค้นหาเส้นทางจากรูตถึงลีฟนั้นต้องใช้O(n)เวลา
Kevin Cathcart

108

เริ่มต้นจากrootโหนดและย้ายลงหากคุณพบโหนดใด ๆ ที่มีpหรือqเป็นลูกโดยตรงของมันแล้วมันเป็น LCA (แก้ไข - นี่ควรเป็นถ้า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
ทางออกที่หรูหรา แต่ราก == p || root == q => บิตกลับมาดูเหมือนว่ามีมากเกินไป จะเกิดอะไรขึ้นถ้ามันกลายเป็นรูตคือ p / q, แต่โหนดที่ค้นหาอีกอันนั้นไม่ได้อยู่ในทรี?
Ian Durkan

15
ฉันเดารหัสนี้ล้มเหลวเมื่อ p หรือ q เป็นค่าที่ไม่ได้อยู่ในต้นไม้ไบนารี ฉันถูกไหม? เช่น LCA (8,20) โค้ดของ
url

3
ค่าใช้จ่ายสำหรับโซลูชันนี้คืออะไร มันมีประสิทธิภาพหรือไม่ ดูเหมือนว่าจะค้นหาต่อไปแม้ว่าจะพบทั้ง p และ q นั่นเป็นเพราะความเป็นไปได้ที่ p และ q อาจไม่ซ้ำกันในต้นไม้เนื่องจากไม่ใช่ BST และอาจมีการซ้ำซ้อน?
MikeB

3
@ MikeB ทางออกนี้แน่นอน O (n) เพราะคุณสำรวจแต่ละโหนดเพียงครั้งเดียวในกรณีที่เลวร้ายที่สุด Peter Lee นี่คือประสิทธิภาพสูงสุดที่คุณสามารถทำได้โดยไม่ต้องใช้พอยน์เตอร์พอยน์เตอร์ คุณมีทางออกที่ดีกว่าไหม?
gsingh2011

8
ควรลบโซลูชันที่ไม่สมบูรณ์ครั้งแรกเพื่อไม่ให้เสียสมาธิ
Zinan Xing

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
สิ่งนี้ไม่ทำงานเมื่อไม่มีโหนดในทรี
Pratik Khadloya

คุณจะเพิ่มประสิทธิภาพรหัสของคุณถ้าต้นไม้ที่กำหนดเป็น BST?
Mona Jalal

1
"ถ้ารูทเป็นหนึ่งหรือ a ก็แสดงว่าเป็น LCA" สิ่งนี้อาจไม่เป็นจริง สิ่งที่คุณรู้ ณ จุดนี้คือคุณไม่จำเป็นต้องตรวจสอบลูก ๆ ของมันเพื่อค้นหา LCA สิ่งนี้เกิดขึ้นเพราะเราสามารถตรวจสอบได้ในภายหลังว่าสำหรับรากของรากมีการจับคู่ทั้งสองสาขา (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)
John Kurlak

มีข้อสมมติฐานที่ละเอียดอ่อนในโซลูชันนี้ หากโหนดหนึ่งเป็นพาเรนต์โดยตรงหรือโดยอ้อมของโหนดอื่น (เช่นโหนดที่อยู่ลึกกว่านั้นอยู่ในทรีที่รูทที่โหนดตื้นกว่า) วิธีนี้จะส่งคืนพาเรนต์ของโหนดตื้นกว่าที่เป็นผลลัพธ์ ทั้งนี้ขึ้นอยู่กับวิธีที่คุณกำหนดบรรพบุรุษร่วมที่ต่ำที่สุดนี่อาจไม่ใช่สิ่งที่คุณต้องการ คำจำกัดความบางอย่างจะต้องมีโหนดที่ตื้นกว่านั้นเองจึงจะเป็นตัวหลัก ในกรณีนี้คุณจะต้องติดตามว่าโหนดใดที่ตื้นกว่าและส่งคืนนั้น
Srikanth

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 ไม่มีอยู่ในต้นไม้? ในทำนองเดียวกันทั้ง p และ q ไม่มีอยู่ ขอบคุณ !!!
ลองใช้

O ใหญ่ในแง่ของเวลาคืออะไร? ฉันคิดว่ามันเป็น O (n * log (n)) สองช้า
ปีเตอร์ลี


6

เพื่อหาบรรพบุรุษร่วมกันของสองโหนด: -

  • ค้นหาโหนดที่ระบุ Node1 ในแผนผังโดยใช้การค้นหาแบบไบนารีและบันทึกโหนดทั้งหมดที่เข้าชมในกระบวนการนี้ในอาร์เรย์ที่บอกว่า A1 เวลา - O (logn), Space - O (logn)
  • ค้นหา Node2 ที่กำหนดในแผนผังโดยใช้การค้นหาแบบไบนารีและบันทึกโหนดทั้งหมดที่เข้าชมในกระบวนการนี้ในอาร์เรย์ที่เรียกว่า A2 เวลา - O (logn), Space - O (logn)
  • หากรายการ A1 หรือรายการ A2 ว่างเปล่าหนึ่งโหนดจะไม่มีอยู่ดังนั้นจึงไม่มีบรรพบุรุษที่พบบ่อย
  • หากรายการ A1 และรายการ A2 ไม่ว่างเปล่าให้ดูในรายการจนกว่าคุณจะพบโหนดที่ไม่ตรงกัน ทันทีที่คุณพบโหนดดังกล่าวแล้วโหนดก่อนหน้านั้นเป็นบรรพบุรุษร่วมกัน

สิ่งนี้จะทำงานกับแผนภูมิการค้นหาแบบไบนารี


2
เขาระบุชัดเจนว่าต้นไม้ไม่จำเป็นต้องเป็น BST
Peter Lee

@ Peter Lee - ตรรกะข้างต้นจะใช้ได้แม้กับไบนารีต้นไม้ใด ๆ ที่มีการเปลี่ยนแปลงอย่างง่าย แทนการค้นหาแบบไบนารีของโหนดที่กำหนดให้ใช้การค้นหาเชิงเส้น (เช่นการแวะผ่านใด ๆ แต่ควรเหมือนกันสำหรับทั้งสองกรณี) รันไทม์ของหลักสูตรนอกหลักสูตรจะเป็น O (n) แทน O (logn) ในความเป็นจริงอัลโกนี้มีประสิทธิภาพมากที่สุดเมื่อตัวชี้พาเรนต์ไม่พร้อมใช้งาน อัลกอริทึม rucursive ที่ได้รับจากหลาย ๆ (viz. 'codaddict') จะไม่ทำงานเมื่อหนึ่งในโหนดที่กำหนดไม่ได้เป็นของต้นไม้)
KGhatak


3

อัลกอริทึมแบบเรียกซ้ำด้านล่างจะทำงานใน O (บันทึก N) สำหรับต้นไม้ไบนารีที่สมดุล หากโหนดใดโหนดหนึ่งที่ส่งผ่านไปยังฟังก์ชัน getLCA () เหมือนกันกับรูทดังนั้นรูตจะเป็น LCA และไม่จำเป็นต้องทำการคำนวณซ้ำอีก

กรณีทดสอบ [1]ทั้งสองโหนด n1 & n2 อยู่ในแผนผังและอยู่ในทั้งสองด้านของโหนดหลัก [2]โหนด n1 หรือ n2 เป็นรูต LCA คือรูท [3]มีเพียง n1 หรือ n2 เท่านั้นที่อยู่ในทรี LCA จะเป็นโหนดรูทของทรีย่อยด้านซ้ายของทรีรากหรือ LCA จะเป็นโหนดรูทของทรีย่อยด้านขวาของทรีราก

[4]ไม่มี n1 หรือ n2 อยู่ในต้นไม้ไม่มี LCA [5]ทั้ง n1 และ n2 อยู่ในแนวเส้นตรงติดกัน LCA จะเป็น n1 หรือ n2 ซึ่งเคยอยู่ใกล้กับรากของต้นไม้

//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ซึ่งบรรพบุรุษจะต้องพบอยู่ในต้นไม้ย่อยเดียวกัน (หมายถึงค่าของพวกเขาทั้งเล็กและใหญ่กว่าราก)

สิ่งนี้เดินตรงจากรากไปยังบรรพบุรุษร่วมน้อยที่สุดไม่มองต้นไม้ที่เหลือดังนั้นมันจึงเร็วพอ ๆ กับที่ได้รับ สองสามวิธีที่จะทำ

ซ้ำพื้นที่ 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) q.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

พิจารณาต้นไม้ต้นนี้ ป้อนคำอธิบายรูปภาพที่นี่

ถ้าเราทำ postorder และ preorder traversal และค้นหาสิ่งแรกที่เกิดขึ้นร่วมกันก่อนและผู้สืบทอดเราจะได้รับบรรพบุรุษร่วมกัน

postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 preorder => 7,3,1,0,2,6,4 , 5,12,9,8,11,10,13,15,14

  • เช่น 1

บรรพบุรุษร่วมน้อยที่สุด 8,11 คน

ใน postorder เรามี => 9,14,15,13,12,7 หลังจาก 8 และ 11 ใน preorder เรามี => 7,3,1,0,2,6,4,5,12,9 ก่อน 8 และ 11

9 เป็นหมายเลขทั่วไปแรกที่เกิดขึ้นหลังจาก 8 & 11 ใน postorder และก่อน 8 & 11 ใน preorder ดังนั้น 9 คือคำตอบ

  • เช่น: 2

บรรพบุรุษร่วมน้อยที่สุด 5,10 คน

11,9,14,15,13,12,7 ในคำสั่งซื้อ 7,3,1,0,2,6,4 ในการสั่งซื้อล่วงหน้า

7 คือหมายเลขแรกที่เกิดขึ้นหลังจาก 5,10 ใน postorder และก่อนที่ 5,10 ใน preorder ดังนั้น 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. รับบิตที่ต้องการเพื่อแทนค่า x & y ที่ใช้การค้นหาแบบไบนารี่คือ O (บันทึก (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!
Peter Lee

0

ฉันพบวิธีแก้ปัญหา

  1. ใช้เวลา inorder
  2. สั่งจองล่วงหน้า
  3. ใช้เวลา postorder

คุณสามารถตัดสินใจได้ว่าใครคือ LCA ทั้งนี้ขึ้นอยู่กับ 3 เส้นทาง จาก LCA หาระยะทางของทั้งสองโหนด เพิ่มสองระยะทางซึ่งเป็นคำตอบ


0

นี่คือสิ่งที่ฉันคิด

  1. ค้นหาเส้นทางสำหรับโหนดกำปั้นเก็บไว้ใน arr1
  2. เริ่มค้นหาเส้นทางสำหรับ 2 โหนดในขณะที่ทำเช่นนั้นให้ตรวจสอบทุกค่าจากรูทถึง arr1
  3. เวลาที่ค่าแตกต่างออกจาก ค่าที่ตรงกันเก่าคือ LCA

ความซับซ้อน: ขั้นตอนที่ 1: O (n), ขั้นตอนที่ 2 = ~ O (n), รวม = ~ O (n)


0

นี่คือสองวิธีใน c # (.net) (ทั้งสองที่กล่าวถึงข้างต้น) สำหรับการอ้างอิง:

  1. เวอร์ชันเรียกซ้ำของการค้นหา LCA ในไบนารีทรี (O (N) - เป็นอย่างมากที่แต่ละโหนดถูกเยี่ยมชม) (ประเด็นหลักของการแก้ปัญหาคือ LCA คือ(a)โหนดเดียวในไบนารีต้นไม้ที่องค์ประกอบทั้งสองอยู่ด้านข้างของ subtrees (ซ้าย) และขวา) คือ LCA (b)และมันก็ไม่สำคัญว่าโหนดใดที่อยู่ข้างใดข้างหนึ่ง - เริ่มแรกฉันพยายามเก็บข้อมูลนั้นและเห็นได้ชัดว่าฟังก์ชั่นวนซ้ำกลายเป็นสับสนมากเมื่อฉันรู้ว่ามันกลายเป็นสง่ามาก

  2. การค้นหาทั้งสองโหนด (O (N)) และการติดตามเส้นทาง (ใช้พื้นที่พิเศษ - ดังนั้น # 1 น่าจะดีกว่าแม้จะคิดว่าพื้นที่นั้นอาจไม่สำคัญถ้าต้นไม้ไบนารีมีความสมดุลเพราะการใช้หน่วยความจำเพิ่มเติมจะอยู่ใน O (เข้าสู่ระบบ (N))

    เพื่อให้มีการเปรียบเทียบเส้นทาง (essentailly คล้ายกับคำตอบที่ยอมรับ - แต่เส้นทางจะถูกคำนวณโดยสมมติว่าตัวชี้โหนดไม่ปรากฏในโหนดต้นไม้ไบนารี)

  3. เพื่อความสมบูรณ์ ( ไม่เกี่ยวข้องกับคำถาม ) LCA ใน BST (O (บันทึก (N))

  4. การทดสอบ

recursive:

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 แม้ว่ารหัสจะแสดงแผนภูมิการค้นหาแบบไบนารี (เท่าที่เกี่ยวข้องกับการแทรก ()) แต่อัลกอริทึมก็ใช้งานได้กับต้นไม้แบบไบนารีด้วยเช่นกัน แนวคิดคือการข้ามโหนดทั้งหมดที่อยู่จากโหนด A ถึงโหนด B ในการสำรวจเส้นทาง inorder ค้นหาดัชนีสำหรับสิ่งเหล่านี้ในการสำรวจเส้นทางโพสต์ โหนดที่มีดัชนีสูงสุดในการสำรวจเส้นทางการโพสต์เป็นบรรพบุรุษที่ต่ำที่สุด

นี่คือรหัส C ที่ใช้งานได้เพื่อนำไปใช้กับฟังก์ชั่นเพื่อค้นหาบรรพบุรุษที่พบได้น้อยที่สุดในต้นไม้ไบนารี ฉันกำลังจัดหาฟังก์ชั่นยูทิลิตี้อื่น ๆ ทั้งหมดเช่นกัน แต่ข้ามไปยัง CommonAncestor () เพื่อความเข้าใจที่รวดเร็ว

#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

  • Path vector หมายถึงโหนดที่ตั้งค่าจากที่หนึ่งจะข้ามไปถึงโหนดที่มีปัญหา

  • เปรียบเทียบเวกเตอร์เส้นทางทั้งสอง ดัชนีที่พวกเขาไม่ตรงกันส่งคืนโหนดที่ดัชนีนั้น - 1 ซึ่งจะให้ LCA

ข้อเสียสำหรับวิธีการนี้:

จำเป็นต้องสำรวจต้นไม้สองครั้งเพื่อคำนวณเวกเตอร์เส้นทาง ต้องการพื้นที่เพิ่มเติม 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 ในโหมดแรกที่มีความลึก (หมายเหตุ: เรากำลังให้รูทคุณสมบัติของการเป็น left_set ถ้ามันเป็น n1 | n2)
  • หากทั้ง left_set และ right_set แสดงว่าโหนดนั้นเป็น 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

คุณถูกต้องที่ไม่มีโหนดพาเรนต์โซลูชันที่มีการแวะผ่านจะให้ความซับซ้อนของเวลา O (n) กับคุณ

วิธีการ สำรวจเส้นทางสมมติว่าคุณกำลังค้นหา LCA สำหรับโหนด A และ B วิธีที่ตรงไปตรงมาที่สุดคือการหาเส้นทางจากรูทถึง A และจากนั้นรับพา ธ จากรูทถึงบีเมื่อคุณมีสองพา ธ เหล่านี้คุณสามารถวนซ้ำได้อย่างง่ายดาย และค้นหาโหนดทั่วไปสุดท้ายซึ่งเป็นบรรพบุรุษร่วมที่ต่ำที่สุดของ A และ B

โซลูชันแบบเรียกซ้ำ อีกวิธีหนึ่งคือใช้การเรียกซ้ำ อันดับแรกเราสามารถรับ LCA จากทั้งต้นไม้ด้านซ้ายและต้นไม้ด้านขวา (ถ้ามี) ถ้า A หรือ B ใด ๆ เป็นรูทโหนดดังนั้นรูตคือ LCA และเราเพิ่งส่งคืนรูทซึ่งเป็นจุดสิ้นสุดของการเรียกซ้ำ เมื่อเราแบ่งต้นไม้ออกเป็นต้นไม้ย่อยในที่สุดเราจะกด A และ B

ในการรวมวิธีแก้ปัญหาย่อยถ้า LCA (ต้นไม้ด้านซ้าย) คืนค่าโหนดเรารู้ว่าทั้ง A และ B ค้นหาในต้นไม้ด้านซ้ายและโหนดที่ส่งคืนเป็นผลลัพธ์สุดท้าย หากทั้ง LCA (ซ้าย) และ LCA (ขวา) ส่งคืนโหนดที่ไม่ว่างนั่นหมายความว่า A และ B อยู่ในต้นไม้ด้านซ้ายและขวาตามลำดับ ในกรณีนี้โหนดรูทเป็นโหนดทั่วไปที่ต่ำที่สุด

ตรวจสอบบรรพบุรุษสามัญที่ต่ำที่สุดสำหรับการวิเคราะห์และการแก้ปัญหาโดยละเอียด


0

วิธีแก้ปัญหาบางอย่างในที่นี้สันนิษฐานว่ามีการอ้างอิงถึงโหนดรูทบางรายถือว่าทรีเป็น BST การแบ่งปันโซลูชันของฉันโดยใช้ hashmap โดยไม่มีการอ้างอิงถึง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)
  • Space Complexity: O (h) - สำหรับการโทรซ้ำแบบเรียกซ้ำ
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: การวนซ้ำ - การใช้ตัวชี้พาเรนต์ - ช้ากว่า

  • สร้างตารางแฮชว่าง
  • แทรก p และบรรพบุรุษทั้งหมดในตารางแฮช
  • ตรวจสอบว่า q หรือบรรพบุรุษใด ๆ ของมันมีอยู่ในตารางแฮชหรือไม่ถ้าใช่แล้วส่งคืนบรรพบุรุษที่มีอยู่แรก
  • ความซับซ้อนของเวลา: O (n) - ในกรณีที่เลวร้ายที่สุดเราอาจจะไปที่โหนดทั้งหมดของต้นไม้ไบนารี
  • ความซับซ้อนของพื้นที่: O (n) - พื้นที่ที่ใช้ตัวชี้พาเรนต์ของแฮชตาราง, ancestor_set และคิวจะเป็น 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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.