Come funziona una ricerca breadth-first quando si cerca il percorso più breve?


129

Ho fatto qualche ricerca e mi sembra che manchi una piccola parte di questo algoritmo. Capisco come funziona una Breadth-First Search, ma non capisco come esattamente mi porterà a un percorso specifico, invece di dirmi dove può andare ogni singolo nodo. Immagino che il modo più semplice per spiegare la mia confusione sia fornire un esempio:

Ad esempio, diciamo che ho un grafico come questo:

inserisci qui la descrizione dell'immagine

E il mio obiettivo è passare da A a E (tutti i bordi non sono ponderati).

Comincio da A, perché questa è la mia origine. Faccio la coda A, seguito immediatamente dal dequeueing A ed esplorandolo. Questo produce B e D, poiché A è collegato a B e D. I quindi accodano sia B che D.

Dequeue B e lo esploro, e scopro che conduce ad A (già esplorato) e C, quindi faccio la coda C. Poi dequeue D, e scopro che conduce a E, il mio obiettivo. Devo quindi dequeue C, e scopro che porta anche a E, il mio obiettivo.

So logicamente che il percorso più veloce è A-> D-> E, ma non sono sicuro di come aiuti esattamente la ricerca della larghezza - come dovrei registrare i percorsi in modo tale che quando finisco, posso analizzare i risultati e vedere che il percorso più breve è A-> D-> E?

Inoltre, nota che in realtà non sto usando un albero, quindi non ci sono nodi "genitore", ma solo figli.


2
"Inoltre, tieni presente che in realtà non sto usando un albero, quindi non ci sono nodi" genitore ", solo figli" - ovviamente dovrai conservare il genitore da qualche parte. Per DFS lo stai facendo indirettamente attraverso lo stack di chiamate, per BFS devi farlo esplicitamente. Niente che tu possa fare al riguardo, temo :)
Voo

Risposte:


85

Tecnicamente, Breadth-first search (BFS) da solo non consente di trovare il percorso più breve, semplicemente perché BFS non sta cercando un percorso più breve: BFS descrive una strategia per la ricerca di un grafico, ma non dice che è necessario cercare qualcosa in particolare.

L'algoritmo di Dijkstra adatta BFS per consentire di trovare percorsi più brevi a sorgente singola.

Per recuperare il percorso più breve dall'origine a un nodo, è necessario mantenere due elementi per ciascun nodo nel grafico: la distanza più breve corrente e il nodo precedente nel percorso più breve. Inizialmente tutte le distanze sono impostate su infinito e tutti i predecessori sono vuoti. Nel tuo esempio, imposta la distanza di A su zero, quindi procedi con il BFS. Ad ogni passaggio si controlla se è possibile migliorare la distanza di un discendente, ovvero la distanza dall'origine al predecessore più la lunghezza del bordo che si sta esplorando è inferiore alla distanza migliore corrente per il nodo in questione. Se riesci a migliorare la distanza, imposta il nuovo percorso più breve e ricorda il predecessore attraverso il quale è stato acquisito quel percorso. Quando la coda BFS è vuota, scegli un nodo (nel tuo esempio, è E) e attraversa i suoi predecessori fino all'origine.

Se questo sembra un po 'confuso, Wikipedia ha una bella sezione di pseudocodici sull'argomento.


Grazie! Avevo letto in precedenza lo pseudocodice ma non ero in grado di capirlo, la tua spiegazione mi ha fatto fare clic per me
Jake,

46
Vorrei fare la seguente nota per le persone che guarderanno questo post in futuro: Se i bordi non sono ponderati, non è necessario memorizzare la "distanza più breve corrente" per ciascun nodo. Tutto ciò che deve essere memorizzato è il genitore per ogni nodo scoperto. Quindi, mentre stai esaminando un nodo e accodando tutti i suoi successori, imposta semplicemente il genitore di quei nodi sul nodo che stai esaminando (questo raddoppierà come marcandoli "scoperti"). Se questo puntatore genitore è NUL / nil / None per ogni dato nodo, significa che non è stato ancora scoperto da BFS o è il nodo sorgente / radice stesso.
Shashank,

@Shanhank Se non manteniamo la distanza, come potremmo sapere la distanza più breve, ti preghiamo di spiegare di più.
Gaurav Sehgal,

12
@gauravsehgal Questo commento è per grafici con bordi non ponderati. BFS troverà la distanza più breve semplicemente a causa del suo modello di ricerca radiale che considera i nodi in ordine di distanza dal punto iniziale.
Shashank,

13
Un consiglio per i lettori del commento di @ Shashank: non ponderato e uniformemente ponderato (ad es. Tutti i bordi hanno un peso = 5) sono equivalenti.
TWiStErRob,

54

Come osservato sopra, BFS può solo essere usato per trovare il percorso più breve in un grafico se:

  1. Non ci sono anelli

  2. Tutti i bordi hanno lo stesso peso o nessun peso.

Per trovare il percorso più breve, tutto ciò che devi fare è iniziare dalla sorgente ed eseguire una prima ricerca ampia e fermarti quando trovi il nodo di destinazione. L'unica cosa aggiuntiva che devi fare è avere un array precedente [n] che memorizzerà il nodo precedente per ogni nodo visitato. Il precedente della fonte può essere nullo.

Per stampare il percorso, scorrere semplicemente l'array precedente [] dall'origine fino a raggiungere la destinazione e stampare i nodi. DFS può anche essere utilizzato per trovare il percorso più breve in un grafico in condizioni simili.

Tuttavia, se il grafico è più complesso, contenente bordi e loop ponderati, è necessaria una versione più sofisticata di BFS, ovvero l'algoritmo di Dijkstra.


1
Dijkstra se non -ve pesi altrimenti usa bellman ford algo se -ve pesi
shaunak1111

BFS funziona per trovare tutti i percorsi più brevi tra due nodi?
Maria Ines Parnisari,

35
@javaProgrammer, non è giusto. BFS può essere utilizzato anche per trovare il percorso più breve in un grafico ciclico non ponderato. Se un grafico non è ponderato, è possibile applicare BFS per SP indipendentemente dal fatto di avere dei loop.
Andrei Kaigorodov,

3
To print the path, simple loop through the previous[] array from source till you reach destination.Ma il precedente della fonte è nullo. Penso che volevi dire backtrace from destination using the previous array until you reach the source.
Aandis,

2
Perché dici che DFS funzionerà in condizioni simili? Non è possibile per DFS intraprendere un percorso di arrotondamento ingenuo per ottenere dai nodi inizio-> fine e quindi fornire un percorso che non sia il più breve?
James Wierzba,

26

Dal tutorial qui

"Ha la proprietà estremamente utile che se tutti i bordi di un grafico non sono ponderati (o dello stesso peso), la prima volta che un nodo viene visitato è il percorso più breve verso quel nodo dal nodo di origine"


Questo è utile per il nodo direttamente raggiungibile (1-> 2) (2 viene raggiunto direttamente da 1). Per nodo non direttamente raggiungibile c'è più lavoro (1-> 2-> 3, 3 non viene raggiunto direttamente da 1). Certo, è ancora vero considerando individualmente, cioè 1-> 2 e 2-> 3 individualmente sono i percorsi più brevi.
Manohar Reddy Poreddy,

11

Ho perso 3 giorni alla
fine risolto una domanda del grafico
utilizzata per
trovare la distanza più breve
utilizzando BFS

Vuoi condividere l'esperienza.

When the (undirected for me) graph has
fixed distance (1, 6, etc.) for edges

#1
We can use BFS to find shortest path simply by traversing it
then, if required, multiply with fixed distance (1, 6, etc.)

#2
As noted above
with BFS
the very 1st time an adjacent node is reached, it is shortest path

#3
It does not matter what queue you use
   deque/queue(c++) or
   your own queue implementation (in c language)
   A circular queue is unnecessary

#4
Number of elements required for queue is N+1 at most, which I used
(dint check if N works)
here, N is V, number of vertices.

#5
Wikipedia BFS will work, and is sufficient.
    https://en.wikipedia.org/wiki/Breadth-first_search#Pseudocode

Ho perso 3 giorni a provare tutte le alternative di cui sopra, verificando e ri-verificando ancora e ancora sopra
che non siano un problema.
(Cerca di passare il tempo a cercare altri problemi, se non trovi problemi con sopra 5).


Altre spiegazioni dal commento qui sotto:

      A
     /  \
  B       C
 /\       /\
D  E     F  G

Supponiamo che il grafico del
grafico vada verso il basso.
Per A, gli adiacenti sono B e C
Per B, gli adiacenti sono D ed E
Per C, gli adiacenti sono F e G

ad esempio, il nodo iniziale è A

  1. quando si raggiunge A, a, B & C la distanza più breve da B & C da A è 1

  2. quando raggiungi D o E, attraverso B, la distanza più breve da A e D è 2 (A-> B-> D)

allo stesso modo, A-> E è 2 (A-> B-> E)

inoltre, A-> F & A-> G è 2

Quindi, ora invece di 1 distanza tra i nodi, se è 6, quindi moltiplica la risposta per 6
esempio,
se la distanza tra ciascuno è 1, allora A-> E è 2 (A-> B-> E = 1 + 1 )
se la distanza tra ciascuna è 6, allora A-> E è 12 (A-> B-> E = 6 + 6)

sì, bfs può prendere qualsiasi percorso
ma stiamo calcolando per tutti i percorsi

se devi andare dalla A alla Z, percorriamo tutti i percorsi da A a un I intermedio, e poiché ci saranno molti percorsi scarteremo tutti tranne il percorso più breve fino a I, quindi continueremo con il percorso più breve davanti al nodo J
successivo se ancora ci sono più percorsi da I a J, prendiamo solo un
esempio più breve ,
assumiamo,
A -> I abbiamo distanza 5
(STEP) assumiamo, I -> J abbiamo percorsi multipli, di distanze 7 e 8, poiché 7 è il più breve
prendiamo A -> J come 5 (A-> I più corto) + 8 (il più breve ora) = 13
quindi A-> J ora è 13
, ripetiamo ora sopra (STEP) per J -> K e così via, finché non otteniamo a Z

Leggi questa parte, 2 o 3 volte, e disegna su carta, otterrai sicuramente quello che sto dicendo, buona fortuna



Potresti per favore elaborare come sei riuscito a trovare il percorso più breve con una prima ricerca ampia. Una prima ricerca ampia cerca principalmente un nodo, ci possono essere n percorsi per un nodo obiettivo dal nodo sorgente e bfs può prendere qualsiasi percorso. Come stai determinando il percorso migliore?
sfavorito il

ho aggiunto la parte "più spiegazione" alla risposta sopra, fammi sapere se questo soddisfa
Manohar Reddy Poreddy

1
Vedo che stai cercando di eseguire un BFS su un grafico ponderato. Di distanze 7 e 8 perché hai scelto 8? perché non 7? cosa succede se l'8 nodo non ha altri bordi verso la destinazione? Il flusso dovrà quindi scegliere 7.
sfavorito il

bella domanda, votata, sì, non stiamo buttando via, teniamo traccia di tutti i nodi adiacenti, fino a raggiungere la destinazione. BFS funzionerà solo quando ci sono solo distanze costanti come tutte e 7 o tutte 8. Ne ho dato una generica che ha 7 e 8 che è anche chiamata algoritmo di dijkstra.
Manohar Reddy Poreddy il

scusa, non so cosa intendi, vedi en.wikipedia.org/wiki/Algoritmo di
Dijkstra

2

Sulla base della risposta acheron55 ho pubblicato una possibile implementazione qui .
Ecco un breve riassunto di esso:

Tutto quello che devi fare è tenere traccia del percorso attraverso il quale è stato raggiunto l'obiettivo. Un modo semplice per farlo è quello di inserire Queuel'intero percorso utilizzato per raggiungere un nodo, anziché il nodo stesso.
Il vantaggio di farlo è che quando l'obiettivo è stato raggiunto la coda mantiene il percorso utilizzato per raggiungerlo.
Questo è applicabile anche ai grafici ciclici, in cui un nodo può avere più di un genitore.


0

Visitando questa discussione dopo un periodo di inattività, ma dato che non vedo una risposta completa, ecco i miei due centesimi.

La ricerca per ampiezza troverà sempre il percorso più breve in un grafico non ponderato. Il grafico può essere ciclico o aciclico.

Vedi sotto per lo pseudocodice. Questo pseudocodice presuppone che si stia utilizzando una coda per implementare BFS. Si presume inoltre che sia possibile contrassegnare i vertici come visitati e che ciascun vertice memorizzi un parametro di distanza, che è inizializzato su infinito.

mark all vertices as unvisited
set the distance value of all vertices to infinity
set the distance value of the start vertex to 0
push the start vertex on the queue
while(queue is not empty)   
    dequeue one vertex (well call it x) off of the queue
    if the value of x is the value of the end vertex: 
        return the distance value of x
    otherwise, if x is not marked as visited:
        mark it as visited
        for all of the unmarked children of x:
            set their distance values to be the distance of x + 1
            enqueue them to the queue
if here: there is no path connecting the vertices

Si noti che questo approccio non funziona per i grafici ponderati - per questo, vedere l'algoritmo di Dijkstra.


-6

La seguente soluzione funziona per tutti i casi di test.

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {

   public static void main(String[] args)
        {
            Scanner sc = new Scanner(System.in);

            int testCases = sc.nextInt();

            for (int i = 0; i < testCases; i++)
            {
                int totalNodes = sc.nextInt();
                int totalEdges = sc.nextInt();

                Map<Integer, List<Integer>> adjacencyList = new HashMap<Integer, List<Integer>>();

                for (int j = 0; j < totalEdges; j++)
                {
                    int src = sc.nextInt();
                    int dest = sc.nextInt();

                    if (adjacencyList.get(src) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(src);
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    }


                    if (adjacencyList.get(dest) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(dest);
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    }
                }

                int start = sc.nextInt();

                Queue<Integer> queue = new LinkedList<>();

                queue.add(start);

                int[] costs = new int[totalNodes + 1];

                Arrays.fill(costs, 0);

                costs[start] = 0;

                Map<String, Integer> visited = new HashMap<String, Integer>();

                while (!queue.isEmpty())
                {
                    int node = queue.remove();

                    if(visited.get(node +"") != null)
                    {
                        continue;
                    }

                    visited.put(node + "", 1);

                    int nodeCost = costs[node];

                    List<Integer> children = adjacencyList.get(node);

                    if (children != null)
                    {
                        for (Integer child : children)
                        {
                            int total = nodeCost + 6;
                            String key = child + "";

                            if (visited.get(key) == null)
                            {
                                queue.add(child);

                                if (costs[child] == 0)
                                {
                                    costs[child] = total;
                                } else if (costs[child] > total)
                                {
                                    costs[child] = total;
                                }
                            }
                        }
                    }
                }

                for (int k = 1; k <= totalNodes; k++)
                {
                    if (k == start)
                    {
                        continue;
                    }

                    System.out.print(costs[k] == 0 ? -1 : costs[k]);
                    System.out.print(" ");
                }
                System.out.println();
            }
        }
}

4
Sottovalutato per non aver risposto alla domanda. Semplicemente incollare uno snippet di codice non funzionerà su SO.
Rishabh Agrahari,
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.