de Dijkstra (a, c, e) tem custo igual a −1. Ou seja, o caminho proposto pelo algoritmo não é ótimo.
Para contornar este problema podemos utilizar o algoritmo de Bellman-Ford, que é o foco da próxima seção.
3.2 ALGORITMO DE BELLMAN-FORD 33 Então para toda constante M xada encontramos n0 ∈N tal que para qualquer n≥n0,
c(γn)< M
O que mostra que o problema é ilimitado.
Suponha agora que o problema seja viável e ilimitado. Seja nD o número de vértices do digrafo D.
Como o problema é viável, existe um caminho γ de r a s. Vamos supor que γ seja um caminho sem ciclo negativo.
Então o número máximo de arcos em γ é nD −1. O que implica que o conjunto Γ de todos os caminhos de r as em D sem ciclos tem um número nito de elementos.
Tomando M <min{c(γ) γ ∈ Γ}, teríamos que o problema é limitado, contrariando a hipótese.
Logo, D possui um caminho de r as com ciclo negativo.
Para evitar a ilimitação da solução do problema ocasionada pela existência de ciclos negativos no digrafo, poderíamos adicionar uma nova restrição que exija que o caminho candidato a solução seja simples. Entretanto isso torna o problema NP-difícil , ou seja, não há, ou não acredita-se que haja, algoritmo eciente para resolvê-lo. Na prática, o fato do problema ser NP-difícil sugere que à medida que o número de vértices e arcos do digrafo aumenta, o problema torna-se rapidamente impraticável de ser resolvido por um computador em tempo razoável. Isso também signica que não há algoritmo eciente para o problema, a menos que alguns dos problemas computacionais comprovadamente difíceis possam ser resolvidos ecientemente [Sil07].
Neste trabalho, restringiremos ao caso do problema de caminhos mínimos em digrafos sem ciclos negativos.
Ideia do algoritmo
Sejam D um digrafo e nD o número de vértices de D. Queremos encontrar um caminho de custo mínimo entre os vértices r e s. A ideia básica do algoritmo de Bellman-Ford é propagar os custos mínimos, armazenados no vetor y tal como denido no algoritmo de Dijkstra , através da repetição de nD−1vezes o seguinte procedimento, conforme descrito em Daudt [DdM10]:
(1) Para todo arcouv em D, fazemos:
(a) Se y(u) +cuv< y(v):
então o vértice pai dev é o vérticeue atualizamos seu custo que será:y(v) = y(u) +cuv (b) Caso (a) seja falso, passo para uma nova iteração de (1).
Se admitirmos que não há caminho entre resque possua ciclo negativo (pois nesse caso, o problema será ilimitado conforme visto no teorema3.2), então não há repetição de vértices
no caminho ótimo. Por esse motivo que a repetição denD−1vezes do procedimento descrito acima garante a obtenção do caminho de custo mínimo.
Utilizando esse raciocínio, podemos facilmente vericar a existência de ciclos negativos. Se repetirmos mais uma vez a comparação descrita em (a) e houver a atualização de custo para algum vérticev em D, então estaremos repetindo algum vértice no caminho, caracterizando a existência de um ciclo [DdM10].
FIFO Bellman-Ford
Em nossas implementações utilizamos uma variação do algoritmo de Bellman-Ford, ava-liamos os vértices de acordo com a metodologia FIFO (First In, First Out). Dessa forma, a análise dos vértices ocorre de uma forma mais organizada, embora isso não implique um ganho em desempenho dessa versão [Feo13].
O passo k = 0 do algoritmo FIFO Bellman-Ford é incluir a raiz r na la de vértices.
Fazemos então k =k+ 1, e
(1) O algoritmo é executado até que a la de vértices seja vazia. Analisamos sempre t, o vértice que está no início da la. Tome o conjunto de arcos que pertençam ao leque de saída de{t},LS({t}).
(a) Execute enquantoLS({t})6=∅. Seja u um vértice atingido por t. Sey(t) +ctu< y(u), então:
O pai de u é o vértice t. y(u) =y(t) +ctu
Retire o arcotu deLS({t}), e volto para (a).
(b) SeLS({t}) =∅.
Fim do passo k. Faça k=k+ 1.
Retire o vértice t da la, volte para (1) e continue a execução.
Observe que a execução no passo k termina quando todos os vértices inseridos no passo k−1forem retirados da la. Segue abaixo uma possível implementação do FIFO Bellman-Ford, nela utilizamos um vértice SENTINELA, cuja nalidade é indicar o momento em que ocorre o término de cada passo:
bellman.c - Algoritmo de Bellman-Ford
1 void
2 bellmanFord(Digraph G, Vertex s, Vertex parnt[], int cst[])
3 {
4 Vertex v, w;
5 link p;
6 int k = 0; /* contador de passos */
7
8 for(v = 0; v <= G->V; v++)
3.2 ALGORITMO DE BELLMAN-FORD 35
9 {
10 cst[v] = INFINITO;
11 parnt[v] = -1;
12 }
13
14 /* uma posição a mais para a SENTINELA */
15 queueInit(G->V + 1); /* inicializa a fila de vértices*/
16
17 /* passo 0 */
18 cst[r] = 0;
19 parnt[r] = r;
20 queueInsert(r);
21 queueInsert(SENTINELA);
22
23 while(!queueEmpty()) /* enquanto a fila de vértices não é vazia */
24 {
25 v = queueGet(cst); /* retiro o primeiro vértice a entrar na fila */
26 if(v == SENTINELA) /* v = SENTINELA => final de um passo */
27 {
28 if(k++ == G->V) /* o número de passos atingiu o número de vértices*/
29 {
30 if(!queueEmpty()) /*verifica ciclo negativo*/
31 {
32 AVISO(bellman.c: Ciclo Negativo!);
33 return;
34 }
35 return;
36 }
37 queueInsert(SENTINELA); /* insiro o SENTINELA novamente na fila */
38 }
39 else
40 {
41 for(p = G->adj[v]; p ; p = p->next)
42 {
43 /* se eu encontrar um caminho mais barato de v a w */
44 if(cst[w = p->w] > cst[v] + p->cst)
45 {
46 cst[w] = cst[v] + p->cst; /* atualizo o custo */
47 parnt[w] = v; /* armazeno o arco da arborescência */
48 queueInsert(w); /* insiro o vértice w na fila */
49 }
50 }
51 }
52 }
53 queueFree(); /* libera a memória da fila */
54 }
As funções iniciadas pelo prexo queuesão funções que manipulam las do tipo FIFO, conforme denidas na seção 2.4 do capítulo 2.
O consumo de tempo das implementações do algoritmo de FIFO Bellman-Ford e de Bellman-Ford é de O(nD×mD), ondenD emD representam os números de vértices e arcos do digrafo, respectivamente.
Ilustração do algoritmo FIFO Bellman-Ford
Nessa seção ilustraremos a execução do algoritmo FIFO Bellman-Ford em um digrafo com custos nos arcos. Nas guras subsequentes, o vértice em vermelho representa o primeiro vértice da la e os demais vértices presentes na la têm coloração amarela. Abaixo da gura de cada digrafo apresentaremos a manipulação da la de vértices em FILA. Nela, vértices inseridos no mesmo passo têm a mesma coloração.
O passo 0consiste em inserir a raiz a na la de vértices.
Como a é raiz, então parnt(a) =a ey(a) = 0.
No passo 1, analisamos o leque de saída de a, LS(a) ={ac, ab}. Tomando inicialmente o arco ac, notemos que:
y(c) = ∞
y(c) < y(a) +cac = 0 + 3 = 3
Então atualizamos o custo da raiz até c, armazenamos o caminho de a a c em parnt e inserimos o vérticec na la. Assim, y(c) = 3 e parnt(c) =a.
Retiramos agora o arco ac do conjuntoLS(a). Tome então o arco ab. y(b) = ∞
y(b) < y(a) +cab = 0 + 4 = 4
Então o vértice b é inserido no nal da la e, y(b) = 4 e parnt(b) = a
O vértice ab é retirado deLS(a) e notamos que agora esse conjunto é vazio.
Figura 3.7: Execução do algoritmo de Bellman-Ford: passo 1
Assim, retiramos da la o vértice a, marcando o m do passo1 como podemos observar na gura 3.7.
No passo 2, o vértice c está na primeira posição da la. LS(c) = {ce}. Como e é um vértice que ainda não havia sido atingido, então y(e) = ∞. Assim, armazenamos o caminho (parnt(e) = c), atualizamos o custo associado a e (y(e) = y(c) +cce = −1) e inserimos o vértice e à la. Retiramos da la o vértice c, pois agora o conjunto LS(c) é vazio. Agora o foco da nossa análise é o vértice b. LS(b) ={bd, bc}.
3.2 ALGORITMO DE BELLMAN-FORD 37
Figura 3.8: Execução do algoritmo de Bellman-Ford: passo 2
Tome o arcobd. y(d) =∞, então:y(d) =y(b) +cbd = 1,parnt(d) = b e o vértice dentra na la. Podemos agora retirar o arcobddeLS(b). Agora o único arco contido emLS(b)ébc. Vamos analisar o arco bc. Notemos que y(b) +cbc = 4−2 = 2 que é menor que o custo calculado anteriormente para c, y(c) = 3 (como pode ser visto na gura 3.8(d)).
Então o custo associado ac é atualizado e b será o pai de c. y(c) = 2
parnt(c) = b
Inserimos c na la de vértices. O arco bc pode agora ser retirado de LS(b), que agora é um conjunto vazio. Então retiramos o vértice b da la e terminamos o passo 2.
No passo 2 foram inseridos os vértices e, d e c. O passo 3 se encerrará quando esses três vértices forem retirados da la. Na primeira posição da la encontra-se o vérticee.LS(e) =∅. Então retiramos o vértice e da la. O próximo vértice a ser analisado é o vérticed.LS(d) = {de}.
Como y(d) + cde = −4 < −1 = y(e), então inserimos o vértice e novamente na la e atualizamos os seguintes itens:
y(e) = −4
parnt(e) = d Podemos agora retirar o vértice d da la.
Tomamos agora o vértice c. LS(c) = {ce}. Notemos que y(c) +cce = −2. Esse valor é maior quey(e)que foi calculado anteriormente.
Figura 3.9: Execução do algoritmo de Bellman-Ford: passo 3
Nesse caso não há nenhuma atualização a fazer nos custos ou na arborescência. Retiramos cedeLS(c), que agora é vazio. Podemos então retirar o vértice cda la e assim terminamos o passo 3, conforme descrito na gura 3.9.
Figura 3.10: Execução do algoritmo de Bellman-Ford: passo 4
O único vértice inserido no passo 3 foi vértice e. Como LS(e) =∅, conforme vericamos na gura 3.10(a), então não temos nenhuma comparação a fazer. Podemos então retirar e da la de vértices. Isso conclui o passo 4 do FIFO Bellman-Ford.
Notemos agora que a la de vértices está vazia. Terminamos então a execução do algo-ritmo FIFO Bellman-Ford. A ACM é exibida na gura 3.10(c).
Observemos também que, assim como ocorre no algoritmo de Bellman-Ford, após a inserção da raiz na la de vértices, o número de passos executados pelo algoritmo FIFO Bellman Ford foi 4, que é igual anD−1, ondenD é o número de vértices do digrafo utilizado
3.3 CERTIFICADO DE OTIMALIDADE 39 no exemplo.