Por que dizer que a primeira pesquisa de largura é executada no tempo ?


9

É frequentemente afirmado (por exemplo, na Wikipedia ) que o tempo de execução da pesquisa pela primeira vez (BFS) em um gráfico é . No entanto, qualquer gráfico conectado possui e, mesmo em um gráfico não conectado, o BFS nunca examinará um vértice fora do componente que contém o vértice inicial. Esse componente contém no máximo arestas, portanto contém no máximo vértices, e esses são os únicos que o algoritmo visitará.G=(V,E)O(|V|+|E|)|V||E|+1|E||E|+1

Isso significa que , então por que não dizemos que o tempo de execução é apenas ?|V|+|E|2|E|+1O(|E|)

Isso surgiu nos comentários de uma pergunta sobre o tempo de execução do algoritmo do Disjkstra .


Por que você supõe que existe um vértice inicial? O BFS no problema de correspondência máxima, por exemplo, inicia em todos os vértices incomparáveis ​​no algoritmo hopcroft karp. Neste caso, se o gráfico dada é floresta de muitos componentes conectados teremos mais vértices que edgers e vamos visitá-los todos
narek Bojikian

2
@narekBojikian Embora o BFS possa ser usado de várias maneiras, quando apresentado como um algoritmo autônomo, ele praticamente sempre tem um vértice inicial.
David Richerby

Respostas:


9

BFS é geralmente descrito algo como o seguinte (da Wikipedia ).

 1  procedure BFS(G,start_v):
 2      let Q be a queue
 3      label start_v as discovered
 4      Q.enqueue(start_v)
 5      while Q is not empty
 6          v = Q.dequeue()
 7          if v is the goal:
 8              return v
 9          for all edges from v to w in G.adjacentEdges(v) do
10             if w is not labeled as discovered:
11                 label w as discovered
12                 w.parent = v
13                 Q.enqueue(w)

A questão é um tanto sutil: está escondida na linha 3! A questão é: qual estrutura de dados vamos usar para armazenar quais vértices foram descobertos?

A solução mais simples é usar uma matriz booleana com uma entrada por vértice. Nesse caso, devemos inicializar todos os elementos da matriz falsee isso leva tempo . Isso se aplica a todos os gráficos, mesmo que não haja arestas, portanto, não podemos assumir nenhuma relação entree e temos um tempo de execução de .Θ(|V|)|V||E|O(|V|+|E|)

Podemos evitar ter uma estrutura de dados com o tempo de inicialização ? Nossa primeira tentativa pode ser usar uma lista vinculada. No entanto, agora testar se um vértice foi descoberto (linha 10) leva tempo linear no número de vértices visitados, em vez de tempo constante como antes. Isso significa que o tempo de execução se torna , o que é muito pior no pior caso. (Observe que não queremos reescrever isso como pois isso é ainda pior: pode ser tão ruim quanto , enquanto )Θ(|V|)O(|V||E|)O(|E|2)|V|4|V||E||V|3

O uso de uma matriz redimensionada dinamicamente nos permitiria manter a lista classificada, portanto, agora as pesquisas demorariam apenas mas isso ainda fornece um tempo de execução de apenas , ainda pior que o padrão.O(log|V|)O(|E|log|V|)

Finalmente, poderíamos usar uma tabela de hash de tamanho dinâmico: comece com uma tabela de tamanho constante  e dobre-a toda vez que ela ficar pela metade. Isso significa que o tamanho final da tabela é no máximo duas vezes o número de vértices descobertos antes do término do algoritmo, e isso é no máximo porque nunca descobrimos nada fora do componente do vértice inicial. Além disso, a quantidade total de trabalho realizado copiando a tabela de hash para expandi-la é no máximo. As pesquisas e inserções na tabela de hash são amortizadas portanto, obtemos um tempo de execução de .c|E|+1c+2c+4c++2|E|4|E| O(1)O(|E|)

Então é possível, mas você gostaria de fazer isso em uma implementação real? Eu diria provavelmente não. A menos que você tenha motivos para acreditar que seus gráficos de entrada terão muitos componentes pequenos, a sobrecarga de manutenção da tabela de hash adicionará um fator constante perceptível ao tempo de execução. Crescer a tabela de hash pode levar tempoe as pesquisas exigirão que você calcule a função hash e, em média, observe mais de um slot na tabela. O fraco desempenho do cache das tabelas de hash também pode prejudicá-lo em um computador real. Na maioria dos casos, com a implementação padrão da matriz, a parte é o termo dominante doO(|E|)4|E|O(|E|)O(|V|+|E|) tempo de execução, portanto, não vale a pena usar uma tabela de hash para remover o termo dominado, considerando o custo prático de fazer isso.


11
Eu acho que pode ser muito forte afirmar que, na prática, as tabelas de hash têm um desempenho ruim do cache. Se implementado com encadeamento (ou seja, listas vinculadas), eu concordo. Mas se implementado com um pedaço contínuo de memória e endereçamento aberto, não muito.
217 Juho

Resposta maravilhosa mesmo! Uma observação marginal, porém, as tabelas de hash de tamanho dinâmico são realmente uma boa escolha, não apenas se houver muitos componentes pequenos, mas também se o valor de hash para qualquer vértice for limitado por uma constante razoável e isso acontece com frequência. Boa resposta!
Carlos Linares López

11
David, eu tive pensamentos semelhantes anos atrás. Eu acho que a resposta está em perspectivas históricas.
Kelalaka
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.