• Nenhum resultado encontrado

Algoritmo de Bellman-Ford

No documento Problema da Árvore de Steiner (páginas 48-55)

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.

No documento Problema da Árvore de Steiner (páginas 48-55)

Documentos relacionados