Yönlendirilmemiş bir ağaçta yalnızca bir geçiş olan en uzun yol


44

İki derinlikli ilk arama kullanarak yönlendirilmemiş ağaçlarda en uzun yolu bulmak için bu standart algoritma vardır:

  • Rastgele bir tepe dan Başlat DFS ve ondan uzak köşe bulmak; v olduğunu söyle .vv'
  • Şimdi ondan en uzak olan tepe noktasını bulmak için den bir DFS başlatın . Bu yol, grafikteki en uzun yoldur.v'

Asıl soru, bu daha verimli bir şekilde yapılabilir mi? Tek bir DFS veya BFS ile yapabilir miyiz?

(Bu eşit olarak yönlendirilmemiş bir ağacın çapının hesaplanması sorunu olarak tanımlanabilir .)


2
Peşinde olduğunuza ağacın çapı da denir . (Ağaçlarda, "en uzun yol" ve "en uzun yol", iki düğümü birbirine bağlayan tek bir yol bulunduğundan aynı şeydir.)
Raphael

Yanıtlar:


22

Sonradan sırayla derinlemesine ilk bir arama yaparız ve yolda toplam sonuçları elde ederiz, yani problemi özyinelemeli olarak çözeriz.

Her düğüm için çocuklu u 1 , ... , u k (arama ağacında) iki durum vardır:vu1,...,uk

  • En uzun yol Alt ağaçlardan birinde yalanlarına T u 1 , ... , T u k .TvTu1,...,Tuk
  • En uzun yol içeriyor v .Tvv

İkinci durumda, bir veya iki en uzun yolları birleştirmek zorunda Alt ağaçlardan birine; bunlar kesinlikle en derin yapraklara ait olanlardır. Yolunun uzunluğu daha sonra, 'H ( k ) + * H ( k - 1 ) + 2 ise k > 1 ya da H ( k ) + 1 ise k = 1 olan, H = { s ( t u ı ) | i = 1 , v'H(k)+'H(k-1)+2k>1'H(k)+1k=1 çoklu alt ağaç yüksekliği kümesini¹.'H={h(Tuben)|ben=1,...,k}

Sahte kodda, algoritma şuna benzer:

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. olduğu k içinde -smallest değeri A (sıra istatistiği).bir(k)kbir

@JeffE İkinci yorumu ele aldığımızda: Gerçekten de, ve bu son sırada halledilir: height1 + height2bu yolun uzunluğu. Gerçekten de en uzun yol ise, tarafından seçilir max. Ayrıca yukarıdaki metinde de açıklanmaktadır, bu yüzden sorununuzu tam olarak görmüyorum? Kuşkusuz en uzun yol olup olmadığını öğrenmek için tekrarlamalısınız ve yinelemenin zararı olmasa bile (wrt doğruluğu).
Raphael

@JeffE İlk yorum ile ilgili olarak, hesaplama height2açıkça height1dikkate alınır, bu yüzden aynı çocuğu nasıl iki kez seçebilir? Bu, tanıtım metninde de açıklanmıştır.
Raphael

1
Görünen o ki, farklı sözde kod lehçeleri konuşuyoruz, çünkü seninkini anlamakta zorlanıyorum. Bu açık bir İngiliz deklarasyonu eklemek için yardımcı olur longestPathHeight(T)bir çift döner (h,d), hyüksekliğidir Tve dçapı olan T. (Sağ?)
JeffE

@JeffE Right; Açıklamadan sonra koddan açıkça anlaşıldığını düşündüm, ama görünüşe göre diğer sahte kod paradigmaları için "net" ifadesinin yetersiz kaldığını düşünüyorum (benimki Scalaesque, sanırım). Karışıklık için özür dilerim, kodu açıklığa kavuşturuyorum (umarım).
Raphael

8

Bu daha iyi bir şekilde çözülebilir. Ayrıca, veri yapısındaki küçük bir değişiklikle ve yinelemeli bir yaklaşım kullanarak zaman karmaşıklığını O (n) 'ye indirebiliriz. Detaylı bir analiz ve bu sorunu çeşitli veri yapılarıyla çözmenin birçok yolu.

İşte bir blog yazımda açıklamak istediklerimin bir özeti :

Özyinelemeli Yaklaşım - Ağaç Çapı Bu soruna yaklaşmanın bir başka yolu da aşağıdaki gibidir. Yukarıda belirttiğimiz gibi çap olabilir

  1. tamamen sol alt ağacın içine ya da
  2. Tamamen sağ alt ağacın içine ya da
  3. kökte yayılabilir

Bu, çapın ideal olarak elde edilebileceği anlamına gelir

  1. sol ağacın çapı veya
  2. sağ ağacın çapı veya
  3. sol alt ağacın yüksekliği + sağ alt ağacın yüksekliği + 1 (çap kök düğüme yayıldığında kök düğümünü eklemek için 1)

Ve çapın en uzun yol olduğunu biliyoruz, bu nedenle her iki tarafta da uzanması ya da kökünden geçmesi durumunda en fazla 1 ve 2 olması gerekiyor.

Yinelemeli Yaklaşım - Ağaç Çapı

Bir ağacımız var, her bir düğümün aşağıdakileri bilmesi için her bir düğüm hakkında bir meta bilgiye ihtiyacımız var:

  1. Sol çocuğunun boyu,
  2. Doğru çocuğunun yüksekliği ve
  3. Yaprak düğümleri arasındaki en uzak mesafe.

Her düğüm bu bilgiye sahip olduğunda, maksimum yolu takip etmek için geçici bir değişkene ihtiyacımız vardır. Algoritma bittiğinde, temp değişkenindeki çap değerine sahibiz.

Şimdi, bu sorunu aşağıdan yukarıya bir yaklaşımla çözmemiz gerekiyor, çünkü kök için üç değer hakkında hiçbir fikrimiz yok. Fakat bu değerleri yapraklar için biliyoruz.

Çözülecek adımlar

  1. Tüm yaprakları leftHeight ve rightHeight ile 1 olarak başlat.
  2. Tüm yaprakları maxDistance ile 0 olarak başlat, bir noktaya bırakıyoruz: leftHeight veya rightHeight 1 ise maxDistance = 0 yapıyoruz
  3. Bir seferde bir yukarı doğru hareket ettirin ve hemen üst öğeye ilişkin değerleri hesaplayın. Kolay olurdu çünkü artık çocuklar için bu değerleri biliyoruz.
  4. Belirli bir düğümde,

    • leftHeight'ı maksimum olarak atayın (sol çocuğun Yüksekliği veya sağ Yüksekliği).
    • rightHeight'ı maksimum olarak atayın (sol çocuğun Yüksekliği veya sağ Yüksekliği).
    • Bu değerlerden herhangi biri (leftHeight veya rightHeight) 1 ise, maxDistance değerini sıfır yapın.
    • Her iki değer de sıfırdan büyükse, maxDistance işlevini leftHeight + rightHeight - 1 olarak yapın.
  5. Bir temp değişkeninde maxDistance değerini koruyun ve eğer 4. adımda maxDistance değişkenin akım değerinden fazlaysa, onu yeni maxDistance değeri ile değiştirin.
  6. Algoritmanın sonunda, maxDistance içindeki değer çaptır.

1
Daha az genel olmanın yanı sıra (sadece ikili ağaçlarla uğraşıyorsunuz), eski cevabımı ne ekliyor?
Raphael

9
Bu cevabın bence daha okunaklı ve anlaşılması kolaydır (sözde kodunuz çok kafa karıştırıcıdır).
reggaeguitar

-3

Aşağıda yalnızca tek bir DFS geçişi kullanarak bir çap yolu döndüren kod verilmiştir. Ağacın belirli bir düğümünde başlayan en uzun yolun yanı sıra, şimdiye kadar görülen en iyi çapın izini sürmek için ekstra alan gerekir. Bu, en uzun çaplı bir yolun ya kök içermemesi veya kökün komşularının en uzun iki yolunun birleşimi olduğu gerçeğine dayanan dinamik bir programlama yaklaşımıdır. Bu yüzden bu bilgiyi takip etmek için iki vektöre ihtiyacımız var.

 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
Bu bir kodlama sitesi değil. Öncelikle bir kod bloğundan oluşan cevapları önermiyoruz. Bunun yerine, algoritmanın arkasındaki fikirleri açıklayan ve özlü sözde kod (cevaplamak için herhangi bir özel programlama dili bilgisi gerektirmeyen) veren cevaplar istiyoruz. Ağaçtaki belirli bir düğümde başlayan en uzun yolu nasıl hesaplarsınız? (özellikle en uzun yol DFS ağacını "yukarı" başlayarak başlayabileceğinden, yani köke doğru)
DW
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.