Caminho mais curto a partir de um nó Algoritmos de Dijkstra e

Transcrição

Caminho mais curto a partir de um nó Algoritmos de Dijkstra e
Caminho mais curto a partir de um nó
Algoritmos de Dijkstra e Bellman-Ford
Fernando Lobo
Algoritmos e Estrutura de Dados II
1 / 28
Caminho mais curto a partir de um nó
I
Input: Um grafo com pesos nos arcos G = (V , E ), w : E → R
e um nó de partida s.
I
Output: Caminho mais curto de s para todos os outros nós do
grafo.
I
O peso (ou distância ou custo) de um caminho é igual ao
somatório dos pesos dos arcos que constituem o caminho, ou
∞ se não existir caminho.
I
Formalmente, seja p o caminho v0 ; vk = hv0 , v1 , . . . , vk i.
peso(p) =
k
X
w (vi−1 , vi )
i=1
2 / 28
O problema tem subestrutura óptima
I
Qualquer subcaminho de um caminho mais curto, também é
um caminho mais curto.
I
Demonstração: Por redução ao absurdo.
I
Seja p um caminho mais curto de u para v .
p passa pelos nós x e y (ver figura).
I
peso(p) = peso(Pux ) + peso(Pxy ) + peso(Pyv )
3 / 28
I
Vamos mostrar que Pxy é forçosamente um caminho mais
curto de x para y .
I
Suponhamos que existe um caminho estritamente mais curto
0 entre x e y . Isto é, peso(P 0 ) < peso(P )
Pxy
xy
xy
I
Então poderı́amos construir o caminho p 0 do seguinte modo:
0
peso(p 0 ) = peso(Pux ) + peso(Pxy
) + peso(Pyv )
< peso(Pux ) + peso(Pxy ) + peso(Pyv )
= peso(p)
I
=⇒ p não é um caminho mais curto. Uma contradição.
4 / 28
Relaxamento de arcos
I
Os algoritmos que vamos ver usam a técnica de relaxamento.
I
Cada nó v mantém um atributo, v .d, que nos dá um limite
superior do peso de um caminho mais curto de s para v .
I
A técnica de relaxamento para um arco (u, v ) consiste em
testar se é possı́vel melhorar o caminho mais curto para v
passando por u, e em caso afirmativo, actualizar v .d.
5 / 28
Pseudocódigo
Relax(u, v , w )
if v .d > u.d + w (u, v )
v .d = u.d + w (u, v )
v .π = u
Ao inı́cio, o atributo v .d = ∞ para todos os nós excepto para o
nó de partida s, em que s.d = 0.
Initialize-Single-Source(G , s)
for each v ∈ G .V
v .d = ∞
v .π = nil
s.d = 0
6 / 28
Algoritmo de Dijkstra
I
Restrição: Os pesos não podem ser negativos.
I
Parecido com BFS (e também com Algoritmo de Prim).
I
I
Usa uma fila com prioridade em vez de uma fila FIFO.
I
Chave (prioridade) de um nó na fila é o limite superior do
custo do caminho mais curto desde o nó de origem.
O algoritmo mantém dois conjuntos de nós:
I
S = nós para os quais já determinamos o caminho mais curto.
I
Q = V − S (nós que ainda estão na fila).
7 / 28
Pseudocódigo
Dijkstra(G , w , s)
Initialize-Single-Source(G , s)
S =∅
Q = G .V
while Q 6= ∅
u = Extract-Min(Q)
S = S ∪ {u}
for each v ∈ G .Adj[u]
Relax(u, v , w )
I
No final do algoritmo, v .d tem o peso (custo) do caminho
mais curto do nó de origem s até v .
I
Tal como no BFS, o atributo π permite-nos obter o caminho
mais curto.
8 / 28
Exemplo
Nó de origem é A
9 / 28
Inicialização
1a iteração: nó A sai da fila
10 / 28
2a iteração: nó C sai da fila
3a iteração: nó E sai da fila
11 / 28
4a iteração: nó B sai da fila
12 / 28
5a iteração: nó D sai da fila
13 / 28
Complexidade do Algoritmo de Dijkstra
I
Inicialização: Θ(V )
I
Ciclo while é executado |V | vezes.
I
|V | Extract-Mins
I
Todos os arcos do grafo são visitados. Para cada um, há
potencialmente um Decrease-Key.
I
I
=⇒ Θ(V · TExtract-Min + E · TDecrease-Key )
Se a fila com prioridade for implementada com um heap
binário (ver matéria de AED-I), obtemos:
I
TExtract-Min = O(lg V )
I
TDecrease-Key = O(lg V )
14 / 28
Complexidade do Algoritmo de Dijkstra
I
Total = O(V lg V + E lg V ) = O ((V + E ) lg V )
I
Igual à complexidade do Algoritmo de Prim.
I
Consegue-se melhorar a complexidade implementando a fila
com prioridade com heaps de Fibonacci.
15 / 28
Algoritmo de Bellman-Ford
I
O Algoritmo de Dijkstra só funciona se todas as arestas do
grafo tiverem pesos ≥ 0.
I
Agora vamos ver um algoritmo que também funciona no caso
de haver arestas com < 0.
16 / 28
Exemplo
I
Caminho mais curto de A para D tem peso -1.
A→B→E→D
I
O algoritmo de Dijkstra daria: A → B → D (com peso 1)
17 / 28
Algumas observações
I
Caminho mais curto não pode ter mais do que |V | − 1 arcos.
I
Caso contrário teria de haver forçosamente um ciclo no
caminho mais curto.
I
ciclo com peso < 0 =⇒ não existe caminho mais curto.
I
ciclo com peso > 0 nunca pode fazer parte de um caminho
mais curto.
I
ciclo com peso = 0 pode ser sempre removido.
18 / 28
Algoritmo de Bellman-Ford
I
Algoritmo de Bellman-Ford tira partido das observações
anteriores.
I
Tal como o Algoritmo de Dijkstra, também usa a técnica de
relaxamento dos arcos.
I
É conceptualmente mais simples que o Algoritmo de Dijkstra,
mas a complexidade temporal é maior.
19 / 28
Pseudocódigo
Bellman-Ford(G , w , s)
Initialize-Single-Source(G , s)
for i = 1 to |G .V | − 1
for each (u, v ) ∈ G .E
Relax(u, v , w )
for each (u, v ) ∈ G .E
if v .d > u.d + w (u, v )
return false
return true
———————————————
I
Retorna true se e só se o grafo não tiver um ciclo com peso
negativo que possa ser alcançado a partir do nó de origem s.
I
Complexidade: Θ(V · E )
20 / 28
Exemplo de execução. Nó de origem: A
21 / 28
I
Ordem pela qual o algoritmo processa os arcos:
#1 → (B,E)
#2 → (D,B)
#3 → (B,D)
#4 → (A,B)
#5 → (A,C)
#6 → (D,C)
#7 → (B,C)
#8 → (E,D)
I
Qualquer outra ordem servia.
22 / 28
i =1
i =2
A
0
0
0
0
0
0
0
B
∞
-1
-1
-1
-1
-1
-1
C
∞
∞
4
2
2
2
2
D
∞
∞
∞
∞
∞
1
-1
E
∞
∞
∞
∞
2
2
2
// A → B
// A → C
// B → C
// B → E
// B → D
// E → D
i =3
i =4
Neste caso nem era necessário fazermos a iteração i = 4 porque na
iteração i = 3 já não foi possı́vel relaxar qualquer arco.
23 / 28
Correcção do algoritmo
I
Definição: δ(s, v ) é o peso de um caminho mais curto de s
para v .
min{w (p) : s ; v }
δ(s, v ) =
∞ se não existir caminho de s para v
I
Se G = (V , E ) não tiver ciclos com peso negativo, então ao
terminar a execução do algoritmo Bellman-Ford,
v .d = δ(s, v ), ∀v ∈V
24 / 28
Demonstração
I
Seja v ∈ V um qualquer nó do grafo. Consideremos um
caminho mais curto p de s para v .
I
Como p é um caminho mais curto,
δ(s, vi ) = δ(s, vi−1 ) + w (vi−1 , vi )
I
Porquê? Devido à subestrutura óptima.
25 / 28
Demonstração (cont.)
I
Após a inicialização,
v0 .d = 0 = δ(s, v0 )
e nunca mais vai ser alterado. Porquê? Porque isso implicaria
haver ciclos com peso negativo.
I
Após a 1a iteração (i = 1): v1 .d = δ(s, v1 )
I
Após a 2a iteração (i = 2): v2 .d = δ(s, v2 )
I
...
I
Após a k a iteração (i=k): vk .d = δ(s, vk )
26 / 28
Demonstração (cont.)
I
Como G não tem ciclos com peso negativo, o caminho mais
curto p é um caminho simples e não poderá ter mais do que
|V | − 1 arcos (daı́ o ciclo exterior ser executado |V | − 1
vezes).
27 / 28
Corolário
I
I
Se no final das |V | − 1 iterações existir algum v .d 6= δ(s, v ),
então é porque existe um ciclo com peso negativo em G que
pode ser alcançado a partir de s.
=⇒ não existe caminho mais curto de s para v .
28 / 28

Documentos relacionados

Minimum Spanning Tree Algoritmos de Prim e Kruskal Algoritmo de

Minimum Spanning Tree Algoritmos de Prim e Kruskal Algoritmo de Ao contrário do algoritmo de Prim, o algoritmo de Kruskal mantém ums floresta. Inicialmente a floresta tem |V | árvores, uma para cada nó. No final, a floresta só tem uma árvore que é uma Mi...

Leia mais

Grafos - Caminhos mais curtos de todos os pares

Grafos - Caminhos mais curtos de todos os pares Se k não é um vértice intermediário de p, então, todos os vértices intermediários de p estão em {1, 2, . . . , k − 1}. Deste modo, um caminho mais curto i j com todo os vértices intermedia...

Leia mais