เส้นทางที่ยาวที่สุดในต้นไม้ที่ไม่ได้ถูกส่งทิศทางด้วยการสำรวจเส้นทางเดียว


44

มีอัลกอริธึมมาตรฐานนี้สำหรับการค้นหาเส้นทางที่ยาวที่สุดในต้นไม้ที่ไม่ได้ใช้เส้นทางโดยใช้การค้นหาสองครั้งแรก:

  • เริ่ม DFS จากจุดสุดยอดแบบสุ่มและหาจุดสุดยอดที่ไกลที่สุดจากมัน บอกว่ามันเป็นวี 'vv
  • ตอนนี้เริ่ม DFS จากเพื่อค้นหาจุดสุดยอดจากนั้น เส้นทางนี้เป็นเส้นทางที่ยาวที่สุดในกราฟv

คำถามคือสิ่งนี้สามารถทำได้อย่างมีประสิทธิภาพมากขึ้น? เราสามารถทำได้ด้วย DFS หรือ BFS เดียวได้หรือไม่?

(สิ่งนี้สามารถอธิบายได้อย่างเท่าเทียมกันว่าเป็นปัญหาของการคำนวณเส้นผ่านศูนย์กลางของต้นไม้ที่ไม่ได้บอกทิศทาง)


2
สิ่งที่คุณเป็นหลังจากนั้นก็เรียกว่าเส้นผ่าศูนย์กลางของต้นไม้ (บนต้นไม้ "เส้นทางที่สั้นที่สุดที่ยาวที่สุด" และ "เส้นทางที่ยาวที่สุด" เป็นสิ่งเดียวกันเนื่องจากมีเพียงหนึ่งเส้นทางที่เชื่อมต่อสองโหนดใด ๆ )
Raphael

คำตอบ:


22

เราทำการค้นหาในเชิงลึกครั้งแรกตามลำดับการโพสต์และผลลัพธ์โดยรวมนั่นคือเราแก้ปัญหาซ้ำ ๆ

สำหรับทุกโหนดมี children u 1 , , u k (ในแผนผังการค้นหา) มีสองกรณี:vu1,,uk

  • เส้นทางที่ยาวที่สุดในโกหกในหนึ่งใน subtrees T U 1 , ... , T U kTvTu1,,Tuk
  • เส้นทางที่ยาวที่สุดในมีโวลต์Tvv

vH(k)+H(k1)+2k>1H(k)+1k=1H={h(Tui)i=1,,k}

ในโค้ดหลอกอัลกอริทึมจะมีลักษณะดังนี้:

procedure longestPathLength(T : Tree) = helper(T)[2]

/* Recursive helper function that returns (h,p)
 * where h is the height of T and p the length
 * of the longest path of T (its diameter) */
procedure helper(T : Tree) : (int, int) = {
  if ( T.children.isEmpty ) {
    return (0,0)
  }
  else {
    // Calculate heights and longest path lengths of children
    recursive = T.children.map { c => helper(c) }
    heights = recursive.map { p => p[1] }
    paths = recursive.map { p => p[2] }

    // Find the two largest subtree heights
    height1 = heights.max
    if (heights.length == 1) {
      height2 = -1
    } else {
      height2 = (heights.remove(height1)).max
    }

    // Determine length of longest path (see above)        
    longest = max(paths.max, height1 + height2 + 2)

    return (height1 + 1, longest)
  }
}

  1. A(k)kA

@JeffE เกี่ยวกับความคิดเห็นที่สอง: อันที่จริงและสิ่งนี้ได้รับการดูแลในแถวสุดท้าย: height1 + height2คือความยาวของเส้นทางนี้ maxถ้ามันเป็นความจริงเส้นทางที่ยาวที่สุดก็ถูกเลือกโดย มันได้อธิบายไว้ในข้อความข้างต้นด้วยดังนั้นฉันจึงไม่เห็นปัญหาของคุณบ้าง? แน่นอนคุณต้องรับเงินคืนเพื่อค้นหาว่ามันเป็นเส้นทางที่ยาวที่สุดจริงหรือไม่และแม้ว่ามันจะไม่เจ็บ (ความถูกต้อง WRT) เพื่อชดเชย
Raphael

@JeffE เกี่ยวกับความคิดเห็นแรกการคำนวณเพื่อheight2ลบออกheight1จากการพิจารณาอย่างชัดเจนดังนั้นจะเลือกเด็กคนเดียวกันได้อย่างไรสองครั้ง ที่ได้รับการอธิบายในข้อความเบื้องต้น
กราฟิลส์

1
เห็นได้ชัดว่าเราพูดภาษาปลอมต่าง ๆ เพราะฉันมีเวลายากที่จะเข้าใจคุณ มันจะช่วยในการเพิ่มการประกาศภาษาอังกฤษอย่างชัดเจนว่าlongestPathHeight(T)ผลตอบแทนที่ได้คู่(h,d)ที่hเป็นความสูงของTและเส้นผ่าศูนย์กลางของd T(ใช่มั้ย)
JeffE

@JeffE ถูกต้อง; ฉันคิดว่ามันชัดเจนจากรหัสให้คำอธิบาย แต่เห็นได้ชัดว่าการคาดการณ์ของฉัน "ชัดเจน" สำหรับกระบวนทัศน์ pseudocode อื่น ๆ ไม่เพียงพอ (ฉันเดาว่าเป็น Scalaesque ฉันเดา) ขออภัยในความสับสนฉันกำลังชี้แจงรหัส (หวังว่า)
ราฟาเอล

8

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

นี่คือบทสรุปของสิ่งที่ฉันต้องการอธิบายในบล็อกโพสต์ของฉัน :

วิธีการแบบเรียกซ้ำ - เส้นผ่าศูนย์กลางต้นไม้ อีกวิธีในการเข้าถึงปัญหานี้มีดังนี้ ดังที่เราได้กล่าวไปแล้วว่าเส้นผ่านศูนย์กลางสามารถ

  1. อยู่ในต้นไม้ย่อยด้านซ้ายหรือ
  2. อยู่ในต้นไม้ย่อยที่ถูกต้องหรือ
  3. อาจแผ่ขยายไปทั่วรูต

ซึ่งหมายความว่าเส้นผ่านศูนย์กลางสามารถได้มาโดยอุดมคติ

  1. เส้นผ่าศูนย์กลางของต้นไม้ด้านซ้ายหรือ
  2. เส้นผ่าศูนย์กลางของต้นไม้ที่เหมาะสมหรือ
  3. ความสูงของทรีย่อยด้านซ้าย + ความสูงของทรีย่อยด้านขวา + 1 (1 เพื่อเพิ่มโหนดรูทเมื่อเส้นผ่าศูนย์กลางขยายผ่านโหนดรูท)

และเรารู้ว่าเส้นผ่านศูนย์กลางเป็นเส้นทางที่ยาวที่สุดดังนั้นเราจึงใช้สูงสุด 1 และ 2 ในกรณีที่มันอยู่ข้างใดข้างหนึ่งหรือใช้เวลา 3 ถ้ามันแผ่ผ่านรูท

วิธีการทำซ้ำ - เส้นผ่าศูนย์กลางต้นไม้

เรามีแผนผังเราต้องการข้อมูลเมตากับแต่ละโหนดเพื่อให้แต่ละโหนดรู้ดังต่อไปนี้:

  1. ความสูงของลูกซ้าย
  2. ความสูงของลูกที่เหมาะสมและ
  3. ระยะทางที่ไกลที่สุดระหว่างโหนดใบไม้

เมื่อแต่ละโหนดมีข้อมูลนี้เราต้องการตัวแปรชั่วคราวเพื่อติดตามเส้นทางสูงสุด เมื่อเวลาที่อัลกอริทึมเสร็จสิ้นเรามีค่าเส้นผ่าศูนย์กลางในตัวแปร temp

ตอนนี้เราต้องแก้ปัญหานี้ด้วยวิธีการจากล่างขึ้นบนเนื่องจากเราไม่มีความคิดเกี่ยวกับค่าสามค่าสำหรับรูท แต่เรารู้ค่าเหล่านี้สำหรับใบไม้

ขั้นตอนในการแก้ไข

  1. เริ่มต้นใบทั้งหมดด้วยซ้ายความสูงและขวาความสูงเป็น 1
  2. เริ่มต้นใบทั้งหมดด้วย maxDistance เป็น 0 เราทำให้มันเป็นจุดที่ถ้าซ้ายใดสูงหรือขวาสูงคือ 1 เราทำให้ maxDistance = 0
  3. เลื่อนขึ้นหนึ่งครั้งและคำนวณค่าสำหรับพาเรนต์ทันที มันจะง่ายเพราะตอนนี้เรารู้ค่าเหล่านี้สำหรับเด็กแล้ว
  4. ที่โหนดที่ระบุ

    • กำหนด leftHeight เป็นค่าสูงสุดของ (leftHeight หรือ rightHeight ของลูกซ้ายของมัน)
    • กำหนด rightHeight เป็นสูงสุดของ (leftHeight หรือ rightHeight ของลูกขวาของ)
    • ถ้าค่าใด ๆ เหล่านี้ (leftHeight หรือ rightHeight) เป็น 1 ให้ maxDistance เป็นศูนย์
    • ถ้าทั้งสองค่ามากกว่าศูนย์ให้ maxDistance เป็น leftHeight + rightHeight - 1
  5. รักษา maxDistance ในตัวแปร temp และถ้าในขั้นตอนที่ 4 maxDistance นั้นมากกว่าค่าปัจจุบันของตัวแปรให้แทนที่ด้วยค่า maxDistance ใหม่
  6. ในตอนท้ายของอัลกอริทึมค่าใน maxDistance คือเส้นผ่านศูนย์กลาง

1
สิ่งนี้เพิ่มอะไรในคำตอบเดิมของฉันนอกเหนือจากการเป็นคนทั่วไปน้อยกว่า (คุณจัดการกับต้นไม้ไบนารี)?
Raphael

9
คำตอบนี้อ่านง่ายขึ้นและเข้าใจง่ายขึ้นในความเห็นของฉัน (รหัสเทียมของคุณสับสนมาก)
reggaeguitar

-3

ด้านล่างคือรหัสที่ส่งคืนเส้นผ่าศูนย์กลางเส้นทางโดยใช้การแวะผ่าน DFS เพียงครั้งเดียว มันต้องใช้พื้นที่เพิ่มเติมเพื่อติดตามเส้นผ่านศูนย์กลางที่ดีที่สุดเท่าที่เห็นจนถึงเส้นทางที่ยาวที่สุดที่เริ่มต้นที่โหนดใดโหนดหนึ่งในต้นไม้ นี่เป็นวิธีการเขียนโปรแกรมแบบไดนามิกโดยยึดตามความจริงที่ว่าเส้นผ่านศูนย์กลางที่ยาวที่สุดนั้นไม่รวมถึงรูทหรือเป็นการรวมกันของสองเส้นทางที่ยาวที่สุดของเพื่อนบ้านของรูท ดังนั้นเราต้องการเวกเตอร์สองตัวเพื่อติดตามข้อมูลนี้

 int getDiam(int root, vector<vector<int>>& adj_list, int& height, vector<int>& path, vector<int>& diam) {
    visited[root] = true;
    int m1 = -1;
    int m2 = -1;
    int max_diam = -1;
    vector<int> best1 = vector<int>();
    vector<int> best2 = vector<int>();
    vector<int> diam_path = vector<int>();
    for(auto n : adj_list[root]) {
        if(!visited[n]) {
            visited[n] = true;
            int _height = 0;
            vector<int> path1;
            vector<int> path2;
            int _diam = getDiam(n, adj_list, _height, path1, path2);
            if(_diam > max_diam) {
                max_diam = _diam;
                diam_path = path2;
            }
            if(_height > m1) {
                m2 = m1;
                m1 = _height;
                best2 = best1;
                best1 = path1;
            }
            else if(_height > m2) {
                m2 = _height;
                best2 = path1;
            }
        }
    }

    height = m1 + 1;

    path.insert( path.end(), best1.begin(), best1.end() );
    path.push_back(root);

    if(m1 + m2 + 2 > max_diam) {
        diam = path;
        std::reverse(best2.begin(), best2.end());
        diam.insert( diam.end(), best2.begin(), best2.end() );
    }
    else{
        diam = diam_path;
    }


    return max(m1 + m2 + 2, max_diam);
}

2
นี่ไม่ใช่ไซต์การเข้ารหัส เราไม่สนับสนุนคำตอบที่ประกอบด้วยกลุ่มของรหัสเป็นหลัก แต่เราต้องการคำตอบที่อธิบายแนวคิดเบื้องหลังอัลกอริทึมและให้ pseudocode ที่กระชับ (ซึ่งไม่ต้องการความรู้ภาษาการเขียนโปรแกรมใด ๆ เพื่อให้เข้าใจ) คุณคำนวณเส้นทางที่ยาวที่สุดที่เริ่มต้นที่โหนดใดโหนดหนึ่งในต้นไม้ได้อย่างไร? (โดยเฉพาะอย่างยิ่งเนื่องจากเส้นทางที่ยาวที่สุดอาจเริ่มต้นด้วยการ "เพิ่ม" ทรี DFS เช่นกลับไปสู่ราก)
DW
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.