É 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á.
Isso significa que , então por que não dizemos que o tempo de execução é apenas ?
Isso surgiu nos comentários de uma pergunta sobre o tempo de execução do algoritmo do Disjkstra .
algorithm-analysis
search-algorithms
David Richerby
fonte
fonte
Respostas:
BFS é geralmente descrito algo como o seguinte (da Wikipedia ).
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Θ(|V|) |V| |E| O(|V|+|E|)
false
e 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 .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|+1 c+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.
fonte