Algoritmos para Grafos  |  Índice

Caminhos simples de custo máximo

Este capítulo é uma introdução geral ao difícil e traiçoeiro problema dos caminhos simples de custo máximo em grafos com custos nos arcos.

Vamos supor que os custos de todos os arcos são positivos, embora a maior parte dos conceitos e observações deste capítulo façam sentido com custos arbitrários.

Sumário:

does this digraph have a cycle?

O problema

Num grafo com custos nos arcos, o custo de um caminho é a soma dos custos dos arcos do caminho.  Convém lembrar que, por definição, caminhos não têm arcos repetidos.

Um caminho D é mais caro que um caminho C se tiver custo maior que o de C.  Dizemos que um caminho C  tem custo máximo  se nenhum caminho com a mesma origem e o mesmo término que C for mais caro que C

Problema do caminho simples de custo máximo: Dados vértices s e t de um grafo com custos positivos nos arcos, encontrar um caminho simples de custo máximo que tenha origem s e término t.

A qualificação simples não é redundante nesse problema, diferentemente do que acontece com caminhos de custo mínimo.  O problema tem solução se e somente se t está ao alcance de s.  A propósito, se st então a única solução do problema é o caminho trivial de comprimento 0.

Um caso particular célebre do problema é o do caminho hamiltoniano:  encontrar um caminho simples de st que passe por todos os vértices do grafo.  (Quanto ao problema do ciclo hamiltoniano, ele não é um caso particular do nosso problema pois sua solução tem um vértice repetido.)

Exemplo A.  Considere o grafo (não-dirigido) definido pelo conjunto de arcos abaixo. Suponha que todos os arcos têm custo 1. Um caminho simples de custo máximo do vértice 0 ao vértice 2 tem custo 4.

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

Exemplo B.  Considere o grafo (não-dirigido) definido pelo conjunto de arcos abaixo. Suponha que todos os arcos têm custo 1. Um caminho simples de custo máximo do vértice 0 ao vértice 3 tem custo 2.

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

Exemplo C.  Considere o grafo definido pelo conjunto de arcos abaixo. Suponha que todos os arcos têm custo 1. Um caminho simples de custo máximo do vértice 0 ao vértice 4 tem custo 2.  (Note que há um caminho de 04 que tem custo 5.)

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

Exercícios 1

  1. Seja s um vértice de um grafo com custos arbitrários (positivos e negativos) nos arcos. Qual o custo de um caminho simples de custo máximo de ss?
  2. Encontre um caminho de custo máximo de 07 no grafo com custos nos arcos definido a seguir.
         0-7  0-1  1-2  2-3  3-4  4-5  5-6  6-7
          60   10   10   10   10   10   10   10
    
  3. Ciclo hamiltoniano. Considere o seguinte problema: dado um grafo, encontrar um ciclo simples que passe por todos os vértices do grafo. Mostre como podemos decidir se esse problema tem solução usando um algoritmo para o problema do caminho simples de custo máximo.
  4. Circuito hamiltoniano. Considere o seguinte problema: dado um grafo não-dirigido, encontrar um circuito simples que passe por todos os vértices do grafo. Mostre como podemos decidir se esse problema tem solução usando um algoritmo para o problema do caminho simples de custo máximo.
  5. Caminho hamiltoniano.  Considere o seguinte problema: encontrar um caminho simples que passe por todos os vértices de um grafo não-dirigido. Mostre como podemos decidir se esse problema tem solução usando um algoritmo para o problema do caminho simples de custo máximo.
  6. Grafo não-dirigido.  Considere o seguinte problema: dados vértices s e t de um grafo não-dirigido com custos positivos nas arestas, encontrar um caminho simples de s a t que tenha o maior custo possível. Mostre como esse problema pode ser resolvido usando um algoritmo para o problema do caminho simples de custo máximo.
  7. Para quaisquer dois vértices st de cada um dos grafos abaixo, encontre um caminho simples de comprimento máximo de st.  Encontre um ciclo simples de comprimento máximo em cada um dos grafos.
    figs/grafos-exercicios/cube-thick.png       figs/grafos-exercicios/Petersen-rbrito.png       figs/grafos-exercicios/icosaedro-dodecaedro.png
  8. Considere o seguinte problema: encontrar um ciclo simples de custo máximo num grafo com custos positivos nos arcos. Mostre como esse problema pode ser resolvido usando um algoritmo para o problema do caminho simples de custo máximo.
  9. Considere o seguinte problema: encontrar um circuito simples de custo máximo num grafo não-dirigido com custos positivos nas arestas. Mostre como esse problema pode ser resolvido usando um algoritmo para o problema do caminho simples de custo máximo.
  10. Custos negativos.  Mostre que o problema do caminho simples de custo mínimo num grafo com custos negativos nos arcos é equivalente ao problema do caminho simples de custo máximo com custos positivos.
  11. 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.  Analise e discuta a seguinte ideia: para resolver esse problema, multiplique o custo de cada arco por  −1  e depois use o algoritmo de Bellman-Ford.

Um algoritmo para dags

Quando restrito a dags, o problema do caminho simples de custo máximo é fácil.  Em um dag, todos os caminhos são simples e portanto o problema pode ser reformulado assim:

Problema do caminho de custo máximo em dags: Dados vértices s e t de um dag com custos positivos nos arcos, encontrar um caminho de custo máximo que tenha origem s e término t.

A função DAGcpt() do capítulo Caminhos baratos em dags pode ser usada para resolver o problema:  basta inverter o sinal de todos os custos!  Outra possibilidade é deixar os custos inalterados e trocar INT_MAX e dist[v]+a->c < dist[a->w] por INT_MIN e dist[v]+a->c > dist[a->w], respectivamente, no código de DAGcpt().  Essas duas variantes de DAGcpt() são muito eficientes:  consomem tempo proporcional a V + A no pior caso, sendo V o número de vértices e A o número de arcos do dag.

Exercícios 2

  1. Caminho máximo.  Considere o dag com custos nos arcos definido abaixo.  A sequência  5 1 3 6 4 7 0 2  é uma permutação topológica dos vértices.  Verifique que a árvore destacada na figura é uma árvore de caminhos máximos (= longest-paths tree) com raiz 5. (Ignore a cor vermelha do vértice 0 e o fundo cinza do vértice 2.)
    5-4 4-7 5-7 5-1 4-0 0-2 3-7 1-3 7-2 6-2 3-6 6-0 6-4
     35  37  28  32  38  26  39  29  34  40  52  58  93
    
  2. Caminho de comprimento máximo em dag.  Considere o seguinte problema: Dado um dag sem custos nos arcos, encontrar um caminho simples C tal que nenhum outro caminho — quaisquer que sejam sua origem e seu término — tenha comprimento maior que o de C.  (É claro que algum caminho desse tipo começa numa fonte e termina num sorvedouro.)  Escreva uma função que resolva o problema.  O consumo de tempo de sua função deve ser proporcional, no pior caso, ao número de vértices do dag.
  3. PERT/CPM.  Considere o seguinte problema: Dado um dag com custos positivos nos arcos, encontrar um caminho simples C tal que nenhum outro caminho — quaisquer que sejam sua origem e seu término — tenha custo maior que o de C.  (É claro que algum caminho desse tipo começa numa fonte e termina num sorvedouro.)
         Esse problema surge naturalmente na administração de grandes projetos de engenharia que usam o Program Evaluation and Review Technique e o Critical Path Method para fazer o escalonamento de tarefas.  Os arcos representam tarefas do projeto e os vértices representam estágios do projeto. Uma tarefa v-w só pode começar depois que todos os arcos que desembocam em v estiverem concluídas. O custo de cada arco representa a duração da correspondente tarefa. O custo de um caminho de custo máximo é a duração total do projeto. Qualquer atraso em uma das tarefas no caminho de custo máximo aumenta o tempo de execução do projeto todo. Por isso, o caminho de custo máximo é um caminho crítico.
  4. Dual do teorema de Dilworth.  Seja G um dag sem custos nos arcos.  Mostre que um caminho de comprimento máximo tem comprimento igual ao tamanho de uma cobertura de tamanho mínimo.
         Uma cobertura é um conjunto  K0 K1Kj−1  de anticadeias tal que todo vértice de G pertence a pelo menos uma Ki.  O tamanho da cobertura é o número j.  Uma anticadeia (= antichain) é um conjunto K de vértices de G tal que não existe caminho em G que comece e termine em K.

Algoritmo de força bruta

O problema do caminho simples de custo máximo é difícil. Parte da dificuldade está na exigência de que os caminhos sejam simples. Mas a maior dificuldade decorre da exigência de custo máximo.  Nenhum algoritmo razoavelmente rápido foi encontrado ainda.

O problema não fica mais fácil se todos os arcos tiverem custo 1.  Também não fica mais fácil se restrito a grafos não-dirigidos.

Examinaremos a seguir um algoritmo de força bruta que calcula o custo de um caminho simples de custo máximo de st (embora não exiba o caminho propriamente dito).  O algoritmo é extremamente lento. pois examina todos os caminhos simples de st. Embora a ideia seja simplória, o código não é inteiramente óbvio.

static bool mark[1000];
/* Recebe um grafo G com custos positivos nos arcos
   e vértices st.
   Devolve o custo de um caminho simples de custo máximo
   de st.
   (Em particular, devolve 0 se s == t.)
   Se não existe caminho algum de s a t,
   devolve -1 (que representa infinito negativo).
   Cuidado! Esta função só é razoável para grafos muito pequenos.
   (O código foi inspirado no programa 17.12 
   de Sedgewick.) */ 
int GRAPHmaxCostPath( Graph G, vertex s, vertex t)
{
   for (vertex v = 0; v < G->V; ++v) 
      mark[v] = false;
   return dfsRmaxCostPath( G, s, t);
}
/* Esta função recebe um grafo G
   com custos positivos nos arcos, um conjunto de vértices marcados, 
   e vértices não marcados v e t
   (possivelmente iguais). A função examina o conjunto, digamos C, 
   de todos os caminhos simples de v a t
   que não têm vértices marcados.
   Se C é vazio, devolve -1.
   Senão, devolve o custo do caminho mais caro de C.
   (Em particular, devolve 0 se v == t.) 
   A função não altera o conjunto de vértice marcados. */
static int dfsRmaxCostPath( Graph G, vertex v, vertex t)
{
   if (v == t) 
      return 0;
   mark[v] = true;
   int m = -1;
   for (link a = G->adj[v]; a != NULL; a = a->next) {
      if (!mark[a->w]) {
         int mw = dfsRmaxCostPath( G, a->w, t);
         if (mw != -1 && a->c + mw > m) 
            m = a->c + mw;
      }
   }
   mark[v] = false; // atenção! backtrack
   return m;
}

Esse algoritmo usa uma variante da busca em profundidade conhecida como backtracking. A única diferença com relação ao código da busca em profundidade usual (veja o capítulo Acessibilidade) está na penúltima linha de dfsRmaxCostPath(), que desmarca o vértice v restaurando o valor original de mark[v]. Isso garante que todos os caminhos simples sejam examinados.

Desempenho.  O algoritmo examina todos os caminhos simples de st. Isso equivale, no pior caso, a examinar todas as permutações dos vértices do grafo. Um grafo com V vértices tem V ! tais permutações, e esse número é bem maior que 2V. Assim, o consumo de tempo de GRAPHmaxCostPath() cresce mais que exponencialmente com o tamanho do grafo. Portanto, o algoritmo só pode ser aplicado a grafos muito pequenos.

Exercícios 3

  1. Prove cuidadosamente que a função dfsRmaxCostPath() está correta, ou seja, que devolve o custo do caminho mais caro dentre os caminhos simples de v a t que não têm vértices marcados (a menos que um tal caminho não existe).
  2. Supondo que a função dfsRmaxCostPath() está correta, prove cuidadosamente que a função GRAPHmaxCostPath() está correta.
  3. [Sedgewick 17.107]  Considere o grafo não-dirigido definido pelo conjunto de arestas  1-2 5-2 4-2 2-6 0-8 3-0 1-3 3-6 1-0 1-4 4-0 4-6 6-5 6-9 9-0 3-1 4-3 9-2 4-9 7-9 5-0 9-7 7-3 4-5 0-5 7-8 .  Suponha que todas as arestas têm custo 1. Encontre um ciclo hamiltoniano (ou mostre que um tal ciclo não existe).
  4. É possível modificar os códigos de GRAPHmaxCostPath() e dfsRmaxCostPath() de modo a imprimir um caminho de custo máximo?
  5. Reescreva dfsRmaxCostPath() de modo que a parte central do código fique como indicado a seguir. Reescreva o código de GRAPHmaxCostPath() de acordo com a nova versão de dfsRmaxCostPath().
            mark[a->w] = true;
            int mw = dfsRmaxCostPath( G, a->w, t);
            mark[a->w] = false;
    
  6. A função GRAPHmaxCostPath() produz resultados corretos se alguns (ou todos) arcos tiverem custo negativo?
  7. Caminhos de comprimento especificado.  Escreva uma variante de GRAPHmaxCostPath() que tenha uma parâmetro adicional inteiro positivo d e decida se existe um caminho simples de s a t de comprimento d.
  8. Critique a seguinte proposta de solução do problema do caminho simples de custo máximo:  Inverta todos os custos (ou seja, troque c por 1/c) e calcule caminho de custo mínimo de st.
  9. Desafio.  Escreva um algoritmo que consuma tempo limitado por um polinômio no tamanho do grafo para resolver o problema do caminho simples de custo máximo.