Algoritmos para Grafos  |  Índice

Algoritmo de Bellman-Ford

Este capítulo discute um algoritmo para do problema da CPT (= cheapest-paths tree), que já foi apresentado no capítulo Caminhos de custo mínimo.

Problema da CPT:  Dado um vértice s de um grafo G com custos arbitrários (positivos e negativos) nos arcos, encontrar uma árvore de caminhos baratos de G que tenha raiz s.

Vale lembrar que uma árvore de caminhos baratos, ou CPT, é uma sub­árvore radicada geradora T de G tal que todo caminho em T que começa na raiz é mínimo em G.  Como observamos no capítulo introdutório, uma instância do problema tem solução se e somente se (1) todos os vértices de G estão ao alcance de s e (2) o grafo não tem ciclos negativos.  Para escamotear a primeira condição, podemos trabalhar com o sub­grafo induzido pelos vértices que estão ao alcance de s. Já a segunda condição é fundamental e não pode ser contornada.

Um algoritmo para o problema foi descoberto por R. Bellman e L.R. Ford e publicado em 1956-58.  O algoritmo parece muito simples, mas é lento e a análise da sua correção é difícil.

Sumário:

O algoritmo

Ao receber um grafo G com custos nos arcos e um vértice s, o algoritmo de Bellman-Ford procura encontrar uma CPT de G que tenha raiz s.  Para simplificar a discussão do algoritmo, suporemos que todos os vértices de G estão ao alcance de s.  O algoritmo calculará o vetor de pais de uma CPT ou anunciará que o grafo tem um ciclo negativo.

A ideia é simples:  o algoritmo mantém um potencial dist[ ] sobre o conjunto de vértices e relaxa, sistematicamente, os arcos que estão tensos em relação a esse potencial.

Para discutir os detalhes, convém introduzir o seguinte conceito. Se G tem V vértices, uma V-permutação do conjunto de arcos de G é a concatenação de V permutações dos arcos de G, ou seja, uma sequência da forma P0 P1 … PV−1 em que cada Pi é uma permutação do conjunto de arcos de G.  Podemos supor que as V permutações são iguais, embora isso não seja essencial. Por exemplo, se G tem 4 vértices e arcos a, b, c, então

a  b  c  a  b  c  a  b  c  a  b  c

é uma V-permutação dos arcos de G.

O algoritmo de Bellman-Ford começa por escolher alguma V-permutação do conjunto de arcos de G. Digamos que a V-permutação escolhida é  a0 a1 a2aVA−1,  sendo V o número de vértices e A o número de arcos de G.  O algoritmo processa a0, depois a1, depois a2, e assim por diante.  Cada iteração começa com um arco ak, um potencial dist[ ], e um vetor pa[ ] que leva vértices em vértices.  A primeira iteração começa com k ≡ 0, dist[s] ≡ 0, pa[s] ≡ s, e com dist[v] ≡ ∞ e pa[v] indefinido para todo v distinto de s.  A k-ésima iteração consiste no seguinte:

  1. sejam v e w as pontas inicial e final do arco ak
  2. seja c o custo de v-w
  3. se v-w está tenso então faça

Ao final desse processo — ou seja, quando kVA — temos duas alternativas:  se todos os arcos estão relaxados então pa[ ] é o vetor de pais de uma CPT com raiz s e dist[ ] é o vetor das distâncias em G a partir de s.  Senão, G tem um ciclo negativo e portanto não tem CPT.

Convém lembrar que, por definição, um arco v-w está tenso em relação a dist[ ] se dist[v]+c < dist[w], está relaxado se dist[v]+cdist[w], e está justo se dist[v]+cdist[w].  É claro que todo arco v-w que tem dist[v] ≡ ∞ está relaxado e todo arco v-w que tem dist[v] < ∞ e dist[w] ≡ ∞ está tenso.

Para facilitar o discussão do algoritmo, é útil quebrar a sequência de VA iterações em V grupos de A iterações. Diremos que cada grupo é uma rodada. Portanto, cada rodada é uma sequência de A iterações que processa uma permutação do conjunto de arcos de grafo. A primeira rodada processa os arcos  a0 a1aV−1;  a segunda rodada processa os arcos  aV aV+1a2V−1;  e assim por diante.

Durante a execução do algoritmo, a relaxação de um arco tenso v-w diminui o valor de dist[w] e assim pode deixar tenso algum arco w-x que já havia sido relaxado numa rodada anterior.  Com isso, cada arco pode ser relaxado várias vezes, uma vez em cada rodada.

Exemplo A.  Considere o problema de encontrar uma CPT com raiz 4 no grafo definido pela listas de adjacência abaixo. Todos os vértices estão ao alcance de 4. O custo de cada arco está indicado entre parênteses.

0: 4  (2)
1: 0  (2)
2: 1 (-9)
3: 2  (2)
4: 3  (2)

Como o grafo tem 5 vértices, o algoritmo executa 5 rodadas. Suponha que cada rodada examina os arcos na ordem  0-4 1-0 2-1 3-2 4-3.  Veja o estado dos vetores dist[ ] e pa[ ] no início de cada rodada, com * no lugar de ∞ e - no lugar de valores indefinidos:

 dist[]           pa[]
 0  1  2  3  4    0  1  2  3  4  
  	                              
 *  *  *  *  0    -  -  -  -  4
 *  *  *  2  0    -  -  -  4  4  
 *  *  4  2  0    -  -  3  4  4  
 * -5  4  2  0    -  2  3  4  4  
-3 -5  4  2  0    1  2  3  4  4  
-3 -5  4  2 -1    1  2  3  4  0

Ao final desse processo, o arco 4-3 continua tenso, o que indica a existência de um ciclo negativo. (O ciclo negativo em questão é 4-3-2-1-0-4. Note que todos os arcos desse ciclo têm a forma pa[x]-x.)

 0 — 1 — 4 
   / \    
 2 — 3   

Exemplo B.  Considere o grafo com custos nos arcos descrito abaixo. Todos os vértices estão ao alcance de 0. Vamos submeter o grafo ao algoritmo de Bellman-Ford para procurar uma CPT com raiz 0.

0-1 1-2 2-3 3-1 1-4
  5   1   1  -4   5

(Estamos especialmente interessados em um caminho mínimo de 0 a 4.)  O algoritmo executa 5 rodadas, pois o grafo tem 5 vértices. Suponha que cada rodada examina os arcos na ordem  0-1 1-2 2-3 3-1 1-4.  Veja o estado dos vetores dist[ ] e pa[ ] no início de cada rodada:

dist[]           pa[]
0  1  2  3  4    0  1  2  3  4  

0  *  *  *  *    0  -  -  -  -
0  3  6  7  8    0  3  1  2  1  
0  1  4  5  6    0  3  1  2  1  
0 -1  2  3  4    0  3  1  2  1  
0 -3  0  1  2    0  3  1  2  1  
0 -5 -2 -1  0    0  3  1  2  1

O arco 1-2 continua tenso. Portanto o grafo tem algum ciclo negativo ao alcance de 0.  (O ciclo negativo em questão é 1-2-3-1. Todos os arcos desse ciclo têm a forma pa[x]-x.)  A propósito, note que o caminho 0-1-2-3-1-4 tem custo mínimo, mas não é simples.  Note também que o sub­grafo descrito por pa[ ] não é uma árvore radicada.

Exercícios 1

  1. Critique a seguinte afirmação:  uma V-permutação dos arcos de um grafo é uma sequência de arcos em que cada arco aparece exatamente V vezes, sendo V o número de vértices do grafo
  2. Refaça o exemplo A depois de trocar por 3 o custo do arco 4-3.
  3. Implementação ingênua.  Escreva uma função GRAPHcptBF0() que implemente o algoritmo de Bellman-Ford de maneira ingênua. Sua implementação deve seguir de perto a descrição do algoritmo dada acima.  Mostre que sua função consome VA unidades de tempo no pior caso, sendo V o número de vértices e A o número de arcos do grafo.

Análise

Antes de começar a análise do algoritmo de Bellman-Ford, faremos três observações preliminares.  Suponha que C é um caminho com origem x e término y e denote por  cst(C)  o custo do caminho.  As três observações se referem ao estado de relaxação ou tensão dos arcos de C em relação a um potencial dist[ ] definido sobre os vértices do grafo:

  1. Se cada arco de C está relaxado então cst(C) ≥ dist[y] − dist[x].
  2. Se cada arco de C está tenso ou justo então cst(C) ≤ dist[y] − dist[x].
  3. Se cada arco de C está justo então cst(C) ≡ dist[y] − dist[x].

Para provar essas observações basta fazer um cálculo análogo ao que usamos na prova da cota inferior para distâncias entre vértices no capítulo Caminhos de custo mínimo.  Um caso particular importante de A: se C for um caminho fechado cujos arcos estão todos relaxados então cst(C) ≥ 0.

Podemos agora começar a análise da correção do algoritmo de Bellman-Ford. Para provar que o algoritmo produz o resultado prometido, é preciso observar que certas propriedades valem no início de cada iteração. Dois desses invariantes são óbvios e podem ser enunciadas já. No início de cada iteração, denotaremos por S o conjunto dos vértices x para os quais pa[x] está definido, ou, equivalentemente, para os quais dist[x] < ∞. Então, para todo x em S,

  1. pa[x] está em S;
  2. se xs então pa[x] ≠ x.

Os demais invariantes são menos óbvios. Para enunciá-los, é preciso introduzir algumas definições.

No início de cada iteração, seja  F  o sub­grafo de G cujo conjunto de vértices é S e cujos arcos são todos os que têm a forma pa[x]-x com x em S.  Esse sub­grafo está bem definido graças ao invariante i.  É claro que nenhum vértice de F tem grau de entrada maior que 1. Ademais, todo vértice de F exceto possivelmente s tem grau de entrada igual a 1, graças ao invariante ii.  (Portanto, F é um grafo funcional, embora não seja necessariamente uma floresta radicada.)

No início de cada iteração, denotaremos por  L  a sequência dos arcos que o algoritmo examinou até esse momento. É claro que L é um segmento inicial da V-permutação  a0 a1 a2aVA−1  adotada antes da primeira iteração.  Diremos que um caminho em G está bem-casado com L se a sequência de arcos do caminho é uma subsequência de L.

Podemos agora enunciar os demais invariantes. No início de cada iteração,

  1. para todo caminho C em G com origem s e término z, se C é bem-casado com L então cst(C) ≥ dist[z];
  2. todo arco de F está justo ou tenso em relação a dist[ ];
  3. todo ciclo em F é negativo;
  4. dist[s] ≡ 0 se e somente se pa[s] ≡ s.

(Graças à observação preliminar B acima, o invariante iv garante que cst(C) ≤ dist[y] − dist[x] para qualquer caminho C em F com origem x e término y.  Note, entretanto, que o invariante iii não é consequência da observação preliminar A acima, pois os arcos de C não estão necessariamente relaxados e o lado direito da desigualdade não tem o termo − dist[s].)

Prova da correção.  Podemos agora usar os invariantes para provar que o algoritmo produz o resultado prometido. Suponha que o algoritmo já executou as V rodadas, ou seja, já examinou todos os arcos da V-permutação a0 a1 a2aVA−1.  Nesse momento, L é igual à V-permutação e portanto todo caminho em G com V arcos ou menos está bem-casado com L.  Portanto, todo caminho C de s a um vértice z que tenha no máximo V arcos satisfaz a desigualdade cst(C) ≥ dist[z], de acordo com o invariante iii.  Devemos agora considerar duas possibilidades:

Concluímos assim que o algoritmo produz o resultado que prometeu: devolve uma CPT de G ou esbarra num ciclo negativo (que prova a inexistência de uma CPT).

Exercícios 2

  1. Prove as desigualdades A, B, e C enunciadas acima.
  2. Prove os invariantes i a vi enunciados acima.
  3. Suponha que os invariantes enunciados acima estão corretos e prove que o algoritmo de Bellman-Ford produz o resultado prometido.  (Preencha os detalhes da prova dada acima.)
  4. Critique a seguinte afirmação: Depois de k rodadas, dist[v] é o custo de um passeio mínimo de sv dentre os que têm no máximo k−1 arcos.
  5. Nem tudo ao alcance de s.  Seja s um vértice de um grafo G. Suponha que nem todos os vértices de G estão ao alcance de s.  Mostre que a aplicação do algoritmo de Bellman-Ford a Gs detecta um ciclo negativo ao alcance de s ou produz uma CPT do sub­grafo induzido pelo conjunto de vértices que estão ao alcance de s.

Implementação do algoritmo

Nossa implementação do algoritmo de Bellman-Ford supõe que o grafo é representado por listas de adjacência com custos.  Assim, para cada vértice v e cada a em G->adj[v], a relaxação do arco que vai de v a a->w consiste em fazer

      dist[a->w] = dist[v] + a->c; // relaxação
      pa[a->w] = v;

Para evitar trabalho supérfluo, não vamos examinar todos os arco do grafo em cada rodada, pois muitos já estão relaxados desde a rodada anterior. É suficiente examinar os arcos v-w para o quais o valor de dist[v] diminuiu na rodada anterior, pois somente esses arcos podem estar tensos.  Para isso, basta manter uma fila de vértices ativos e inserir um vértice w na fila somente quando algum arco v-w sofrer relaxação. Com isso, em cada iteração, a ponta inicial de todo arco tenso estará na fila.

Para isolar cada rodada da seguinte, a fila tem dois segmentos: o primeiro corresponde à rodada corrente e o segundo corresponde à próxima.  Uma sentinela é inserida na fila para separar o primeiro segmento do segundo; o pseudovértice V é uma sentinela conveniente.  O processamento da fila consiste em examinar os leques de saída de todos os vértices do primeiro segmento e alimentar o segundo segmento. Esse processo é interrompido depois de V rodadas.

/* Recebe um vértice s de um grafo G 
   com custos arbitrários nos arcos
   e supõe que todos os vértices estão ao alcance de s.
   Devolve false 

   se G tem algum ciclo negativo
   e portanto não tem CPT.
   Senão, devolve true
   e armazena em pa[] o vetor de pais de 
   uma CPT de G com raiz s.
   Nesse segundo caso,
   as distâncias a partir de s são armazenadas 
   no vetor dist[].
   (A função supõe que a soma de todos os custos positivos é menor 
   que INT_MAX.
   O código foi inspirado no Programa 21.9
   de Sedgewick.) */
bool GRAPHcptBF( Graph G, vertex s, vertex *pa, int *dist)
{
   QUEUEinit( G->A);
   bool onqueue[1000];
   for (vertex u = 0; u < G->V; ++u)
      pa[u] = -1, dist[u] = INT_MAX, onqueue[u] = false;
   pa[s] = s, dist[s] = 0;
   QUEUEput( s);
   onqueue[s] = true;
   vertex V = G->V; // pseudovértice
   QUEUEput( V); // sentinela
   int k = 0;

   while (true) { 
      vertex v = QUEUEget( );
      if (v < V) {
         for (link a = G->adj[v]; a != NULL; a = a->next) {
            if (dist[v] + a->c < dist[a->w]) {
               dist[a->w] = dist[v] + a->c; 
               pa[a->w] = v;
               if (onqueue[a->w] == false) {
                  QUEUEput( a->w);
                  onqueue[a->w] = true;
               }
            }
         }
      } else {
         if (QUEUEempty( )) return true; // A
         if (++k >= G->V) return false; // B
         QUEUEput( V); // sentinela
         for (vertex u = 0; u < G->V; ++u) 
            onqueue[u] = false;
      }
   }
}

Aa seguintes observações podem ajudar a entender o código:

A função supõe que todos os vértices estão ao alcance de s. Mas não é necessário testar essa condição antes de invocar a função:  Se essa condição não estiver satisfeita, a função GRAPHcptBF() produzirá uma CPT do sub­grafo induzido pelo conjunto de vértices que estão ao alcance de s  ou  dirá que existe um ciclo negativo ao alcance de s.

 0 
 | 
 1 
 | 
 2 
 | 
 3 
 | 
 4 

Exemplo C.  Considere o grafo com custos nos arcos descrito a seguir. Todos os vértices estão ao alcance de 0. Queremos encontrar uma CPT com raiz 0.

0-1  1-2  2-3  3-4 
 10   10   10   10 

Veja o estado da fila e dos vetores dist[] e pa[] no início de cada iteração controlada pelo  while (true),  com * no lugar de INT_MAX e - no lugar de -1:

       dist[]           pa[]                    
queue  0  1  2  3  4    0 1 2 3 4
              
0 5    0  *  *  *  *    0 - - - -
5 1    0 10  *  *  *    0 0 - - -
1 5    0 10  *  *  *    0 0 - - -
5 2    0 10 20  *  *    0 0 1 - -
2 5    0 10 20  *  *    0 0 1 - -
5 3    0 10 20 30  *    0 0 1 2 -
3 5    0 10 20 30  *    0 0 1 2 -
5 4    0 10 20 30 40    0 0 1 2 3
4 5    0 10 20 30 40    0 0 1 2 3
5      0 10 20 30 40    0 0 1 2 3

Como a fila ficou vazia, o grafo não tem ciclo negativo e o último valor de pa[] descreve uma CPT com raiz 0.

Exemplo D.  Queremos encontrar uma CPT com raiz 0 no grafo com custos nos arcos descrito a seguir. Todos os vértices estão ao alcance de 0.

0-1  1-2  0-3  3-1  0-4  4-5  5-1 
 20   30    9    9    5    5    5

Veja o estado da fila e dos vetores dist[] e pa[] no início de cada iteração controlada pelo  while (true),  com * no lugar de INT_MAX e - no lugar de -1:

          dist[]             pa[]                    
queue     0  1  2  3  4  5   0 1 2 3 4 5
              
0 6       0  *  *  *  *  *   0 - - - - -
6 1 3 4   0 20  *  9  5  *   0 0 - 0 0 -
1 3 4 6   0 20  *  9  5  *   0 0 - 0 0 -
3 4 6 2   0 20 50  9  5  *   0 0 1 0 0 -
4 6 2 1   0 18 50  9  5  *   0 3 1 0 0 -
6 2 1 5   0 18 50  9  5 10   0 3 1 0 0 4
2 1 5 6   0 18 50  9  5 10   0 3 1 0 0 4
1 5 6     0 18 50  9  5 10   0 3 1 0 0 4
5 6 2     0 18 48  9  5 10   0 3 1 0 0 4
6 2 1     0 15 48  9  5 10   0 5 1 0 0 4
2 1 6     0 15 48  9  5 10   0 5 1 0 0 4
1 6       0 15 48  9  5 10   0 5 1 0 0 4
6 2       0 15 45  9  5 10   0 5 1 0 0 4
2 6       0 15 45  9  5 10   0 5 1 0 0 4
6         0 15 45  9  5 10   0 5 1 0 0 4

A fila ficou vazia. Portanto, o grafo não tem ciclo negativo e o último valor de pa[] descreve uma CPT com raiz 0.

 0 
 | 
 1 
 | 
 2 
 | 
 3 
 | 
 4 

Exemplo E.  Queremos encontrar uma CPT com raiz 0 no grafo com custos nos arcos descrito a seguir. Todos os vértices estão ao alcance de 0.

0-1  1-0  1-2  2-1  2-3  3-2  3-4  4-3
 -1   -1   -1   -1   -1   -1   -1   -1

Veja o estado da fila e dos vetores dist[] e pa[] no início de cada iteração controlada pelo while (true):

            dist[]           pa[]                    
queue       0  1  2  3  4    0 1 2 3 4
              
0 5         0  *  *  *  *    0 - - - -
5 1         0 -1  *  *  *    0 0 - - -
1 5         0 -1  *  *  *    0 0 - - -
5 0 2      -2 -1 -2  *  *    1 0 1 - -
0 2 5      -2 -1 -2  *  *    1 0 1 - -
2 5 1      -2 -3 -2  *  *    1 0 1 - -
5 1 3      -2 -3 -2 -3  *    1 2 1 2 -
1 3 5      -2 -3 -2 -3  *    1 2 1 2 -
3 5 0 2    -4 -3 -4 -3  *    1 2 1 2 -
5 0 2 4    -4 -3 -4 -3 -4    1 2 1 2 3
0 2 4 5    -4 -3 -4 -3 -4    1 2 1 2 3
2 4 5 1    -4 -5 -4 -3 -4    1 0 1 2 3
4 5 1 3    -4 -5 -4 -5 -4    1 0 1 2 3
5 1 3      -4 -5 -4 -5 -4    1 0 1 2 3

Depois de cinco rodadas, a fila não ficou vazia. Logo, o grafo tem um ciclo negativo. De fato, 0-1-0 é um ciclo negativo.

   0    
  / \   
 1───2  

Exemplo F.  Considere o grafo completo com custos nos arcos descrito abaixo. Queremos encontrar uma CPT com raiz 0. Todos os vértices estão ao alcance de 0.

0-1  1-0  1-2  2-1  2-0  0-2
 -1   -1   -1   -1   -1   -1

Veja o estado da fila e dos vetores dist[] e pa[] no início de cada iteração controlada pelo while (true):

                dist[]     pa[]                    
queue           0  1  2    0  1  2
              
0 3             0  *  *    0  -  -
3 1 2           0 -1 -1    0  0  0
1 2 3           0 -1 -1    0  0  0
2 3 0 2        -2 -1 -2    1  0  1
3 0 2 1        -3 -2 -2    2  2  1
0 2 1 3        -3 -2 -2    2  2  1
2 1 3 1 2      -3 -3 -3    2  0  0
1 3 1 2 0      -4 -4 -3    2  2  0
3 1 2 0        -5 -4 -4    1  2  1

Depois de três rodadas, a fila não ficou vazia. Logo, o grafo tem um ciclo negativo. De fato, 0-1-2-0 é um ciclo negativo.  (É interessante refazer esse exemplo depois de inibir o efeito do vetor booleano onqueue[] que impede que um vértice seja inserido pela segunda vez no segundo segmento da fila.)

 hand-photo/fig.21.2-sedgewick-x.png

Exemplo G.  A tabela a seguir define um grafo com custos nos arcos. Submeta o grafo à função GRAPHcptBF() com vértice inicial s4. Todos os vértices estão ao alcance de s.

0-1 0-5 1-2 1-4 2-3 3-0 3-5 4-2 4-3 5-1 5-4 
 41  29  51  32  50  45 -38  32  36 -29	 21

Veja o estado da fila e dos vetores dist[] e pa[] no início de cada iteração controlada pelo while (true), com * no lugar de INT_MAX e - no lugar de -1:

          dist[]              pa[]                    
queue     0  1   2  3 4  5    0 1 2 3 4 5
        
4 6       *  *   *  * 0  *    - - - - 4 -
6 2 3     *  *  32 36 0  *    - - 4 4 4 -
2 3 6     *  *  32 36 0  *    - - 4 4 4 -
3 6       *  *  32 36 0  *    - - 4 4 4 -
6 0 5    81  *  32 36 0 -2    3 - 4 4 4 3
0 5 6    81  *  32 36 0 -2    3 - 4 4 4 3
5 6 1    81 122 32 36 0 -2    3 0 4 4 4 3
6 1      81 -31 32 36 0 -2    3 5 4 4 4 3
1 6      81 -31 32 36 0 -2    3 5 4 4 4 3
6 2      81 -31 20 36 0 -2    3 5 1 4 4 3
2 6      81 -31 20 36 0 -2    3 5 1 4 4 3
6        81 -31 20 36 0 -2    3 5 1 4 4 3

Depois de cinco rodadas, a fila está vazia. Logo, o grafo não tem ciclo negativo e portanto o vetor dist[] dá as distâncias a partir de 4.

Desempenho.  A função GRAPHcptBF() é quadrática: ela consome V (V+A) unidades de tempo no pior caso.  Ou seja, a função é V vezes mais lenta, no pior caso, que uma busca em largura.

Exercícios 3

  1. Critique a seguinte afirmação: No início de cada iteração na função GRAPHcptBF(), o potencial dist[] é o vetor das distâncias a partir de s na árvore radicada F representada por pa[].
  2. No início de cada iteração, antes de v = QUEUEget(), a fila pode estar vazia?
  3. Instâncias extremas.  A função GRAPHcptBF() está correta no caso em que todos os arcos têm custo 1?  Compare com uma busca BFS.
  4. O espaço reservado para a fila de vértices na função GRAPHcptBF() é suficiente?
  5. Não deveríamos ter onqueue[v] = false depois de v = QUEUEget( )?
  6. Aplique a função GRAPHcptBF() aos grafos abaixo. Faça o rastreamento da execução da função.
    coelho-2011/coelho2011-18-1.png         coelho-2011/coelho2011-18-2.png
  7. Suponha que a função GRAPHcptBF() é aplicada a um grafo que tem um ciclo negativo. Suponha que v-w é o primeiro arco a ser relaxado tal que pa[w] ≠ -1 antes da relaxação.  É verdade que logo depois da relaxação a regressão de w termina num ciclo negativo?
  8. [Sedgewick 21.134]  Modifique a função GRAPHcptBF() de modo que ela devolva um ciclo negativo se algum existir.

Exercícios 4

  1. ★ Escreva uma função que receba um grafo G com custos nos arcos e encontre um ciclo negativo, onde quer que ele esteja.  Note que não estamos supondo que todos os vértices de G estão ao alcance de algum vértice fixo.
  2. Escreva uma versão especializada da função GRAPHcptBF() para o caso em que os custos dos arcos são todos positivos. (Não precisamos do pseudovértice V para separar os dois segmentos da fila.) Compare o seu código com a implementação GRAPHcptD1() do algoritmo de Dijkstra e com a função GRAPHspt() que calcula caminhos mínimos na ausência de custos.
  3. Caminho máximo.  Dado um grafo com custos positivos nos arcos, quero encontrar um caminho simples de custo máximo que vá de um vértice s a um vértice t.  Para resolver esse problema, mutiplique o custo de cada arco por  −1  e depois use o algoritmo de Bellman-Ford.  Analise e discuta essa ideia.

Conclusão

O algoritmo de Bellman-Ford prova o seguinte teorema: Um grafo com custos nos arcos tem uma CPT com raiz s se e somente se todos os vértices estão ao alcance de s e o grafo não tem ciclo negativo.  A propósito, já vimos provas algorítmicas de três outros teoremas da mesma família:

O primeiro teorema é demonstrado pela função GRAPHcycle0(), o segundo é demonstrado pela função UGRAPHtwoColor(), e o terceiro é demonstrado pela função UGRAPHbipMatching() somada ao fragmento de código que calcula uma cobertura mínima.


Perguntas e respostas