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
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...
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...