Percorso più lungo in un albero non orientato con un solo attraversamento


44

Esiste questo algoritmo standard per trovare il percorso più lungo negli alberi non indirizzati usando due ricerche approfondite:

  • Avvia DFS da un vertice casuale e trova il vertice più lontano da esso; dire che è .v vv
  • Ora avvia un DFS da per trovare il vertice più lontano da esso. Questo percorso è il percorso più lungo nel grafico.v

La domanda è: è possibile farlo in modo più efficiente? Possiamo farlo con un singolo DFS o BFS?

(Questo può essere equivalentemente descritto come il problema del calcolo del diametro di un albero non orientato.)


2
Quello che stai cercando è anche chiamato il diametro dell'albero. (Sugli alberi, "percorso più lungo più breve" e "percorso più lungo" sono la stessa cosa poiché esiste un solo percorso che collega due nodi).
Raffaello

Risposte:


22

Eseguiamo una ricerca approfondita in ordine postale e risultati aggregati lungo la strada, ovvero risolviamo il problema in modo ricorsivo.

Per ogni nodo con figli (nella struttura di ricerca) ci sono due casi:vu1,,uk

  • Il percorso più lungo in trova in uno dei .TvTu1,,Tuk
  • Il percorso più lungo in contiene .Tvv

Nel secondo caso, dobbiamo combinare uno o due percorsi più lunghi da in uno dei sottotitoli; questi sono certamente quelli fino alle foglie più profonde. La lunghezza del percorso è quindi se o se , con il multi set di altezze sottotree¹.vH(k)+H(k1)+2k>1H(k)+1k=1H={h(Tui)i=1,,k}

Nello pseudo codice, l'algoritmo è simile al seguente:

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) è il valore -piccolo in (statistica ordine).kA

@JeffE Per quanto riguarda il secondo commento: in effetti, e questo è curato nell'ultima riga: height1 + height2è la lunghezza di questo percorso. Se è davvero il percorso più lungo, viene scelto da max. È anche spiegato nel testo sopra, quindi non vedo del tutto il tuo problema? Sicuramente devi ricorrere per scoprire se è davvero il percorso più lungo, e anche se non fa male (correttezza wrt) per ricorrere.
Raffaello

@JeffE Per quanto riguarda il primo commento, il calcolo height2rimuove esplicitamente height1dalla considerazione, quindi come può scegliere lo stesso figlio due volte? Anche questo è stato spiegato nel testo introduttivo.
Raffaello

1
Apparentemente, parliamo dialetti pseudocodici diversi, perché faccio fatica a capire il tuo. Aiuterebbe ad aggiungere una dichiarazione inglese esplicita che longestPathHeight(T)restituisce una coppia (h,d), dove hè l'altezza Te dil diametro di T. (Giusto?)
JeffE,

@JeffE Right; Ho pensato che fosse chiaro dal codice, data la spiegazione, ma apparentemente la mia estrapolazione di "clear" per altri paradigmi pseudocodici era insufficiente (la mia è Scalaesque, immagino). Scusate la confusione, sto chiarendo il codice (si spera).
Raffaello

8

Questo può essere risolto in un modo migliore. Inoltre, possiamo ridurre la complessità temporale a O (n) con una leggera modifica nella struttura dei dati e utilizzando un approccio iterativo. Per un'analisi dettagliata e molteplici modi di risolvere questo problema con varie strutture di dati.

Ecco un riassunto di ciò che voglio spiegare in un mio post sul blog :

Approccio ricorsivo - Diametro dell'albero Un altro modo di affrontare questo problema è il seguente. Come accennato in precedenza, il diametro può

  1. giacciono completamente nel sottoalbero sinistro o
  2. giacciono completamente nel sottoalbero destro o
  3. può estendersi attraverso la radice

Ciò significa che il diametro può essere idealmente derivato da

  1. il diametro dell'albero sinistro o
  2. il diametro dell'albero giusto o
  3. l'altezza dell'albero secondario sinistro + l'altezza dell'albero secondario destro + 1 (1 per aggiungere il nodo radice quando il diametro si estende attraverso il nodo radice)

E sappiamo che il diametro è il percorso più lungo, quindi prendiamo il massimo di 1 e 2 nel caso in cui si trovi in ​​uno dei lati o ne prendiamo 3 se attraversa la radice.

Approccio iterativo - Diametro dell'albero

Abbiamo un albero, abbiamo bisogno di una meta informazione con ciascuno dei nodi in modo che ogni nodo sappia quanto segue:

  1. L'altezza del suo bambino sinistro,
  2. L'altezza del figlio giusto e
  3. La distanza più lontana tra i suoi nodi fogliari.

Una volta che ogni nodo ha queste informazioni, abbiamo bisogno di una variabile temporanea per tenere traccia del percorso massimo. Al termine dell'algoritmo, abbiamo il valore del diametro nella variabile temp.

Ora, dobbiamo risolvere questo problema con un approccio dal basso verso l'alto, perché non abbiamo idea dei tre valori per il root. Ma conosciamo questi valori per le foglie.

Passaggi da risolvere

  1. Inizializza tutte le foglie con leftHeight e rightHeight come 1.
  2. Inizializza tutte le foglie con maxDistance come 0, facciamo un punto che se uno dei leftHeight o rightHeight è 1 facciamo il maxDistance = 0
  3. Spostati verso l'alto uno alla volta e calcola i valori per il genitore immediato. Sarebbe facile perché ora conosciamo questi valori per i bambini.
  4. In un dato nodo,

    • assegnare leftHeight come massimo di (leftHeight o rightHeight del figlio sinistro).
    • assegnare rightHeight come massimo di (leftHeight o rightHeight del figlio destro).
    • se uno di questi valori (leftHeight o rightHeight) è 1, imposta maxDistance come zero.
    • se entrambi i valori sono maggiori di zero, impostare maxDistance come leftHeight + rightHeight - 1
  5. Mantenere maxDistance in una variabile temporanea e se nel passaggio 4 maxDistance è superiore al valore corrente della variabile, sostituirlo con il nuovo valore maxDistance.
  6. Alla fine dell'algoritmo il valore in maxDistance è il diametro.

1
Cosa aggiunge questo alla mia risposta più vecchia, oltre ad essere meno generale (hai a che fare solo con alberi binari)?
Raffaello

9
Questa risposta è più leggibile e più facile da capire secondo me (il tuo pseudocodice è molto confuso).
reggaeguitar

-3

Di seguito è riportato un codice che restituisce un percorso diametro utilizzando solo un singolo attraversamento DFS. Richiede spazio aggiuntivo per tenere traccia del diametro migliore visto finora e del percorso più lungo che inizia in un particolare nodo dell'albero. Questo è un approccio di programmazione dinamica basato sul fatto che un percorso di diametro più lungo o non include root o è una combinazione dei due percorsi più lunghi dei vicini di root. Pertanto abbiamo bisogno di due vettori per tenere traccia di queste informazioni.

 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
Questo non è un sito di codifica. Scoraggiamo le risposte che consistono principalmente in un blocco di codice. Invece, vogliamo risposte che spieghino le idee alla base dell'algoritmo e forniscano uno pseudocodice conciso (che non richiede la conoscenza di un particolare linguaggio di programmazione per capire). Come si calcola il percorso più lungo a partire da un nodo particolare nella struttura? (specialmente dal momento che il percorso più lungo potrebbe iniziare andando "su" l'albero DFS, cioè verso la radice)
DW
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.