Você está na página 1de 7

2 - BUSCA DE CAMINHO MAIS CURTO

Algoritmo de Dijkstra

Descrio do algoritmo

O algoritmo de Dijkstra identifica, a partir de um vrtice do grafo, qual o custo mnimo entre
esse vrtice e todos os outros do grafo. No incio, o conjunto S contm somente esse vrtice,
chamado origem. A cada passo, selecionamos no conjunto de vrtices sobrando, o que o
mais perto da origem. Depois atualizamos, para cada vrtice sobrando, a sua distncia em
relao origem. Se passando pelo novo vrtice acrescentado, a distncia fica menor, essa
nova distncia que ser memorizada.

Suponhamos que o grafo representado por uma matriz de adjacncia onde temos o valor
se no existe aresta entre dois vrtices. Suponhamos tambm que os vrtices do grafo so
enumerados de 1 at n, isto , o conjunto de vrtices N = {1, 2, ..., n}. Utilizaremos tambm
um vetor D[2..n] que conter a distncia que separa todo vrtice do vrtice 1 (o vrtice do grafo
que o vrtice 1 escolhido arbitrariamente). Eis o algoritmo:

funo Dijkstra(L = [1..n, 1..n]: grafo): vetor[2..n]


C := {2,3,...,n} {Implicitamente S = N - C}
Para i := 2 at n:
D[i] := L[1,i]
Repetir n-2 vezes:
v := Elemento de C que minimiza D[v]
C := C - {v}
Para cada elemento w de C:
D[w] := min(D[w],D[v]+ L[v,w])
Retornar D

Para entender melhor o algoritmo, considere o grafo direcionado ilustrado na figura 1.

Figura 1

As etapas da execuo do algoritmo so as seguintes:

Passo v C D
Incio - {2,3,4,5} [50,30,100,10]
1 5 {2,3,4} [50,30,20,10]
2 4 {2,3} [40,30,20,10]
3 3 {2} [35,30,20,10]
Da maneira que o algoritmo foi apresentado, obtemos o custo do caminho mnimo para
qualquer vrtices, mas no obtemos o caminho mnimo. Para identificar esse caminho mnimo,
s acrescentar mais um vetor P[2..n], onde P[v] indica o vrtice que precede v no caminho
mais curto. A modificao do algoritmo para obter esse vetor simples. Primeiro devemos
inicializar esse vetor. Segundo, no mesmo momento que o vetor D atualizado, atualizamos
tambm o vetor P. O algoritmo modificado o seguinte:

funo Dijkstra(L = [1..n, 1..n]: grafo): vetor[2..n]


C := {2,3,...,n}
Para i := 2 at n:
D[i] := L[1,i]
P[i] := 1
Repetir n-2 vezes:
v := Elemento de C que minimiza D[v]
C := C - {v}
Para cada elemento w de C:
Se D[w] > D[v]+ L[v,w] ento
D[w] := D[v]+ L[v,w]
P[w] := v
Retornar P

Com esse algoritmo modificado, as etapas da execuo so as seguintes:

Passo v C D P
Incio - {2,3,4,5} [50,30,100,10] [1,1,1,1]
1 5 {2,3,4} [50,30,20,10] [1,1,5,1]
2 4 {2,3} [40,30,20,10] [4,1,5,1]
3 3 {2} [35,30,20,10] [3,1,5,1]

Vemos que o estado final do vetor P [3,1,5,1]. Para saber qual o caminho mais curto entre
os vrtices 1 e 2, procuramos o valor na posio 2 desse vetor (no esquea que P e D so
indexados a partir de 2). O vetor indica que o ltimo vrtice antes do vrtice 2 o vrtice 3.
Repetimos de novo o mesmo processo para ver o caminho mais curto entre 1 e 3. No vetor, na
posio 3, temos o valor 1, que a origem. Ento, o caminho mais curto 1,3,2.

Prova que o algoritmo funciona corretamente

Para efetuar a prova, vamos primeiro definir o conceito de caminho especial mais curto. Seja
v1 a origem. Para qualquer vrtice vi que no faz parte do conjunto S, o caminho especial mais
curto o caminho mais curto para alcanar vi a partir de v1 e que passa somente por vrtices
do conjunto S.

Para provar que o algoritmo funciona corretamente, so provar o seguinte:

1. Se um vrtice i pertence ao conjunto S, ento D[i] contm o comprimento do caminho


mais curto entre a origem e i.
2. Se um vrtice i no faz parte do conjunto S, ento D[i] contm o comprimento do
caminho especial mais curto entre a origem e i.
A prova por induo. Inicialmente os valores no vetor D contm o valor da aresta que liga
cada vrtice ao vrtice 1 (valor infinito se no tem aresta). Nenhum dos vrtices, com a
exceo do vrtice 1, faz parte de S. Portanto, trivial conferir que as duas propriedade so
verdadeiras nesse caso. Isso a base da induo. A hiptese de induo supe que as duas
condies so verdadeiras imediatamente antes de acrescentar um novo vrtice v no conjunto
S. Vamos ver agora o que acontece quando acrescentamos v no conjunto S.
1. Para cada vrtice que j estava no conjunto S antes de acrescentar v, a primeira
propriedade continua verdadeira. Por hiptese de induo, j sabemos que D[v] a
distncia do caminho especial mais curto at v. Se conseguirmos provar que o caminho
mais curto at v passa somente por vrtices do conjunto S, teremos a prova que D[v]
o valor do caminho mais curto e que acrescentando v no conjunto S respeitamos a
primeira propriedade. Vamos supor ento que existe um outro caminho mais curto at
v. Consideramos agora x, o primeiro vrtice desse caminho alternativo que no
pertence a S. Isso ilustrado na figura 2, onde em vermelho indicado o caminho mais
curto, e em preto o caminho especial mais curto.

Figura 2

Nesse caso, supondo que o grafo no contm nenhum peso negativo, a distncia at v
passando por x maior ou igual distncia at x. Como esse caminho comea com
um caminho especial at x e que por induo o comprimento D[x] dessa parte do
caminho mnimo, sabemos que a distncia total at v passando por x maior ou igual
a D[x]. Como v foi selecionado antes de x, devemos ter tambm D[x] D[v]. Isso
implica que o caminho em vermelho, que supostamente mais curto, deve possuir um
comprimento maior ou igual ao comprimento do caminho especial indicado em preto, o
que contradiz a hiptese. Portanto o caminho especial at v o mais curto.

2. Consideramos agora o que acontece com os outros vrtices diferentes de v que ficam
fora do conjunto S. Seja w tal vrtice. Quando v acrescentado no conjunto S tem
duas possibilidades no que concerne o caminho especial mais curto at w: ou ele fica
igual, ou ele passa agora por v. Vamos ento considerar o segundo caso.

Parece que ainda tem duas possibilidades: ou o vrtice v e o ltimo no caminho at w


ou ele no . Vamos mostrar que o primeiro caso nunca vai acontecer. Suponhamos
que o ultimo vrtice antes de chegar a w um vrtice x diferente de v, como ilustrado
na figura 3:

Figura 3

Esse caminho no pode ser mais curto, pois j existe um caminho mais curto (ou igual)
at x que no inclui v (isso porque x j estava em S antes da adio de v e por induo
j tnhamos um caminho mais curto). Ento, o caminho especial mais curto o menor
entre o que passa por v antes de chegar a w e o que tnhamos antes de acrescentar v.
E isso exatamente o que o algoritmo verifica.

Anlise do algoritmo

A inicializao do algoritmo exige um tempo em O(n). O loop executado n-2 vezes e a cada
iterao todos os vrtices no atualmente no conjunto S so visitados. Na primeira iterao,
visitaremos n-1 vrtices, na segunda, n-2 vrtices, e assim por diante. Podemos ento deduzir
2
que o tempo de execuo em O(n ).

Consideramos agora um grafo esparso. Mais especificamente, um grafo de a arestas e n


vrtices, onde a << n. Podemos melhorar o desempenho do algoritmo, utilizando uma estrutura
de adjacncia. Supondo que para cada vrtice temos um campo que indica se ele pertence ao
conjunto C, e que cada elemento da lista de adjacncia associada a um vrtice v indica no
somente o vrtice w ligado, mas tambm o peso d(v,w) de v para w, eis a verso modificada do
algoritmo:

funo Dijkstra(G: grafo): vetor[2..n]


C := {2,3,...,n} {Implicitamente S = N - C}
Para i := 2 at n:
D[i] :=
Para cada vrtice v adjacente ao vrtice 1:
D[v] := d(1,v)
Repetir n-2 vezes:
v := Elemento de C que minimiza D[v]
C := C - {v}
Para cada vrtice w adjacente a v:
Se w pertence a C:
D[w] := min(D[w],D[v]+ d[v,w])
Retornar D

Nesse caso, o loop mais interno no vai visitar todos os vrtices do grafo, somente os que so
adjacentes ao ltimo vrtice acrescentado. No total, o teste interno ser executado a vezes, o
que proporcional a n no caso de um grafo esparso. Mas mesmo se isso melhora na prtica, o
2
algoritmo ainda em O(n ) porque devemos ainda visitar todos os elementos do conjunto C
para retirar o vrtice que possui o menor valor.

possvel melhorar mais ainda, usando, alm da estrutura de adjacncia, um heap que
contm um nodo para cada vrtice v do conjunto C, ordenados em ordem crescente pelo valor
D[v]. O tempo para inicializar o heap e em O(n). Consideramos agora o loop principal. Para
retirar o vrtice v do conjunto C que minimiza D[v], simplesmente retiramos o vrtice que est
na raiz do heap e ajustamos o heap. Sabemos que isso exige um tempo em O(log n). No que
concerne o loop mais interno, devemos considerar todo vrtice x adjacente a v que no
conjunto C. Nesse caso, talvez deveremos ajustar o valor D[x]. O ajustamento necessrio no
heap exige um tempo em O(log n). Em resumo, preciso retirar n-2 vezes um vrtice do
conjunto C (exigindo uma atualizao do heap a cada vez) e revisar o valor D[v] no mximo a
vezes (isso tambm exige uma atualizao do heap). Portanto, com essa implementao, o
tempo de execuo ser em O((a+n)log n). Se o grafo conexo, temos a n-1, e ento um
tempo de execuo em O(a log n).

Programao dinmica

O algoritmo de Dijkstra permite identificar todos os caminhos mais curtos a partir de um nico
vrtice de origem. Utilizando a programao dinmica, podemos obter o caminho mais curto
entre qualquer par de vrtices. Lembramos que a programao dinmica consiste
essencialmente em produzir solues intermedirias que sero utilizadas incrementalmente
para obter a soluo final. Vamos primeiro ver uma soluo interessante em teoria mas
ineficiente na prtica.

A idia do nosso primeiro algoritmo o seguinte. Seja um grafo direcionado representado por
uma matriz de adjacncia L[1..n,1..n]. Calculamos a cada etapa do algoritmo uma matriz D
onde cada elemento D[i,j] representa o valor do caminho mais curto de k arestas entre i e j. Na
etapa seguinte os valores das matrizes D e L so utilizadas para calcular a nova matriz D que
contm os valores para k+1 arestas. Para obter o novo valor D[i,j], consideramos o valor D[i,u]
+ L[u,j] para todo vrtice u e escolhemos o menor valor. Isto , consideramos todos o caminhos
de k vrtices a partir de i, acrescentamos, para cada um desses caminhos, a arestas que falta
para alcanar j e selecionamos o mais curto.

Eis o algoritmo:

funo CamMaisCurtos(L[1..n, 1..n]: grafo): matriz [1..n,1..n]


D := L
Repetir n-2 vezes:
Para i := 1 at n:
Para j := 1 at n:
min :=
Para k := 1 at n:
Se D[i,k]+L[k,j] < min ento min
:= D[i,k]+L[k,j]
D'[i,j]:= min
D := D'
Retornar D

Considere agora o grafo ilustro na figura 4:

Figura 4

4
Considerando agora o tempo de execuo desse algoritmo, vemos que ele est em O(n ). Ser
que d para fazer melhor? Sim, possvel. Vamos ver agora o algoritmo de Floyd, que tambm
retorna o caminho mais curto entre qualquer par de vrtices.

Seja G=(V,E) um grafo direcionado. Como antes, supomos que os vrtices de G so


enumerados de 1 at n. O grafo representado por uma matriz de adjacncia L onde L[i,i] = 0,
L[i,j] 0 para i j, e L[i,j] = quando no existe nenhuma aresta entre i e j.

O princpio do algoritmo que vamos apresentar agora o seguinte: O algoritmo efetua n


iteraes. Na iterao k do algoritmo obteremos uma matriz D cujos elementos na posio D[i,j]
representam o caminho mais curto de i at j, entre todos que passam somente por vrtices do
conjunto C = {1,2,...,k}, isto , um caminho que pode conter somente os vrtices i e j e qualquer
outro do conjunto C = {1,2,...,k}. Quando acrescentamos o vrtice k+1, temos que atualizar
todas as distncias para ver se agora no temos um caminho mais curto passando pelo vrtice
k. O valor na posio D[i,j] muda somente se o valor D[i,k] + D[k,j] menor. Nesse caso
colocamos em D[i,j] o valor D[i,k] + D[k,j]. Quando o algoritmo pra, isto , quando k = n, temos
na matriz os valores dos caminhos mais curtos entre qualquer par de vrtices.

Eis a descrio do algoritmo:


funo Floyd(L[1..n, 1..n]: grafo): matriz [1..n,1..n]
D := L
Para k := 1 at n:
Para i := 1 at n:
Para j := 1 at n:
D[i,j] := min(D[i,j], D[i,k]+D[k,j])
Retornar D

Para se convencer que o algoritmo funciona corretamente, de novo faremos um prova por
induo. No incio D[i,j] contm o valor da aresta entre i e j se tal aresta existe, seno. Como
inicialmente o conjunto C vazio, no pode ter vrtice intermedirio entre i e j, temos em D[i,j]
o valor do caminho mais curto. Suponhamos agora que a hiptese verdadeira para a iterao
k. Se caminho entre i e j que passa por k+1 mais curto, necessariamente os pedaos de i at
k e de k at j so tambm os mais curtos. J temos na matriz, nas posies D[i,k] e D[k,j], o
valor desses caminhos mais curtos. Como o programa compara a soma desses dois valores
com o valor encontrado em D[i,j] e escolhe o menor, tambm na etapa k+1 teremos os
caminhos mais curtos que passam pelos vrtices {1,2,...,k+1}.
3
muito fcil ver que o algoritmo tem um tempo de execuo em O(n ). Note que o mesmo
problema pode ser resolvido aplicando n vezes o algoritmo de Dijkstra, comeando cada vez
3
com um vrtice diferente. Nesse caso, teramos tambm um tempo de execuo em O(n ). Mas
isso no significa que ambos tm a mesma eficincia. O algoritmo de Floyd tem que inicializar
uma matriz de tamanho n x n. O algoritmo de Dijktra tem, dentro do primeiro loop, dois loops
onde cada uma exige um tempo de execuo na ordem de n. Tambm ele deve efetuar uma
2
inicializao que exige um tempo em O(n) (isso repetido n vezes vai dar um total em O(n )).
Lembramos que usando uma estrutura de heap, podemos obter uma implementao do
algoritmo de Dijkstra que est em O((a+n) log n), onde a o nmero de arestas. Aplicando n
2
vezes o algoritmo, obtemos um tempo de execuo de n x O((a+n) log n) = O((an + n ) log n).
Se o grafo esparso, pode ser melhor utilizar o algoritmo de Dijkstra. Se o nmero de arestas
se aproxima do grafo completo, provavelmente o algoritmo de Floyd ser melhor.

A descrio do algoritmo dada acima no permite identificar o caminho mais curto. Para obter
isso, podemos modificar o algoritmo de maneira semelhante ao que foi feito com o algoritmo de
Dijkstra. Utilizaremos uma matriz P onde cada elemento P[i,j] aponta no ltimo vrtice (diferente
de i) encontrado no caminho antes de chegar a j. A matriz P ser atualizada cada vez que uma
posio D[i,j] ser atualizada, colocando o ltimo vrtice k considerado. No incio, toda posio
da matriz P recebe o valor 0. No final, para identificar o caminho mais curto entre i e j, so
percorrer os ponteiros a partir de P[i,j] at chegar a uma entrada que tem valor 0. Eis a verso
modificada do algoritmo:

funo Floyd(L[1..n, 1..n]: grafo): matriz [1..n,1..n]


D := L
Para i := 1 at n:
Para j := 1 at n:
P[i,j] := 0
Para k := 1 at n:
Para i := 1 at n:
Para j := 1 at n:
Se D[i,k]+D[k,j] < D[i,j]
D[i,j] := D[i,k]+D[k,j]
P[i,j] := k
Retornar D

Com a matriz P retornada pelo algoritmo, vemos que o caminho mais curto de 1 at 3 passa
pelo vrtice 4. Isso porque o valor 4 aparece na posio (1,3) da matriz. Agora, para saber o
caminho inteiro, devemos procurar recursivamente o caminho mais curto de 1 at 4 e de 4 at
3. Procurando novamente na matriz P, vemos que na posio (4,3) h um 0, que indica que o
caminho de 4 at 3 direto. Na posio (1,4) da matriz aparece um 2, indicando que o caminho
mais curto de 1 at 4 passa pelo vrtice 2. Repetindo o processo, vemos que o caminhos mais
curtos de 1 at 2 e de 2 at 4 no contm outros vrtices intermedirios. Portanto, o caminho
mais curto entre 1 e 3 a sequncia dos vrtices 1,2,4,3.
Exerccios

Exerccio 1: Execute o algoritmo de Dijkstra com o grafo da figura 1, mas comeando com o
vrtice 4. A resposta a mesma?

Exerccio 2: Para cada grafo ilustrado na figura 5, mostre cada passo da execuo do
algoritmo de Dijkstra.

Figura 5

Exerccio 3: D um exemplo mostrando que se arestas de peso negativo so permitidas, o


algoritmo de Dijkstra no funciona sempre corretamente.

Exerccio 4: Inspire-se do algoritmo de Dijkstra para escrever um algoritmo que retorna o


caminho mais comprido.

Exerccio 5: Para cada grafo ilustrado na figura 5, mostre cada passo da execuo do
algoritmo de Floyd.

Exerccio 6: Como o algoritmo de Floyd se comporta com um grafo que contm arestas de
peso negativo? (Considere os casos onde existem circuitos de valor negativo e os casos onde
no existe tal circuito)

Exerccio 7: Modifique o algoritmo de Floyd para determinar o nmero de caminhos entre cada
par de vrtices.

Exerccio 8: Modifique o algoritmo de Floyd para determinar o nmero de caminhos mais


curtos entre cada par de vrtices.

Você também pode gostar