Introdução
A teoria dos grafos é
um ramo da matemática que estuda as relações entre os objetos de um determinado
conjunto. Para tal são empregadas estruturas chamadas de grafos, G(V,A), onde V
é um conjunto não vazio de objetos denominados vértices e A é um conjunto de
pares não ordenados de V, chamado arestas.
Dependendo da
aplicação, arestas podem ou não ter direção, pode ser permitido ou não arestas
ligarem um vértice a ele próprio e vértices e/ou arestas podem ter um peso
(numérico) associado. Se as arestas têm uma direção associada (indicada por uma
seta na representação gráfica) temos um grafo direcionado, grafo orientado ou
digrafo. Um grafo com um único vértice e sem arestas é conhecido como o grafo
trivial.
Estruturas que podem
ser representadas por grafos estão em toda parte e muitos problemas de
interesse prático podem ser formulados como questões sobre certos grafos. Por
exemplo, a estrutura de links da Wikipedia pode ser representada por um
dígrafo: os vértices são os artigos da Wikipedia e existe uma aresta do artigo
A para o artigo B se e somente se A contém um link para B. Dígrafos são também
usados para representar máquinas de estado finito. O desenvolvimento de
algoritmos para manipular grafos é um importante tema da ciência da computação.
A teoria dos grafos é
um ramo da matemática que estuda as relações entre os objetos de um determinado
conjunto. Para tal são empregadas estruturas chamadas de grafos, G(V,A), onde V
é um conjunto não vazio de objetos denominados vértices e A é um conjunto de
pares não ordenados de V, chamado arestas.
Dependendo da
aplicação, arestas podem ou não ter direção, pode ser permitido ou não arestas
ligarem um vértice a ele próprio e vértices e/ou arestas podem ter um peso
(numérico) associado. Se as arestas têm uma direção associada (indicada por uma
seta na representação gráfica) temos um grafo direcionado, grafo orientado ou
digrafo. Um grafo com um único vértice e sem arestas é conhecido como o grafo
trivial.
Estruturas que podem
ser representadas por grafos estão em toda parte e muitos problemas de
interesse prático podem ser formulados como questões sobre certos grafos. Por
exemplo, a estrutura de links da Wikipedia pode ser representada por um
dígrafo: os vértices são os artigos da Wikipedia e existe uma aresta do artigo
A para o artigo B se e somente se A contém um link para B. Dígrafos são também
usados para representar máquinas de estado finito. O desenvolvimento de
algoritmos para manipular grafos é um importante tema da ciência da computação.
Caminho mínimo
Na teoria de grafos, o problema
do caminho mínimo consiste na minimização do custo de travessia de um grafo
entre dois nós (ou vértices); custo este dado pela soma dos pesos de cada
aresta percorrida.
Os algoritmos especializados em
solucionar o problema do caminho mínimo são eventualmente chamados de
algoritmos de busca de caminhos. Entre os algoritmos dessa classe, os mais
conhecidos são:
- Algoritmo de Dijkstra
- Algoritmo de Bellman-Ford
- Algoritmo A*
- Algoritmo de Johnson
- Algoritmo de Prim
Na teoria de grafos, o problema
do caminho mínimo consiste na minimização do custo de travessia de um grafo
entre dois nós (ou vértices); custo este dado pela soma dos pesos de cada
aresta percorrida.
Os algoritmos especializados em
solucionar o problema do caminho mínimo são eventualmente chamados de
algoritmos de busca de caminhos. Entre os algoritmos dessa classe, os mais
conhecidos são:
- Algoritmo de Dijkstra
- Algoritmo de Bellman-Ford
- Algoritmo A*
- Algoritmo de Johnson
- Algoritmo de Prim
Algoritmo de
Dijkstra
O algoritmo de Dijkstra,
concebido pelo cientista da computação holandês Edsger Dijkstra em 1956 e
publicado em 19591 2 , soluciona o problema do caminho mais curto num grafo
dirigido ou não dirigido com arestas de peso não negativo, em tempo
computacional O([m+n]log n) onde m é o número de arestas e n é o número de
vértices. O algoritmo que serve para resolver o mesmo problema em um grafo com
pesos negativos é o algoritmo de Bellman-Ford, que possui maior tempo de
execução que o Dijkstra.
O algoritmo de Dijkstra
assemelha-se ao BFS, mas é um algoritmo guloso, ou seja, toma a decisão que
parece ótima no momento. Para a teoria dos grafos uma "estratégia
gulosa" é conveniente já que sendo P um menor caminho entre 2 vértices U e
V, todo sub-caminho de P é um menor caminho entre 2 vértices pertencentes ao
caminho P, desta forma construímos os melhores caminhos dos vértices
alcançáveis pelo vértice inicial determinando todos os melhores caminhos
intermediários. Nota: diz-se 'um menor caminho' pois caso existam 2 'menores
caminhos' apenas um será descoberto.
O algoritmo considera um conjunto
S de menores caminhos, iniciado com um vértice inicial I. A cada passo do
algoritmo busca-se nas adjacências dos vértices pertencentes a S aquele vértice
com menor distância relativa a I e adiciona-o a S e, então, repetindo os passos
até que todos os vértices alcançáveis por I estejam em S. Arestas que ligam
vértices já pertencentes a S são desconsideradas.
O algoritmo de Dijkstra não
consegue encontrar o menor caminho em um grafo com pesos negativos. Para esse
propósito, pode-se usar o algoritmo de Floyd-Warshall, que consegue descobrir a
menor distância entre todos os pares de vértices de qualquer grafo sem ciclos
com peso negativo em uma complexidade de tempo O(V³). Se o problema não exigir
o cálculo da distância entre todos os pares de vértices ou se existirem ciclos
com peso negativo, pode-se aplicar o algoritmo de Bellman-Ford, com complexidade
de tempo O(V*E). Em uma árvore, é possível encontrar a distância entre um
vértice inicial e todos os outros vértices em tempo O(V+E), utilizando busca em
profundidade (também conhecida como DFS). Em um grafo cujas arestas têm todas o
mesmo peso, pode-se encontrar a distância entre um vértice inicial e todos os
outros vértices, para um grafo qualquer, em O(V+E), utilizando busca em largura
(também conhecida como BFS). O processo utilizado no algoritmo de Dijkstra é
bastante similar ao processo usado no algoritmo de Prim. O propósito deste
último, entretanto, é encontrar a árvore geradora mínima que conecta todos os
nós de um grafo.
O algoritmo de Dijkstra,
concebido pelo cientista da computação holandês Edsger Dijkstra em 1956 e
publicado em 19591 2 , soluciona o problema do caminho mais curto num grafo
dirigido ou não dirigido com arestas de peso não negativo, em tempo
computacional O([m+n]log n) onde m é o número de arestas e n é o número de
vértices. O algoritmo que serve para resolver o mesmo problema em um grafo com
pesos negativos é o algoritmo de Bellman-Ford, que possui maior tempo de
execução que o Dijkstra.
O algoritmo de Dijkstra
assemelha-se ao BFS, mas é um algoritmo guloso, ou seja, toma a decisão que
parece ótima no momento. Para a teoria dos grafos uma "estratégia
gulosa" é conveniente já que sendo P um menor caminho entre 2 vértices U e
V, todo sub-caminho de P é um menor caminho entre 2 vértices pertencentes ao
caminho P, desta forma construímos os melhores caminhos dos vértices
alcançáveis pelo vértice inicial determinando todos os melhores caminhos
intermediários. Nota: diz-se 'um menor caminho' pois caso existam 2 'menores
caminhos' apenas um será descoberto.
O algoritmo considera um conjunto
S de menores caminhos, iniciado com um vértice inicial I. A cada passo do
algoritmo busca-se nas adjacências dos vértices pertencentes a S aquele vértice
com menor distância relativa a I e adiciona-o a S e, então, repetindo os passos
até que todos os vértices alcançáveis por I estejam em S. Arestas que ligam
vértices já pertencentes a S são desconsideradas.
O algoritmo de Dijkstra não
consegue encontrar o menor caminho em um grafo com pesos negativos. Para esse
propósito, pode-se usar o algoritmo de Floyd-Warshall, que consegue descobrir a
menor distância entre todos os pares de vértices de qualquer grafo sem ciclos
com peso negativo em uma complexidade de tempo O(V³). Se o problema não exigir
o cálculo da distância entre todos os pares de vértices ou se existirem ciclos
com peso negativo, pode-se aplicar o algoritmo de Bellman-Ford, com complexidade
de tempo O(V*E). Em uma árvore, é possível encontrar a distância entre um
vértice inicial e todos os outros vértices em tempo O(V+E), utilizando busca em
profundidade (também conhecida como DFS). Em um grafo cujas arestas têm todas o
mesmo peso, pode-se encontrar a distância entre um vértice inicial e todos os
outros vértices, para um grafo qualquer, em O(V+E), utilizando busca em largura
(também conhecida como BFS). O processo utilizado no algoritmo de Dijkstra é
bastante similar ao processo usado no algoritmo de Prim. O propósito deste
último, entretanto, é encontrar a árvore geradora mínima que conecta todos os
nós de um grafo.
Algoritmo de
Bellman-Ford
O Algoritmo de Bellman-Ford é um
algoritmo de busca de caminho mínimo em um dígrafo ponderado, ou seja, cujas
arestas têm peso, inclusive negativo. O Algoritmo de Dijkstra resolve o mesmo
problema, em um tempo menor, porém exige que todas as arestas tenham pesos
positivos. Portanto, o algoritmo de Bellman-Ford é normalmente usado apenas
quando existem arestas de peso negativo.
O Algoritmo de Bellman-Ford é um
algoritmo de busca de caminho mínimo em um dígrafo ponderado, ou seja, cujas
arestas têm peso, inclusive negativo. O Algoritmo de Dijkstra resolve o mesmo
problema, em um tempo menor, porém exige que todas as arestas tenham pesos
positivos. Portanto, o algoritmo de Bellman-Ford é normalmente usado apenas
quando existem arestas de peso negativo.
Algoritmo A*
Algoritmo A* é um algoritmo para
Busca de Caminho. Ele busca o caminho em um grafo de um vértice inicial até um
vértice final. Ele é a combinação de aproximações heurísticas como do algoritmo
Best-first Search e da formalidade do Algoritmo de Dijkstra.
O algoritmo foi descrito pela
primeira vez em 1968 por Peter Hart, Nils Nilsson, e Bertram Raphael. Na
publicação deles, ele foi chamado de algoritmo A; usando este algoritmo com uma
heurística apropriada atinge-se um comportamento ótimo, e passou a ser
conhecido por A*.
Sua aplicação vai desde aplicativos para encontrar rotas de
deslocamento entre localidades a resolução de problemas, como a resolução de um
quebra-cabeças. Ele é muito usado em jogos.
Algoritmo A* é um algoritmo para
Busca de Caminho. Ele busca o caminho em um grafo de um vértice inicial até um
vértice final. Ele é a combinação de aproximações heurísticas como do algoritmo
Best-first Search e da formalidade do Algoritmo de Dijkstra.
O algoritmo foi descrito pela
primeira vez em 1968 por Peter Hart, Nils Nilsson, e Bertram Raphael. Na
publicação deles, ele foi chamado de algoritmo A; usando este algoritmo com uma
heurística apropriada atinge-se um comportamento ótimo, e passou a ser
conhecido por A*.
Sua aplicação vai desde aplicativos para encontrar rotas de
deslocamento entre localidades a resolução de problemas, como a resolução de um
quebra-cabeças. Ele é muito usado em jogos.
Algoritmo de Johnson
Algoritmo de Johnson é uma forma
de encontrar o menor caminho entre dois pontos. Ele permite que algumas bordas
tenham número negativo, mas ciclos negativos não devem existir. Este algoritmo
trabalha com base no Algoritmo de Bellman-Ford, para computar uma transformação
de um grafo de entrada, que remove todos os pesos negativos, permitindo o uso
do algoritmo de Dijkstra no grafo transformado. Recebe esse nome em homenagem a
Donald B. Johnson, o primeiro a descrevê-lo, em 1977.
Algoritmo de Johnson é uma forma
de encontrar o menor caminho entre dois pontos. Ele permite que algumas bordas
tenham número negativo, mas ciclos negativos não devem existir. Este algoritmo
trabalha com base no Algoritmo de Bellman-Ford, para computar uma transformação
de um grafo de entrada, que remove todos os pesos negativos, permitindo o uso
do algoritmo de Dijkstra no grafo transformado. Recebe esse nome em homenagem a
Donald B. Johnson, o primeiro a descrevê-lo, em 1977.
Algoritmo de Plim
O algoritmo de Prim é um
algoritmo guloso (greedy algorithm) empregado para encontrar uma árvore
geradora mínima (minimal spanning tree) num grafo conectado, valorado e não
direcionado. Isso significa que o algoritmo encontra um subgrafo do grafo
original no qual a soma total das arestas é minimizada e todos os vértices
estão interligados. O algoritmo foi desenvolvido em 1930 pelo matemático
Vojtěch Jarník e depois pelo cientista da computação Robert Clay Prim em 1957 e
redescoberto por Edsger Dijkstra em 1959.
Outros algoritmos conhecidos para
encontrar árvores geradoras mínimas são o algoritmo de Kruskal e algoritmo de
Boruvka. No entanto estes algoritmos podem ser empregados em grafos desconexos,
enquanto o algoritmo de Prim precisa de um grafo conexo.
O algoritmo de Prim é um
algoritmo guloso (greedy algorithm) empregado para encontrar uma árvore
geradora mínima (minimal spanning tree) num grafo conectado, valorado e não
direcionado. Isso significa que o algoritmo encontra um subgrafo do grafo
original no qual a soma total das arestas é minimizada e todos os vértices
estão interligados. O algoritmo foi desenvolvido em 1930 pelo matemático
Vojtěch Jarník e depois pelo cientista da computação Robert Clay Prim em 1957 e
redescoberto por Edsger Dijkstra em 1959.
Outros algoritmos conhecidos para
encontrar árvores geradoras mínimas são o algoritmo de Kruskal e algoritmo de
Boruvka. No entanto estes algoritmos podem ser empregados em grafos desconexos,
enquanto o algoritmo de Prim precisa de um grafo conexo.
O Problema a ser
resolvido
Imagine que uma transportadora
decidiu alavancar as suas entregas e fez uma promoção de 50% em todas as
entregas da cidade A até a cidade F. Para que ela não tenha prejuízo ela terá
sempre que percorrer o menor caminho entre as cidades. Como fazer isso?
·
Problema:
o Obter
Caminhos interligando Vértices de um Grafo, cujo comprimento (Custo)
seja Mínimo.
·
Implementações:
o Algoritmo
de Dijkstra
·
Aplicação:
o Redes
de Computadores (Percurso entre Roteadores)
o Tráfego
Urbano.
o Sistemas
Rodoviários, Ferroviários e Aéreos,
o Importante
para vários outros problemas
Será
resolvido com o algoritmo de Dijkstra melhor algoritmo conhecido aplicado a
grafos com pesos não negativos. Passo-a-passo a seguir:
•
Encontra os caminhos mais curtos de acordo com a
distância a uma dada fonte
•
Primeiro,encontra o caminho mais curto da
fonte ao vértice mais próximo e assim por diante.
•
Em geral,
antes da i-ésima iteração iniciar, o algoritmo já encontrou os menores caminhos
para os outros i-1 vértices próximos da fonte
•
Estes vértices, a fonte, e as arestas dos
caminhos mais curtos formam uma subárvore Ti.
•
Como
todos os pesos das arestas são não negativos, o vértice seguinte mais próximo
da fonte pode ser encontrado dentre os vértices adjacentes aos vértices de Ti.
•
Para
identificar o i-ésimo vértice mais próximo, o algoritmo calcula, para cada
vértice candidato u, a soma da distância ao vértice da árvore v mais próximo e
o comprimento dv do caminho mais curto da fonte à v e então seleciona o vértice
cuja soma seja a menor.
•
Cada
vértice possui duas etiquetas:
•
Um valor numérico d que indica o comprimento do
caminho mais curto da fonte ao vértice encontrado pelo algoritmo até o momento.
Quando um vértice for adicionado a árvore, d indica o comprimento do caminho
mais curto da fonte até o vértice.
•
A outra etiqueta indica o nome do próximo ao
último vértice neste caminho, i.e., o pai do vértice na árvore sendo
construída.
•
Com estas etiquetas, encontrar o vértice mais
próximo u* seguinte torna-se uma tarefa simples que consiste em encontrar o
vértice candidato com o menor valor de d.
•
Após
identificado o vértice u* a ser adicionado a árvore, as seguintes operações
devem ser realizadas:
•
Mover o
vértice u* do vértice candidato para o conjunto dos vértices da árvore
•
Para cada vértice candidato remanescente u que
estiver conectado a u* por uma aresta de pesos(u*,u) tal que du* + w(u*,u) <
dw, atualize as etiquetas de u por u* e du* + w(u*,u) respectivamente.
•
Dijkstra
compara comprimento de caminhos
•
Algoritmo de Dijkstra
•
topologia da rede, custos dos enlaces conhecidos
por todos os nós
•
realizado através de “difusão do estado dos
enlaces”
•
todos os nós têm mesma info.
•
calcula caminhos de menor custo de um nó
(“origem”) para todos os demais
•
gera tabela de rotas para aquele nó
•
iterativo: depois de k iterações, sabemos menor
custo p/ k destinos
•
Notação:
•
c(i,j): custo do enlace do nó i ao nó j. custo é
infinito se não forem vizinhos diretos
•
D(V): valor corrente do custo do caminho da
origem ao destino V
•
p(V): nó antecessor no caminho da origem ao nó V
•
N: conjunto de nós cujo caminho de menor custo
já foi determinado
Resumo:
1
Inicialização:
2
N = {A}
3
para todos os nós V
4
se V for adjacente ao nó A
5
então D(V) = c(A,V)
6
senão D(V) = infinito
7
Repete
8
determina W não contido em N tal que D(W) é o
mínimo
9
adiciona W ao conjunto N
10 atualiza
D(V) para todo V adjacente ao nó W e ainda não em N:
11
D(V)
= min( D(V), D(W) + c(W,V) )
12 /*
novo custo ao nó V ou é o custo velho a V ou o custo do
13 menor
caminho ao nó W, mais o custo de W a V */
14 até
que todos nós estejam em N
A eficiência do algoritmo de
Dijkstra depende das estruturas de dados usadas na implementação da fila de
prioridade e do grafo.
Este algoritmo parte de uma
estimativa inicial para o custo mínimo e vai sucessivamente ajustando esta
estimativa. Ele considera que um vértice estará fechado quando já tiver sido
obtido um caminho de custo mínimo do vértice tomado como raiz da busca até ele.
Caso contrário ele dito estar aberto.
Imagine que uma transportadora
decidiu alavancar as suas entregas e fez uma promoção de 50% em todas as
entregas da cidade A até a cidade F. Para que ela não tenha prejuízo ela terá
sempre que percorrer o menor caminho entre as cidades. Como fazer isso?
·
Problema:
o Obter
Caminhos interligando Vértices de um Grafo, cujo comprimento (Custo)
seja Mínimo.
·
Implementações:
o Algoritmo
de Dijkstra
·
Aplicação:
o Redes
de Computadores (Percurso entre Roteadores)
o Tráfego
Urbano.
o Sistemas
Rodoviários, Ferroviários e Aéreos,
o Importante
para vários outros problemas
Será
resolvido com o algoritmo de Dijkstra melhor algoritmo conhecido aplicado a
grafos com pesos não negativos. Passo-a-passo a seguir:
•
Encontra os caminhos mais curtos de acordo com a
distância a uma dada fonte
•
Primeiro,encontra o caminho mais curto da
fonte ao vértice mais próximo e assim por diante.
•
Em geral,
antes da i-ésima iteração iniciar, o algoritmo já encontrou os menores caminhos
para os outros i-1 vértices próximos da fonte
•
Estes vértices, a fonte, e as arestas dos
caminhos mais curtos formam uma subárvore Ti.
•
Como
todos os pesos das arestas são não negativos, o vértice seguinte mais próximo
da fonte pode ser encontrado dentre os vértices adjacentes aos vértices de Ti.
•
Para
identificar o i-ésimo vértice mais próximo, o algoritmo calcula, para cada
vértice candidato u, a soma da distância ao vértice da árvore v mais próximo e
o comprimento dv do caminho mais curto da fonte à v e então seleciona o vértice
cuja soma seja a menor.
•
Cada
vértice possui duas etiquetas:
•
Um valor numérico d que indica o comprimento do
caminho mais curto da fonte ao vértice encontrado pelo algoritmo até o momento.
Quando um vértice for adicionado a árvore, d indica o comprimento do caminho
mais curto da fonte até o vértice.
•
A outra etiqueta indica o nome do próximo ao
último vértice neste caminho, i.e., o pai do vértice na árvore sendo
construída.
•
Com estas etiquetas, encontrar o vértice mais
próximo u* seguinte torna-se uma tarefa simples que consiste em encontrar o
vértice candidato com o menor valor de d.
•
Após
identificado o vértice u* a ser adicionado a árvore, as seguintes operações
devem ser realizadas:
•
Mover o
vértice u* do vértice candidato para o conjunto dos vértices da árvore
•
Para cada vértice candidato remanescente u que
estiver conectado a u* por uma aresta de pesos(u*,u) tal que du* + w(u*,u) <
dw, atualize as etiquetas de u por u* e du* + w(u*,u) respectivamente.
•
Dijkstra
compara comprimento de caminhos
•
Algoritmo de Dijkstra
•
topologia da rede, custos dos enlaces conhecidos
por todos os nós
•
realizado através de “difusão do estado dos
enlaces”
•
todos os nós têm mesma info.
•
calcula caminhos de menor custo de um nó
(“origem”) para todos os demais
•
gera tabela de rotas para aquele nó
•
iterativo: depois de k iterações, sabemos menor
custo p/ k destinos
•
Notação:
•
c(i,j): custo do enlace do nó i ao nó j. custo é
infinito se não forem vizinhos diretos
•
D(V): valor corrente do custo do caminho da
origem ao destino V
•
p(V): nó antecessor no caminho da origem ao nó V
•
N: conjunto de nós cujo caminho de menor custo
já foi determinado
Nenhum comentário:
Postar um comentário