Na teoria dos grafos, busca em largura (ou busca em amplitude, também conhecido em inglês por Breadth-First Search - BFS) é um algoritmo de busca em grafos utilizado para realizar uma busca ou travessia num grafo e estrutura de dados do tipo árvore. Intuitivamente, você começa pelo vértice raiz e explora todos os vértices vizinhos. Então, para cada um desses vértices mais próximos, exploramos os seus vértices vizinhos inexplorados e assim por diante, até que ele encontre o alvo da busca.
Definição
Formalmente, uma busca em largura é um método de busca não-informada (ou desinformada) que expande e examina sistematicamente todos os vértices de um grafo direcionado ou não-direcionado. Em outras palavras, podemos dizer que o algoritmo realiza uma busca exaustiva num grafo passando por todas as arestas e vértices do grafo. Sendo assim, o algoritmo deve garantir que nenhum vértice ou aresta será visitado mais de uma vez e, para isso, utiliza uma estrutura de dados fila para garantir a ordem de chegada dos vértices. Dessa maneira, as visitas aos vértices são realizadas através da ordem de chegada na estrutura fila e um vértice que já foi marcado não pode entrar novamente a esta estrutura.
Uma analogia muito conhecida (figura ao lado) para demonstrar o funcionamento do algoritmo é pintando os vértices de branco, cinza e preto. Os vértices na cor branca ainda não foram marcados e nem enfileirados, os da cor cinza são os vértices que estão na estrutura fila e os pretos são aqueles que já tiveram todos os seus vértices vizinhos enfileirados e marcados pelo algoritmo.
Tal mecanismo permite que se descubra todos os vértices a uma distância n do vértice raiz antes de qualquer outro vértice de distancia maior que n, sendo n o número de arestas para atingir qualquer outro vértice no grafo considerado. Essa característica do algoritmo permite construir uma árvore de distâncias mínimas (menor número de arestas) entre o vértice raiz e os demais, sendo que o vértice responsável por enfileirar o seu vizinho na cor branca que será o vértice pai deste na representação em árvore gerada.
Características
Complexidade de Tempo
Considerando um grafo representado em listas de adjacência, o pior caso, aquele em que todos os vértices e arestas são explorados pelo algoritmo, a complexidade de tempo pode ser representada pela seguinte expressão , onde significa o tempo total gasto nas operações sobre todas as arestas do grafo onde cada operação requer um tempo constante sobre uma aresta, e que significa o número de operações sobre todos os vértices que possui uma complexidade constante para cada vértice uma vez que todo vértice é enfileirado e desenfileirado uma única vez.
Complexidade de Espaço
Quando o número de vértices no grafo é conhecido e supondo-se a representação deste em listas de adjacência, a complexidade de espaço do algoritmo pode ser representada por onde representa o número total de vértices no grafo.
História
O algoritmo de busca em largura foi inventado pela primeira vez por Konrad Zuse em sua tese de doutorado sobre a linguagem de programação Plankalkül, mas como sua tese rejeitada porque Zuse esqueceu de pagar a taxa de matrícula, ela acabou esquecida e só foi publicada inteira em 1972. O algoritmo acabou sendo reinventado novamente em 1959 por Edward F. Moore, que o utilizou para encontrar o caminho mais curto para sair de um labirinto.[1]
Pseudocódigo
A seguir é apresentado um pseudocódigo do algoritmo busca em largura para uma estrutura de dados grafo com lista de adjacência. A letra F representa uma fila (FIFO) inicialmente vazia, G é o grafo em questão e s, v, w representam vértices do grafo onde listaDeAdjacência representa a lista de adjacência de um vértice.
BuscaEmLargura
escolha uma raiz s de G
marque s
insira s em F
enquanto F não está vazia faça
seja v o primeiro vértice de F
para cada w ∈ listaDeAdjacência de v faça
se w não está marcado então
visite aresta entre v e w
marque w
insira w em F
senao se w ∈ F entao
visite aresta entre v e w
fim se
fim para
retira v de F
fim enquanto
Exemplo 1
Seguindo os passos do pseudocódigo acima e iniciando no vértice 6 da figura ao lado, o algoritmo estará com a sequência de vértices marcados e a fila assim:
Aplicando o pseudocódigo nesse grafo de cidades alemãs e iniciando o algoritmo na cidade de Frankfurt, repare que para montar a árvore da figura foi necessário gravar na figura apenas as arestas que são processadas na primeira condição "se" do pseudocódigo (se w não está marcado então). Caso as arestas desse exemplo não fossem valoradas (como no primeiro exemplo) ficaria fácil encontrar a distância para o vértice raiz com o algoritmo busca em largura, mas, para o grafo deste exemplo (que são valoradas) pesquise por Algoritmo de Dijkstra para encontrar o menor caminho de um vértice a outro.
intBuscaEmLargura(Grafo*G,Fila*F,intraiz){intverticesMarcados[NumVertices];//vetor de vertices marcadosinttamVerticesMarcados=0;intvertice1;no_lista*p;verticesMarcados[0]=raiz;//marca raiztamVerticesMarcados++;PoeVerticeNaFila(F,raiz);//poe raiz na filawhile(!FilaVazia(F)){//enquanto a fila nao esta vaziavertice1=F->ini->vertice;//vertice que esta no inicio da filap=G->Ladj[vertice1-1].inicio;// Ladj = lista de adjacencia de vertice1while(p!=NULL){//enquanto a lista de adjacencia do vertice1 nao acabaif(!BuscaVertice(p->vertice,verticesMarcados,tamVerticesMarcados)){//busca p->vertice no vetor verticesMarcadosverticesMarcados[tamVerticesMarcados++]=p->vertice;//marcou p->verticePoeVerticeNaFila(F,p->vertice);//poe p->vertice na fila//arestas que compoem arvore geradora mínima, aresta (vertice1, p->vertice)}elseif(WPertenceF(p->vertice,F)){//se p->vertice pertence a F//arestas (vertice1, p->vertice) que não compoem árvore geradora mínima}p=p->prox;}RetiraVerticeFila(F);}return0;}
Exemplo de Implementação em Object Pascal
programBusca_em_largura;{$APPTYPE CONSOLE}usesSysUtils;varvListaNos:array[1..8]ofchar;functionNoEsquerdo(pNoAtual:Integer):integer;beginresult:=(2*pNoAtual);end;functionNoDireito(pNoAtual:Integer):integer;beginresult:=(2*pNoAtual)+1;end;functionbusca_Largura(Inicio:integer;Alvo:Char):integer;varvAchou:Boolean;vLoop:integer;beginvAchou:=false;vLoop:=Inicio;Result:=-1;ifvListaNos[Inicio]=AlvothenbeginvAchou:=true;Result:=Inicio;end;while(notvAchou)and(vLoop<=8)dobeginifvListaNos[NoEsquerdo(vLoop)]=AlvothenbeginvAchou:=true;Result:=NoEsquerdo(vLoop);endelseifvListaNos[NoDireito(vLoop)]=AlvothenbeginvAchou:=true;Result:=NoDireito(vLoop);end;inc(vLoop);end;end;begin{ Busca em largura na árvore binária }// Preenchimento da arvore, demostração gráfica e posicionamento na mesma…vListaNos[1]:='R';{ R 1 }vListaNos[2]:='G';{ / \ / \ }vListaNos[3]:='Q';{ G Q 2 3 }vListaNos[4]:='Y';{ /\ /\ /\ /\ }vListaNos[5]:='J';{ Y J B E 4 5 6 7 }vListaNos[6]:='B';{ / / }vListaNos[7]:='E';{ P 8 }vListaNos[8]:='P';// Pesquisa por elementos na árvore…Writeln('A letra "J" esta no no numero: '+IntToStr(busca_Largura(2,'J')));Writeln('A letra "B" esta no no numero: '+IntToStr(busca_Largura(1,'B')));Writeln('A letra "R" esta no no numero: '+IntToStr(busca_Largura(1,'R')));Writeln('A letra "P" esta no no numero: '+IntToStr(busca_Largura(4,'P')));Writeln('A letra "Y" esta no no numero: '+IntToStr(busca_Largura(1,'Y')));Writeln('A letra "E" esta no no numero: '+IntToStr(busca_Largura(1,'E')));Writeln('A letra "Q" esta no no numero: '+IntToStr(busca_Largura(1,'Q')));Readln;end.
Uma implementação em Python usando um array bidimensional representando um mapa. Esta versão é usada para encontrar o caminho mais curto dentro de um grid.
defsolve(start_row,start_col):q=[]q.append((start_row,start_col))visited=[[Falseforiinrange(cols)]forjinrange(rows)]visited[start_row][start_col]=Trueprev=[[Noneforiinrange(cols)]forjinrange(rows)]whilelen(q)>0:row,col=q.pop(0)ifm[row][col]=='E':returnprev# Check adjacent cellsfordr,dcin[(-1,0),(1,0),(0,-1),(0,1)]:next_row=row+drnext_col=col+dcif(next_row>=0andnext_row<rowsandnext_col>=0andnext_col<colsandnotvisited[next_row][next_col]andm[next_row][next_col]!='#'):q.append((next_row,next_col))visited[next_row][next_col]=Trueprev[next_row][next_col]=(row,col)returnNonedefreconstructPath(start_row,start_col,end_row,end_col,prev):path=[]row,col=end_row,end_colwhile(row,col)!=(start_row,start_col):path.append((row,col))row,col=prev[row][col]path.append((start_row,start_col))path.reverse()returnpathdefbfs(start_row,start_col,end_row,end_col):prev=solve(start_row,start_col)ifprevisNone:print("Caminho não encontrado.")return[]returnreconstructPath(start_row,start_col,end_row,end_col,prev)m=[['S','.','.','.','.','.','.','.','.'],['.','#','#','.','#','.','#','#','.'],['.','.','.','.','#','.','.','.','.'],['#','#','.','.','.','#','#','#','#'],['.','.','.','.','.','.','.','.','.'],['.','#','#','#','#','.','.','#','.'],['.','.','.','.','#','.','.','.','E']]rows=len(m)cols=len(m[0])start_row=0start_col=0end_row=0end_col=0foriinrange(rows):forjinrange(cols):ifm[i][j]=='S':start_row=istart_col=jifm[i][j]=='E':end_row=iend_col=jprint("Labirinto:")forrowinm:print(' '.join(row))print("\nBuscando um caminho:")path=bfs(start_row,start_col,end_row,end_col)iflen(path)>0:print("Caminho encontrado:")forrow,colinpath:m[row][col]='P'forrowinm:print(' '.join(row))else:print("Caminho não encontrado.")
Usos e extensões
Achar componentes conectados.
Achar todos os nódulos contectado a apenas um componente.
Achar o menor caminho entre um nó raiz e os outros nós do grafo.
Testar bipartição em grafos.
O conjunto de nós alcançados pela busca em largura são os maiores componentes conectados que contém o nó inicial. Se não houver arestas nos nós adjacentes numa mesma camada de busca, então o grafo deve conter um número ímpar de ciclos e não ser bipartido.
↑Schrijver, Alexander (2012). «On the History of the Shortest Path Problem»(PDF). Deutsche Mathematiker-Vereinigung e.V., Berlin. Documenta Mathematica. Extra Volume ISMP (2012) (Extra Volume ISMP (2012)): 8. Consultado em 20 de julho de 2023