Algoritmos para Grafos  |  Índice

Caminhos de comprimento mínimo

TinyNetwork-x.png

Qual o caminho mais curto, em número de quarteirões, de um ponto A a um ponto B de uma grande cidade?

Este capítulo é uma introdução geral ao problema do caminho de comprimento mínimo em grafos. Embora corriqueiro, o problema tem sutilezas que precisam ser bem entendidas antes que possamos tentar resolvê-lo. Um bom algoritmo para o problema do caminho mínimo será discutido num próximo capítulo. O presente capítulo tratará apenas de condições necessárias e suficientes de minimalidade bem como de um algoritmo especial para grafos acíclicos.

Sumário:

Caminhos mínimos e distâncias

Um caminho C num grafo é mínimo se não existe outro caminho que [!] tenha a mesma origem e o mesmo término que C mas comprimento menor que o de C. (Vale lembrar que o comprimento é o número de arcos do caminho.)  Na figura acima, por exemplo, o caminho  0-2-7-5-1-3-6  não é mínimo porque o caminho  0-2-7-3-6  é mais curto.

Problema do caminho mínimo:  Dados vértices st de um grafo G, encontrar um caminho mínimo em G que tenha origem s e término t.

É claro que todo caminho mínimo é simples. (Portanto, o simples na expressão caminho simples mínimo é redundante.)  É claro também que nem toda instância do problema tem solução: se t não está ao alcance de s então não existe caminho algum de st.

distância  de st é o comprimento de um caminho mínimo com origem s e término t. É claro que a distância de st é 0 se e somente se st. É claro também que a distância de s a t é  d  se e somente se  (1) algum caminho de st tem comprimento d  e  (2) todo caminho de s a t tem comprimento pelo menos d. Se não existe caminho algum de st, podemos dizer que a distância de st é ∞ (infinita).

O conceito de distância é dirigido: a distância de st é, em geral, diferente da distância de ts. Por isso, dizemos distância de s a t e não distância entre s e t.

TinyNetwork-x.png

Exemplo A.  No grafo da figura, o caminho 0-2-7-3-6 tem comprimento 4. Nenhum caminho de 06 tem comprimento menor que 4. Logo, o caminho 0-2-7-3-6 é mínimo e a distância do vértice 0 ao vértice 6 é 4. Agora considere a distância na direção contrária. Verifique que a distância de 6 a 0 é 1.

Exercícios 1

  1. Prove que todo caminho mínimo é simples.
  2. Qual a distância do vértice noroeste ao vértice sudeste em cada um dos grafos abaixo? (O primeiro grafo é não-dirigido.)
    grid-plus-thick.png           directed-grid.png
  3. Mostre que um grafo pode ter mais de um caminho mínimo de um dado vértice s a um dado vértice t. (Por isso dizemos um caminho mínimo e não o caminho mínimo.)
  4. Mostre que todo caminho em uma árvore radicada é mínimo.
  5. Qual a distância de um sorvedouro a cada um dos demais vértices do grafo?
  6. Seja H um sub­grafo de um grafo G. Se s e t são vértices de H, a distância de s a t em H é maior, igual, ou menor que a distância de st em G?
  7. [Sedgewick 18.54]  Suponha que conhecemos a distância de um certo vértice s a cada um dos demais vértices de um grafo. Dados dois vértices vw, o que se pode dizer sobre a distância de vw?  Repita o exercício supondo que o grafo é não-dirigido.
  8. Critique a seguinte função. Ela promete armazenar no vetor dist[] a distância de s a cada um dos demais vértices de um grafo não-dirigido G.
    #define UGraph Graph
    static int dist[1000];
    void distancias( UGraph G, vertex s) { 
       for (vertex v = 0; v < G->V; ++v) dist[v] = INFINITY;
       dist[s] = 0;
       dfsR( G, s); }
    
    void dfsR( UGraph G, vertex v) { 
       for (link a = G->adj[v]; a != NULL; a = a->next) 
          if (dist[a->w] == INFINITY) { 
             dist[a->w] = dist[v] + 1;
             dfsR( G, a->w); } }
    
  9. Num grafo com V vértices e A arcos, quais são os possíveis valores da distância de um dado vértice a outro?
  10. Segmentos de caminhos mínimos.  Prove a seguinte afirmação: Se C é um caminho mínimo num grafo então cada segmento de C também é um caminho mínimo.
  11. Discuta a seguinte função. Ela promete armazenar no vetor dist[] a distância de s a cada um dos demais vértices de um grafo G.
    void distancias( Graph G, vertex s, int *dist) { 
       for (vertex v = 0; v < G->V; ++v) dist[v] = INFINITY;
       dist[s] = 0;
       for (int d = 0; d < G->V-1; ++d) 
          for (vertex v = 0; v < G->V; ++v) 
             if (dist[v] == d)   
                for (link a = G->adj[v]; a != NULL; a = a->next) 
                   if (dist[a->w] == INFINITY)  
                      dist[a->w] = d + 1; }
    

Potencial e condições de minimalidade

Para provar que um dado caminho C é mínimo, precisamos mostrar que todo caminho com a mesma origem e o mesmo término que os de C é pelo menos tão comprido quanto C. Para fazer isso, vamos introduzir o conceito de potencial relaxado.

Um potencial para um grafo G é uma numeração dos vértices de G, ou seja, um vetor que associa um número a cada vértice de G. Em relação a um potencial h[ ], dizemos que [!] um arco v-w está tenso se h[w] − h[v]  > 1  e está relaxado se h[w] − h[v]  ≤ 1. Em outras palavras, v-w está tenso h[v] + 1 < h[w]  e está relaxado se

h[v] + 1  ≥  h[w] .

Um potencial é relaxado (ou viável) se deixa todos os arcos de G relaxados.

Qualquer potencial relaxado h[ ] dá uma cota inferior para as distâncias entre vértices: se P é um caminho de um vértice x a um vértice y então |P| ≥ h[y] − h[x], sendo |P| o comprimento de P. (Por exemplo, [!] se P é o caminho 0-1-2-3 então |P| = 1 + 1 + 1 ≥ h[3] − h[2] + h[2] − h[1] + h[1] − h[0] = h[3] − h[0].)  Isso leva à seguinte condição suficiente de minimalidade de um caminho.

Condição suficiente de minimalidade: Para qualquer caminho C de um vértice x a um vértice y, se existe um potencial relaxado h[ ] tal que h[y] − h[x] = |C| então C é mínimo (e portanto a diferença h[y] − h[x] é a distância de xy).

A recíproca dessa condição é verdadeira se nos restringirmos aos caminhos mínimos que têm uma origem comum: Para qualquer vértice s, o vetor das distâncias a partir de s é um potencial relaxado. Aqui, o vetor das distâncias a partir de s é o vetor d[ ] definido pela seguinte propriedade: para cada vértice v, d[v] é a distância de s a v.

TinyNetworkOnly.png

Exemplo B.  A tabela abaixo dá as distâncias a partir do vértice 5 no grafo da figura. Verifique pacientemente que d[] é o vetor das distâncias a partir de 5.

  v  0 1 2 3 4 5 6 7
d[v] 4 1 4 2 1 0 3 1

Agora verifique que d[] é um potencial relaxado.

TinyNetwork-x.png

Exemplo C.  A tabela abaixo especifica um potencial h[] no grafo da figura. Verifique que o potencial é relaxado. O comprimento do caminho 0-2-7-3-6 é exatamente igual à diferença h[6]-h[0]; portanto, esse caminho é mínimo e a distância de 06 é 4.

  v  0 1 2 3 4 5 6 7
h[v] 0 2 1 3 1 1 4 2

O comprimento de qualquer caminho de 01 é pelo menos h[1]-h[0]. Mas todos os caminhos de 01 têm comprimento maior que h[1]-h[0]. Qual a distância de 01?

TinyNetworkOnly.png

Exemplo D.  A tabela abaixo especifica um potencial h[] no grafo da figura. Verifique que o potencial é relaxado. O caminho 0-4-5-1 é mínimo pois seu comprimento é igual a h[1]-h[0]. O caminho 0-2-7-3 é mínimo pois seu comprimento é igual a h[3]-h[0].

  v  0 1 2 3 4 5 6 7
h[v] 0 3 1 3 1 2 4 2

A diferença h[3]-h[5] mostra que qualquer caminho de 53 tem comprimento pelo menos 1. Mas a distância de 53 é maior que 1.

Exercícios 2

  1. ★ Prove a condição suficiente de minimalidade de caminhos.
  2. ★ Prove a condição necessária de minimalidade de caminhos com origem num dado vértice: Para qualquer vértice s, o vetor das distâncias a partir de s é um potencial relaxado.

Árvore de caminhos curtos

Para encontrar um caminho mínimo de um vértice s a um vértice t é inevitável calcular todos os caminhos mínimos que começam em s. Isso sugere o seguinte conceito: uma árvore de caminhos curtos de um grafo é uma subárvore radicada T do grafo tal que

  1. todos os vértices do grafo estão em T  (ou seja, T é um sub­grafo gerador) e
  2. todo caminho em T a partir da raiz é mínimo no grafo.

Uma árvore de caminhos curtos também é conhecida pela abreviatura SPT de shortest-paths tree. Um grafo tem uma SPT com raiz s  se e somente se  todos os vértices do grafo estão ao alcance de s. Para grafos desse tipo, o problema do caminho mínimo equivale ao seguinte

Problema da SPT:  Dado um vértice s de um grafo G, encontrar uma SPT de G que tenha raiz s.

Mesmo nas instâncias do problema que não têm solução, é claro que existe uma SPT com raiz s no sub­grafo de G induzido pelo conjunto de vértices que estão ao alcance de s.

De acordo com a condição suficiente de minimalidade de caminhos, para resolver o problema da SPT basta encontrar uma árvore radicada geradora T, com raiz s, tal que o vetor das distâncias em T a partir de s seja um potencial relaxado em G.

spt-from-5.png

Exemplo E.  A figura mostra uma sub­árvore radicada T de um grafo G. A raiz da árvore é 5. Veja a lista de todos os caminhos em T que têm origem 5:

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

Na tabela abaixo, h[v] é o vetor das distâncias em T a partir da raiz. Verifique que h[] é um potencial relaxado em G. Portanto, T é uma SPT de G.

  v   0 1 2 3 4 5 6 7
h[v]  4 1 4 2 1 0 3 1

Exercícios 3

  1. Seja G um grafo com vértices  0 1 2 3 4 5 6 7. Suponha que os caminhos abaixo são mínimos. Extraia uma SPT dessa lista de caminhos.
    0
    0-1
    0-1-2
    0-1-3
    0-4
    0-4-5
    0-4-3-6
    0-1-5-7
    
  2. ★ Suponha que todos os vértices de um grafo G estão ao alcance de um vértice s. Prove que G tem uma SPT com raiz s. (Sugestão: Observe que todo caminho mínimo é necessariamente simples. Depois, observe que o segmento inicial de qualquer caminho mínimo é um caminho mínimo.)
  3. Mostre que um grafo pode ter mais de uma SPT com uma dada raiz s. (Por isso dizemos uma SPT e não a SPT.)
  4. Condição suficiente para SPT.  Seja T uma árvore radicada geradora de um grafo G. Seja s a raiz de T e h[ ] o vetor das distâncias em T a partir de s. Suponha que h[ ] é um potencial relaxado em G. Prove que T é uma SPT.
  5. Condição necessária para SPT.  Seja T uma SPT de um grafo G. Seja s a raiz de T e h[ ] o vetor das distâncias em T a partir de s. Prove que h[ ] é um potencial relaxado em G.
  6. Verificação de SPT.  Escreva uma função que receba um grafo G e uma árvore radicada T em G e diga se T é uma SPT de G. Suponha que T é representada por um vetor de pais pa[].

Caminhos mínimos em dags

topsortDAGk-x.png

Examinaremos a seguir um algoritmo para o problema da SPT restrito a dags, ou seja, a grafos acíclicos. A prova da correção do algoritmo depende da condição suficiente para SPT indicada acima.

Como se sabe, um grafo é acíclico se e somente se tem uma numeração topológica e portanto também uma permutação topológica, digamos vv[0..V-1], dos vértices. O algoritmo processará os vértices em ordem topológica: primeiro vv[0], depois vv[1], depois vv[2], etc.

Ao receber um dag G e um vértice s, o algoritmo deve encontrar uma SPT de G com raiz s. É preciso tomar algumas decisões de projeto: vamos supor que (1) o algoritmo recebe uma permutação topológica vv[] e (2) todos os vértices de G estão ao alcance de s  (e portanto vv[0] ≡ s).

#define Dag Graph
/* A função recebe um dag G,
   uma permutação topológica vv[] dos vértices,
   e um vértice s de G.
   A função supõe que todos os vértices estão ao alcance de s.
   A função armazena em pa[]
   o vetor de pais de uma SPT de G com raiz s.

   As distâncias a partir de s
   são armazenadas no vetor dist[].
   O espaço para os vetores pa[] e dist[]
   deve ser alocado pelo usuário.

   (O código foi inspirado no programa 21.6
   de Sedgewick.) */
void DAGspt( Dag G, vertex *vv, vertex s,
             vertex *pa, int *dist)
{ 
   const int INFINITY = G->V;
   for (vertex v = 0; v < G->V; ++v)
      pa[v] = -1, dist[v] = INFINITY;
   pa[s] = s, dist[s] = 0;

   for (int j = 0; j < G->V; ++j) {
      vertex v = vv[j];
      for (link a = G->adj[v]; a != NULL; a = a->next) {
         vertex w = a->w; 
         if (dist[v] + 1 < dist[w]) {
            dist[w] = dist[v] + 1; // relaxação de v-w
            pa[w] = v;
         }
      }
   }
}

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

Para verificar que a função está correta, basta observar os seguintes invariantes: no início de cada iteração do processo iterativo principal,

  1. o vetor pa[] define uma árvore radicada, digamos T, com raiz s;
  2. para todo vértice x de G, dist[x] é a distância de sx em T ;
  3. para todo arco x-y de G, se x está em vv[0..j-1] então x-y está relaxado em relação a dist[].

(Segue imediatamente do invariante 2 que dist[x] vale INFINITY se e somente se x não está em T.)

Agora considere a última iteração, que começa com j ≡ G->V e termina imediatamente. Em virtude do invariante 3, dist[] é um potencial relaxado em G. Em virtude dos invariantes 1 e 2, T é uma árvore radicada em G e dist[] é o vetor das distâncias em T a partir de s. Resta apenas provar que T é geradora de G. Para isso, basta mostrar que não existe arco de G com ponta inicial em T e ponta final fora de T. Tome um arco qualquer x-y de G tal que x pertence a T. Como x-y está relaxado, dist[y] ≤ dist[x] + 1. Graças ao invariante 2, dist[x] < INFINITY. Segue daí que dist[y] < INFINITY e portanto y está em T. Isso mostra que T contém todos os vértices que estão ao alcance de s em G, e portanto todos os vértices de G. Concluímos assim que T satisfaz a condições suficientes para SPT (veja a seção Árvore de caminhos curtos). Portanto T é uma SPT de G.

A função DAGspt() 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 a condição não estiver satisfeita, a função produzirá uma SPT no sub­grafo induzido pelo conjunto de vértices que estão ao alcance de s.

Desempenho.  A função DAGspt() é linear. O consumo de tempo da função é proporcional a  V + A  no pior caso, sendo V o número de vértices e A o número de arcos do dag G. (Se nos restringirmos aos dags em que AV, podemos dizer que o consumo de tempo é proporcional a A.)  A variante de DAGspt() para dags representados por matriz de adjacências, consome tempo proporcional a V2 no pior caso.

Exemplo F.  Considere o dag definido pelos arcos  0-1 1-2 2-4 0-3 3-4. Note que todos os vértices estão ao alcance de 0 e adote a permutação topológica  0 1 2 3 4  dos vértices (faça uma figura). Aplique a função DAGspt() para calcular uma SPT com raiz 0. Veja o estado dos vetores dist[] e pa[] no início de sucessivas iterações (com * representando INFINITY e - representando -1):

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

Note que dist[4] começa valendo INFINITY, diminui para 3, e depois para 2. Os valores de pa[4] acompanham essa evolução. No fim, dist[] é o vetor das distâncias a partir de 0 e pa[] representa uma SPT.

Exemplo G.  Considere o dag definidos pelos arcos  0-1 0-2 1-2 0-3 3-4  e adote a permutação topológica  0 1 2 3 4  (faça uma figura). Queremos calcular uma SPT com raiz 1 no subdag induzido pelos vértices que estão ao alcance de 1. Veja o estado dos vetores dist[] e pa[] no início de sucessivas iterações:

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

No fim da execução de DAGspt(), todos os arcos estão relaxados em relação a dist[]. A árvore radicada definida por pa[] é uma SPT do sub­grafo induzido pelos vértices que estão ao alcance de 1. (Note que não existem arcos com ponta inicial na árvore e ponta final fora da árvore.)

Exercícios 4

  1. Contraexemplo 1.  Considere o dag definido pelos arcos  0-1 0-2 2-1 2-3 1-3. Queremos determinar as distâncias a partir do vértice 0. Verifique que a função DAGspt() produz resultados incorretos se os vértices não forem processados em ordem topológica.
  2. Contraexemplo 2.  Suponha que queremos determinar as distâncias a partir do vértice 0 no grafo definido pelo conjunto de arcos  0-2 2-1 1-3 3-2. (Note que o grafo não é um dag.) Examine os vértices na ordem  0 1 2 3. Mostre que a função produz resultados incorretos.
  3. Prova dos invariantes.  Prove os invariantes do processo iterativo em DAGspt().
  4. Considere o dag definido pelos arcos  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. Verifique que a sequência  5 1 3 6 4 7 0 2  é uma permutação topológica dos vértices. Verifique que a árvore radicada da figura é uma SPT com raiz 5.
  5. Calcule uma SPT com raiz 0 no dag definido pelos arcos  0-2 0-4 0-3 3-4 3-5 2-1 2-4 4-1 4-5 5-1. Use a permutação topológica  0 3 2 4 5 1.
  6. Nem tudo ao alcance de s.  Seja s um vértice de um dag G. Suponha que nem todos os vértices de G estão ao alcance de s. (Na figura à direita, por exemplo, nem todos os vértices estão ao alcance de 7.)  Mostre que a aplicação da função DAGspt() a Gs produz uma SPT do subdag induzido pelo conjunto de vértices que estão ao alcance de s.
  7. É verdade que no início de cada iteração em DAGspt() tem-se dist[vv[i]] ≤ dist[vv[k]] para cada i < j e cada k > j?
  8. ★ Escreva uma versão simplificada da função DAGspt() que receba um dag G, uma permutação topológica vv[] e dois índices i e k em 0..V-1 e devolva a distância de vv[i]vv[k]. Escreva código enxuto, sem variáveis e operações supérfluas.
  9. Versão recursiva.  Escreva uma função recursiva DAGsptR() que calcule a distância de um vértice s a um vértice t num dag.
  10. Escreva uma variante da função DAGspt() sem o parâmetro vv[]. A função deve calcular uma permutação topológica de G à medida que calcula as distâncias. (Sugestão: veja o algoritmo de eliminação iterada de fontes do capítulo Grafos topológicos.)
  11. Matriz de adjacências.  Adapte o código de DAGspt() a dags representados por matriz de adjacências.

Perguntas e respostas