Con đường dài nhất trong một cây vô hướng chỉ có một đường ngang


44

Có thuật toán tiêu chuẩn này để tìm đường đi dài nhất trong các cây vô hướng bằng cách sử dụng hai tìm kiếm theo chiều sâu:

  • Bắt đầu DFS từ một đỉnh ngẫu nhiên và tìm đỉnh xa nhất từ ​​nó; nói nó là v ' .vv
  • Bây giờ bắt đầu một DFS từ để tìm đỉnh xa nhất từ nó. Đường dẫn này là đường dẫn dài nhất trong biểu đồ.v

Câu hỏi là, điều này có thể được thực hiện hiệu quả hơn? Chúng ta có thể làm điều đó với một DFS hoặc BFS không?

(Điều này có thể được mô tả tương đương như là vấn đề tính toán đường kính của một cây không có hướng.)


2
Những gì bạn đang theo sau cũng được gọi là đường kính của cây. (Trên cây, "con đường ngắn nhất dài nhất" và "con đường dài nhất" là như nhau vì chỉ có một con đường nối hai nút bất kỳ.)
Raphael

Câu trả lời:


22

Chúng tôi thực hiện tìm kiếm theo chiều sâu theo thứ tự bài và tổng hợp kết quả trên đường đi, đó là chúng tôi giải quyết vấn đề theo cách đệ quy.

Đối với mỗi nút với trẻ em u 1 , ... , u k (trong cây tìm kiếm) có hai trường hợp:vu1,,uk

  • Con đường dài nhất trong nằm trong một trong những subtrees T u 1 , ... , T u k .TvTu1,,Tuk
  • Đường dẫn dài nhất trong chứa v .Tvv

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

Trong mã giả, thuật toán trông như thế này:

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 Về bình luận thứ hai: Thật vậy, và điều này được quan tâm ở hàng cuối cùng: height1 + height2là độ dài của đường dẫn này. Nếu nó thực sự là con đường dài nhất, nó được chọn bởi max. Nó cũng được giải thích trong văn bản trên, vì vậy tôi không thấy vấn đề của bạn? Chắc chắn bạn phải tái diễn để tìm hiểu xem đó có thực sự là con đường dài nhất hay không, và ngay cả khi nó không bị tổn thương (tính chính xác) để tái diễn.
Raphael

@JeffE Liên quan đến nhận xét đầu tiên, tính toán để height2loại bỏ rõ ràng height1khỏi xem xét, vậy làm thế nào nó có thể chọn cùng một đứa trẻ hai lần? Điều đó, cũng, đã được giải thích trong văn bản giới thiệu.
Raphael

1
Rõ ràng, chúng tôi nói các phương ngữ giả khác nhau, bởi vì tôi có một thời gian khó hiểu của bạn. Nó sẽ giúp thêm một khai báo tiếng Anh rõ ràng longestPathHeight(T)trả về một cặp (h,d), trong đó hchiều cao Tdlà đường kính của T. (Phải không?)
JeffE

@JeffE Phải; Tôi nghĩ rằng điều đó rõ ràng từ mã, đưa ra lời giải thích, nhưng dường như phép ngoại suy "rõ ràng" của tôi đối với các mô hình giả mã khác là không đủ (tôi đoán là Scalaesque). Xin lỗi vì sự nhầm lẫn, tôi đang làm rõ mã (hy vọng).
Raphael

8

Điều này có thể được giải quyết một cách tốt hơn. Ngoài ra, chúng ta có thể giảm độ phức tạp thời gian xuống O (n) bằng một sửa đổi nhỏ trong cấu trúc dữ liệu và sử dụng phương pháp lặp. Để phân tích chi tiết và nhiều cách giải quyết vấn đề này với các cấu trúc dữ liệu khác nhau.

Đây là một bản tóm tắt về những gì tôi muốn giải thích trong một bài đăng trên blog của tôi :

Phương pháp đệ quy - Đường kính cây Một cách khác để tiếp cận vấn đề này như sau. Như chúng tôi đã đề cập ở trên rằng đường kính có thể

  1. nằm hoàn toàn trong cây con bên trái hoặc
  2. hoàn toàn nằm trong cây con bên phải hoặc
  3. có thể kéo dài qua gốc

Điều đó có nghĩa là đường kính có thể được dẫn xuất lý tưởng bởi

  1. đường kính của cây bên trái hoặc
  2. đường kính của cây bên phải hoặc
  3. chiều cao của cây con bên trái + chiều cao của cây con bên phải + 1 (1 để thêm nút gốc khi đường kính kéo dài qua nút gốc)

Và chúng ta biết rằng đường kính là con đường dài nhất, vì vậy chúng ta lấy tối đa 1 và 2 trong trường hợp nó nằm ở một trong hai bên hoặc một nửa mất 3 nếu nó đi qua gốc.

Phương pháp lặp - Đường kính cây

Chúng ta có một cây, chúng ta cần một thông tin meta với mỗi nút để mỗi nút biết sau:

  1. Chiều cao của đứa con bên trái của nó,
  2. Chiều cao của đứa con phải và
  3. Khoảng cách xa nhất giữa các nút lá của nó.

Khi mỗi nút có thông tin này, chúng ta cần một biến tạm thời để theo dõi đường dẫn tối đa. Vào thời điểm thuật toán kết thúc, chúng ta có giá trị đường kính trong biến tạm thời.

Bây giờ, chúng ta cần giải quyết vấn đề này theo cách tiếp cận từ dưới lên, bởi vì chúng ta không có ý tưởng nào về ba giá trị cho gốc. Nhưng chúng tôi biết những giá trị cho lá.

Các bước để giải quyết

  1. Khởi tạo tất cả các lá với tráiHeight và rightHeight là 1.
  2. Khởi tạo tất cả các lá với maxDistance là 0, chúng ta xác định rằng nếu một trong hai bên trái hoặc bên phải là 1, chúng ta tạo maxDistance = 0
  3. Di chuyển lên trên một lần và tính các giá trị cho cha mẹ ngay lập tức. Nó sẽ dễ dàng bởi vì bây giờ chúng tôi biết những giá trị này cho trẻ em.
  4. Tại một nút cho trước,

    • chỉ định bên trái tối đa là (bên trái hoặc bên phải của bên trái của nó).
    • chỉ định mức tối đa bên phải tối đa (bên trái hoặc bên phải của con bên phải).
    • nếu bất kỳ giá trị nào trong số này (leftHeight hoặc rightHeight) là 1, hãy tạo maxDistance thành 0.
    • nếu cả hai giá trị đều lớn hơn 0, hãy tạo maxDistance là leftHeight + rightHeight - 1
  5. Duy trì maxDistance trong biến temp và nếu ở bước 4, maxDistance lớn hơn giá trị hiện tại của biến, hãy thay thế nó bằng giá trị maxDistance mới.
  6. Vào cuối thuật toán, giá trị trong maxDistance là đường kính.

1
Điều này thêm gì vào câu trả lời cũ của tôi, ngoài việc ít chung chung hơn (bạn chỉ đối phó với cây nhị phân)?
Raphael

9
Câu trả lời này dễ đọc hơn và dễ hiểu hơn theo ý kiến ​​của tôi (mã giả của bạn rất khó hiểu).
reggaeg Ức

-3

Dưới đây là mã trả về một đường dẫn đường kính chỉ sử dụng một đường truyền DFS duy nhất. Nó đòi hỏi thêm không gian để theo dõi đường kính tốt nhất được nhìn thấy cho đến nay cũng như con đường dài nhất bắt đầu tại một nút cụ thể trong cây. Đây là một cách tiếp cận lập trình động dựa trên thực tế là đường dẫn có đường kính dài nhất không bao gồm gốc hoặc là sự kết hợp của hai đường dẫn dài nhất của hàng xóm gốc. Vì vậy, chúng ta cần hai vectơ để theo dõi thông tin này.

 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
Đây không phải là một trang web mã hóa. Chúng tôi không khuyến khích các câu trả lời bao gồm chủ yếu là một khối mã. Thay vào đó, chúng tôi muốn các câu trả lời giải thích các ý tưởng đằng sau thuật toán và đưa ra mã giả ngắn gọn (không yêu cầu kiến ​​thức về bất kỳ ngôn ngữ lập trình cụ thể nào để hiểu). Làm thế nào để bạn tính toán con đường dài nhất bắt đầu tại một nút cụ thể trong cây? (đặc biệt là vì con đường dài nhất có thể bắt đầu bằng cách "đi lên" cây DFS, tức là quay trở lại gốc)
DW
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.