Jalur terpanjang di pohon tak berarah dengan hanya satu traversal

44

Ada algoritma standar ini untuk menemukan jalur terpanjang di pohon-pohon yang tidak diarahkan menggunakan dua pencarian kedalaman-pertama:

  • Mulai DFS dari simpul acak dan temukan simpul terjauh darinya; katakan itu .v vv
  • Sekarang mulai DFS dari untuk menemukan titik terjauh dari itu. Jalur ini adalah jalur terpanjang dalam grafik.v

Pertanyaannya adalah, dapatkah ini dilakukan dengan lebih efisien? Bisakah kita melakukannya dengan DFS atau BFS tunggal?

(Ini dapat secara ekuivalen digambarkan sebagai masalah menghitung diameter pohon yang tidak diarahkan.)

emmy
sumber
2
Apa yang Anda kejar juga disebut diameter pohon. (Di pohon, "jalur terpendek terpanjang" dan "jalur terpanjang" adalah hal yang sama karena hanya ada satu jalur yang menghubungkan dua node.)
Raphael

Jawaban:

22

Kami melakukan pencarian mendalam-pertama dalam urutan pos dan hasil agregat di jalan, yaitu kami memecahkan masalah secara rekursif.

Untuk setiap simpul dengan anak-anak u 1 , , u k (di pohon pencarian) ada dua kasus:vu1,,uk

  • Jalan terpanjang di terletak pada salah satu sub pohon T u 1 , ... , T u k .TvTu1,,Tuk
  • Jalan terpanjang di mengandung v .Tvv

Dalam kasus kedua, kita harus menggabungkan satu atau dua jalur terpanjang dari ke salah satu subtree; ini tentu saja untuk daun terdalam. Panjang jalan adalah H ( k ) + H ( k - 1 ) + 2 jika k > 1 , atau H ( k ) + 1 jika k = 1 , dengan H = { h ( T u i ) i = 1 , ...vH(k)+H(k1)+2k>1H(k)+1k=1 set multi ketinggian subtree¹.H={h(Tui)i=1,,k}

Dalam kode semu, algoritmenya terlihat seperti ini:

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. adalah nilai k- terkecil di A (statistik pesanan).A(k)kA
Raphael
sumber
@JeffE Mengenai komentar kedua: Memang, dan ini dijaga di baris terakhir: height1 + height2adalah panjang jalan ini. Jika memang jalan terpanjang, itu dipilih oleh max. Ini juga dijelaskan dalam teks di atas, jadi saya tidak melihat masalah Anda? Tentunya Anda harus recurse untuk mencari tahu apakah itu memang jalan terpanjang, dan bahkan jika tidak ada salahnya (wrt correctness) untuk recurse.
Raphael
@JeffE Mengenai komentar pertama, perhitungan untuk height2secara eksplisit menghapus height1dari pertimbangan, jadi bagaimana bisa memilih anak yang sama dua kali? Itu, juga, telah dijelaskan dalam teks pengantar.
Raphael
1
Rupanya, kita berbicara dengan dialek pseudocode yang berbeda, karena aku kesulitan memahami milikmu. Ini akan membantu untuk menambahkan deklarasi bahasa Inggris eksplisit yang longestPathHeight(T)mengembalikan pasangan (h,d), di mana hketinggian Tdan ddiameter T. (Benar?)
JeffE
@JeffE Benar; Saya pikir itu jelas dari kode, diberikan penjelasan, tetapi tampaknya ekstrapolasi saya "jelas" untuk pseudocode-paradigma lain tidak cukup (milik saya adalah Scalaesque, saya kira). Maaf atas kebingungannya, saya mengklarifikasi kodenya (semoga).
Raphael
8

Ini bisa diselesaikan dengan cara yang lebih baik. Juga, kita dapat mengurangi kompleksitas waktu menjadi O (n) dengan sedikit modifikasi dalam struktur data dan menggunakan pendekatan berulang. Untuk analisis terperinci dan berbagai cara untuk menyelesaikan masalah ini dengan berbagai struktur data.

Berikut ringkasan dari apa yang ingin saya jelaskan di posting blog saya :

Pendekatan Rekursif - Diameter Pohon Cara lain untuk mendekati masalah ini adalah sebagai berikut. Seperti yang kami sebutkan di atas bahwa diameter bisa

  1. sepenuhnya terletak di sub pohon kiri atau
  2. sepenuhnya terletak di sub pohon kanan atau
  3. dapat span melintasi root

Yang berarti bahwa diameternya dapat diturunkan secara ideal

  1. diameter pohon kiri atau
  2. diameter pohon kanan atau
  3. ketinggian sub pohon kiri + tinggi sub pohon kanan + 1 (1 untuk menambahkan simpul akar ketika diameter membentang melintasi simpul akar)

Dan kita tahu bahwa diameternya adalah jalur terpanjang, jadi kita mengambil maksimum 1 dan 2 kalau-kalau itu terletak di salah satu sisi atau wee ambil 3 jika membentang melalui root.

Pendekatan berulang - Diameter Pohon

Kami memiliki pohon, kami membutuhkan informasi meta dengan masing-masing simpul sehingga setiap simpul tahu berikut:

  1. Ketinggian anak kirinya,
  2. Tinggi anak kanannya dan
  3. Jarak terjauh antara simpul daunnya.

Setelah setiap node memiliki informasi ini, kita memerlukan variabel sementara untuk melacak jalur maksimum. Pada saat algoritma selesai, kami memiliki nilai diameter dalam variabel temp.

Sekarang, kita perlu menyelesaikan masalah ini dengan pendekatan bottom-up, karena kita tidak tahu tentang tiga nilai untuk root. Tetapi kita tahu nilai-nilai ini untuk daun.

Langkah-langkah untuk dipecahkan

  1. Inisialisasi semua daun dengan Tinggi kiri dan Tinggi kanan sebagai 1.
  2. Inisialisasi semua daun dengan maxDistance sebagai 0, kami menyatakan bahwa jika salah satu dari leftHeight atau rightHeight adalah 1, kita membuat maxDistance = 0
  3. Bergerak ke atas satu per satu dan menghitung nilai untuk induk langsung. Akan mudah karena sekarang kita tahu nilai-nilai ini untuk anak-anak.
  4. Pada titik tertentu,

    • tetapkan leftHeight sebagai maksimum (leftHeight atau rightHeight dari anak kirinya).
    • tetapkan rightHeight sebagai maksimum (leftHeight atau rightHeight anak kanannya).
    • jika salah satu dari nilai-nilai ini (leftHeight atau rightHeight) adalah 1 menjadikan maxDistance sebagai nol.
    • jika kedua nilai lebih besar dari nol, buat maxDistance sebagai leftHeight + rightHeight - 1
  5. Pertahankan maxDistance dalam variabel temp dan jika pada langkah 4 maxDistance lebih dari nilai saat ini dari variabel, ganti dengan nilai maxDistance baru.
  6. Pada akhir algoritma, nilai dalam maxDistance adalah diameter.
Dharam
sumber
1
Apa yang ditambahkan di atas jawaban saya yang lebih lama, selain kurang umum (Anda hanya berurusan dengan pohon biner)?
Raphael
9
Jawaban ini lebih mudah dibaca dan lebih mudah dimengerti menurut saya (kodesemu Anda sangat membingungkan).
reggaeguitar
-3

Di bawah ini adalah kode yang mengembalikan jalur diameter dengan hanya menggunakan satu DFS traversal. Dibutuhkan ruang ekstra untuk melacak diameter terbaik yang terlihat sejauh ini serta jalur terpanjang yang dimulai pada simpul tertentu di pohon. Ini adalah pendekatan pemrograman dinamis berdasarkan fakta bahwa jalur berdiameter terpanjang tidak menyertakan root, atau merupakan kombinasi dari dua jalur terpanjang dari tetangga root. Jadi kita perlu dua vektor untuk melacak informasi ini.

 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);
}
Trevor Van Loon
sumber
2
Ini bukan situs pengkodean. Kami mencegah jawaban yang terutama terdiri dari satu blok kode. Sebagai gantinya, kami ingin jawaban yang menjelaskan ide-ide di balik algoritma, dan memberikan pseudocode ringkas (yang tidak membutuhkan pengetahuan tentang bahasa pemrograman tertentu untuk memahami). Bagaimana Anda menghitung jalur terpanjang yang dimulai pada simpul tertentu di pohon? (terutama karena jalan terpanjang mungkin dimulai dengan naik "pohon DFS, yaitu, kembali ke root)
DW