Найдовший шлях у непрямому дереві з одним лише обходом


44

Існує цей стандартний алгоритм пошуку найдовшого шляху в непрямих деревах за допомогою двох перших пошукових завдань:

  • Запустіть DFS з випадкової вершини і знайдіть найдальшу вершину від неї; скажіть, це .vv
  • Тепер запустіть DFS з щоб знайти вершину, віддалену від нього. Цей шлях - найдовший шлях у графіку.v

Питання в тому, чи можна це зробити більш ефективно? Чи можемо ми це зробити за допомогою одного DFS або BFS?

(Це може бути рівнозначно описано як проблема обчислення діаметра непрямого дерева.)


2
Те, що ви хочете, також називають діаметром дерева. (На деревах "найдовший найкоротший шлях" і "найдовший шлях" - це одне і те ж, оскільки існує лише одна стежка, що з'єднує будь-які два вузли.)
Рафаель

Відповіді:


22

Ми виконуємо пошук по глибині в порядку замовлення та збираємо результати на шляху, тобто вирішуємо проблему рекурсивно.

Для кожного вузла з дітьми (у дереві пошуку) є два випадки:vu1,,uk

  • Найдовший шлях у лежить в одному з підрядів .T u 1 , , T u kTvTu1,,Tuk
  • Найдовший шлях у містить . vTvv

У другому випадку ми повинні поєднати один або два найдовші шляхи від в одну з підрядів; це, безумовно, ті, до глибоких листя. Тоді довжина шляху дорівнює якщо , або якщо , при цьому множина висот підкреслень¹.H ( k ) + H ( k - 1 ) + 2 k > 1 H ( k ) + 1 k = 1 H = { h ( T u i ) i = 1 , , k }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. k AA(k) - -найменше значення (статистика порядку).kA

@JeffE Щодо другого коментаря: Дійсно, і про це піклується в останньому рядку: height1 + height2чи довжина цього шляху. Якщо це дійсно найдовший шлях, його обирає max. Це також пояснено в тексті вище, тому я не зовсім бачу вашу проблему? Безумовно, вам доведеться повторити, щоб дізнатися, чи дійсно це найдовший шлях, і навіть якщо ні, це не зашкодить (правильність wrt) повторити.
Рафаель

@JeffE Що стосується першого коментаря, то розрахунок для height2явно знімається height1з розгляду, тож як він може вибрати одну і ту ж дитину двічі? Це також було пояснено у вступному тексті.
Рафаель

1
Мабуть, ми розмовляємо різними псевдокодами, бо мені важко зрозуміти твої. Це допоможе додати явну англійську декларацію, яка longestPathHeight(T)повертає пару (h,d), де hвисота Tта dдіаметр T. (Так?)
JeffE

@JeffE Право; Я вважав, що це зрозуміло з коду, з огляду на пояснення, але, очевидно, моя екстраполяція "ясного" для інших псевдокод-парадигм була недостатньою (моя думка - скалаеска, я думаю). Вибачте за плутанину, я уточнюю код (сподіваюся).
Рафаель

8

Це можна вирішити кращим чином. Крім того, ми можемо зменшити часову складність до O (n) з невеликою модифікацією структури даних та використанням ітеративного підходу. Для детального аналізу та кількох способів вирішення цієї проблеми за допомогою різних структур даних.

Ось короткий опис того, що я хочу пояснити у своєму блозі :

Рекурсивний підхід - діаметр дерева Інший спосіб підходу до цієї проблеми полягає в наступному. Як ми вже згадували вище, діаметр може

  1. повністю лежати в лівому під дереві або
  2. повністю лежать у правому під дереві або
  3. може пролізти через корінь

Що означає, що діаметр в ідеалі може бути отриманий

  1. діаметр лівого дерева або
  2. діаметр правого дерева або
  3. висота лівого піддерева + висота правого піддерева + 1 (1 для додавання кореневого вузла, коли діаметр охоплює кореневий вузол)

І ми знаємо, що діаметр - це найдовший шлях, тому ми беремо максимум 1 і 2 у випадку, якщо він лежить в будь-якій з сторін або в 3, якщо він проходить через корінь.

Ітеративний підхід - діаметр дерева

У нас є дерево, нам потрібна метаінформація з кожним із вузлів, щоб кожен вузол знав наступне:

  1. Зріст лівої дитини,
  2. Зріст правильної дитини і
  3. Найдальша відстань між її листовими вузлами.

Після того, як кожен вузол має цю інформацію, нам потрібна тимчасова змінна для відстеження максимального шляху. На той час, коли алгоритм закінчується, ми маємо значення діаметра в змінній temp.

Тепер нам потрібно вирішити цю проблему підходом знизу вгору, оскільки ми не маємо уявлення про три значення для кореня. Але ми знаємо ці значення для листя.

Кроки до вирішення

  1. Ініціалізуйте всі листя лівою висотою та правою висотою як 1.
  2. Ініціалізуйте всі листя з maxDistance як 0, зробимо так, що якщо будь-який з leftHeight або rightHeight дорівнює 1, ми робимо maxDistance = 0
  3. Рухайтеся по черзі вгору і обчислюйте значення для безпосереднього батьків. Це було б легко, тому що зараз ми знаємо ці цінності для дітей.
  4. У заданому вузлі

    • присвоїти leftHeight як максимум (leftHeight або rightHeight лівої дитини).
    • призначте правуВисоту як максимум (ліваВісота або ПраваВисота правої дитини).
    • якщо будь-яке з цих значень (leftHeight або rightHeight) дорівнює 1, зробіть maxDistance як нуль.
    • якщо обидва значення більше нуля, зробіть maxDistance як leftHeight + rightHeight - 1
  5. Підтримуйте maxDistance у змінній temp, і якщо на кроці 4 maxDistance більше, ніж поточне значення змінної, замініть її на нове значення maxDistance.
  6. В кінці алгоритму значення в maxDistance є діаметром.

1
Що це додає до моєї старої відповіді, окрім того, що вона є менш загальною (ви маєте справу лише з двійковими деревами)?
Рафаель

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
Це не сайт кодування. Ми відштовхуємо відповіді, які складаються в основному з блоку коду. Натомість ми хочемо відповіді, які пояснюють ідеї алгоритму та дають стислий псевдокод (для розуміння якого не потрібні знання будь-якої конкретної мови програмування). Як обчислити найдовший шлях, що починається з певного вузла на дереві? (тим більше, що найдовший шлях може починатися з "вгору" дерева DFS, тобто назад до кореня)
DW
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.