Algoritmos para Grafos, via Sedgewick  |  Índice remissivo

Caminhos mínimos

Qual o caminho mais curto do bairro de Pinheiros ao bairro de Itaquera na cidade de São Paulo?

[Esta página corresponde aproximadamente à seção 18.7 (Breadth-First Search), p.114-124, do capítulo 18 do livro de Sedgewick.]

shortest-path.png
A distância de s a t é 6. 
[Copiado das transparências 2006 de Sedgewick.]

Distância

Um caminho C num digrafo é mínimo se não existe outro caminho com mesma origem e mesmo término que C mas comprimento menor que o de C.  É claro que todo caminho mínimo é simples.

distância  de um vértice s a um vértice t num digrafo é o comprimento de um caminho mínimo de st.  Se não existe caminho algum de s a t, a distância de s a t é infinita.   A distância de s a t é d se e somente se  (1) existe um caminho de comprimento d de st  e  (2) nenhum caminho de s a t tem comprimento menor que d.

Em geral, a distância de um vértice s a um vértice t é diferente da distância de ts. Num grafo, entretanto, as duas distâncias são iguais.

Nosso problema básico é determinar a distância de um vértice a outro:

Problema:  Dados vértices st de um digrafo G, determinar a distância de st.

A seguinte generalização do problema não é mais difícil que o original:  Dado s, calcular a distância de s a cada um dos demais vértices de G.

grid-plus-nolabels.png

Qual a distância do vértice noroeste ao vértice sudeste?

Exemplo A

digraph with 6 vertices

Seja G o digrafo com 6 vértices definido pelos arcos listados a seguir.

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

Segue a tabela de distâncias do vértice 0 a cada um dos demais vértices:

                      v    0 1 2 3 4 5
     distância de 0 a v    0 3 1 1 1 2

Exercícios 1

  1. Qual pode ser, em geral, a distância de um vértice s a um vértice t num digrafo com V vétices e A arcos?
  2. Seja s um vértice fixo de um digrafo G. Para cada vértice x de G, seja dist(x) a distância de sx.  Mostre que  dist(w) ≤ dist(v) + 1  para todo arco v-w.
  3. [Sedgewick 18.54, p.124]  Seja s um vértice de um digrafo G. Suponha que conhecemos a distância de s a cada um dos demais vértices de G. O que se pode dizer sobre a distância entre dois vértices quaisquer vw?   Repita o exercício com grafo no lugar de digrafo.
  4. Discuta a seguinte função. Ela promete calcular a distância de s a cada um dos demais vértices do digafo G.
         #define INFINITO G->V
         static int dist[maxV];
         void distancias( Graph G, Vertex s) { 
            Vertex v;
            for (v = 0; v < G->V; v++) dist[v] = INFINITO;
            dist[s] = 0;
            dfsR( G, s);
         }
         void dfsR( Graph G, Vertex v) { 
            link p;
            for (p = G->adj[v]; p != NULL; p = p->next) 
               if (dist[p->w] == INFINITO) { 
                  dist[p->w] = dist[v] + 1;
                  dfsR( G, p->w);
               }
         }
    
  5. Discuta a seguinte função. Ela promete calcular a distância de s a cada um dos demais vértices do digafo G.
         #define INFINITO G->V
         static int dist[maxV];
         void distancias( Digraph G, Vertex s) { 
            int d;
            Vertex v, w;
            for (v = 0; v < G->V; v++) dist[v] = INFINITO;
            dist[s] = 0;
            for (d = 0; d < G->V; d++) 
               for (v = 0; v < G->V; v++) 
                  if (dist[v] == d)   
                     for (w = 0; w < G->V; w++) 
                        if (G->adj[v][w] == 1 && dist[w] == INFINITO)  
                           dist[w] = d + 1;
         }
    

Busca em largura e distâncias

A busca em largura é o método perfeito para calcular distâncias a partir de um dado vértice s.  Ele visita todos os vértices que estão à distância 1 de s, depois todos os vértices que estão à distância 2 de s, e assim por diante.

static int dist[maxV];

/* Armazena no vetor dist a distância do vértice s a cada um dos demais vértices do digrafo G: para cada vértice v, dist[v] é a distância de sv.  (Código inspirado no programa 18.9, p.119, de Sedgewick.) */

void DIGRAPHdist( Digraph G, Vertex s) { 
   const int INFINITO = G->V;
   Vertex v, w;
   for (v = 0; v < G->V; v++)
      dist[v] = INFINITO;
   QUEUEinit( G->V);
   dist[s] = 0;
   QUEUEput( s); 
   while (!QUEUEempty( )) {
      v = QUEUEget( ); 
      for (w = 0; w < G->V; w++)
         if (G->adj[v][w] == 1 && dist[w] == INFINITO) { 
            dist[w] = dist[v] + 1;
            QUEUEput( w); 
         }
   }
   QUEUEfree( ); 
}

(Nesse código, representamos o infinito por G->V.  Isso faz perfeito sentido pois nenhum caminho tem comprimento ≥ G->V.)   Para entender o algoritmo e provar que a função DIGRAPHdist está correta é preciso observar os seguintes invariantes:  no início de cada iteração do while,

  1. a fila está em ordem crescente de dist,
  2. se v é o primeiro vértice da fila e vv é o último então dist[vv] ≤ dist[v]+1,
  3. para cada arco w-z,  se  dist[z]-dist[w] > 1  então w está na fila.

Prova dos invariantes: No início da primeira iteração, essas propriedades são obviamente válidas. Suponha agora que elas valem no início de uma iteração qualquer em que a fila não está vazia e seja v o primeiro vértice da fila. Vamos mostrar que as propriedades continuam válidas no início da próxima iteração:

No fim do processo iterativo, a fila está vazia e portanto, em virtude do invariante 3, o vetor dist é um potencial, ou seja,

dist[y] - dist[x] ≤ 1   para cada arco x-y de G.

(Imagine que dist[x] é a altura de x acima do solo. É claro que s está à altura 0. A desigualdade acima diz que nenhum arco sobe mais que uma unidade.)  Segue daí que para qualquer caminho P que vai de s a um vértice t tem-se dist[t]-dist[s] ≤ |P|, sendo |P| o comprimento de P.  Como dist[s] é 0, temos

dist[t]  ≤  |P|.

Em outras palavras, para todo vértice t, não existe caminho de st que seja mais curto de  dist[t].   Por outro lado, é fácil verificar (veja a arborescência da busca abaixo) que no fim da execução do algoritmo, para cada vértice t, existe um caminho que vai de st e tem comprimento dist[t]  (a menos que dist[t] seja INFINITO).  Logo, dist[t] é, de fato, a distância de st.

Exemplo B

Considere o grafo (ou seja, digrafo simétrico) definido pelo conjunto de arestas abaixo. (Trata-se do mesmo exemplo que examinamos na página de busca em largura.)  Represente o grafo por sua matriz de adjacência.

     0-2 2-6 6-4 4-5 5-0 0-7 7-1 7-4 3-4 3-5
undirected graph with 8 vertices

Agora use a função DIGRAPHdist para calcular as distâncias a partir do vértice 0.  Eis o estado do vetor dist no início de sucessivas iterações (com "-" no lugar de "INFINITO"):

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

Desempenho

Quanto tempo a função DIGRAPHdist consome quando aplicada a um digrafo com V vértices e A arcos?   A função consome tempo proporcional a  V2  no pior caso.  Portanto, a função é linear quando aplicada a digrafos densos.

A variante da função que usa listas de adjacência consome tempo proporcional a  V+A  no pior caso,  e portanto é linear para todos os digrafos.

Exercícios 2

  1. Escreva uma versão da função DIGRAPHdist para digrafos representados por listas de adjacência.
  2. Mostre que no início de cada iteração de DIGRAPHdist existe um inteiro não negativo d tal que a fila consiste em um ou mais vértices cujo dist vale d seguidos de zero ou mais vértices cujo dist vale d+1.
  3. Escreva uma função GRAPHdist que calcule distâncias a partir de um dado vértice s em um grafo.  Quais as diferenças entre sua função e DIGRAPHdist?
  4. A seguinte função promete calcular a distância de um vértice s a cada um dos demais vértices do grafo G.  A função está correta?
         #define INFINITO G->V
         static int dist[maxV];
         void distance( Graph G, Vertex s) { 
            Vertex v, w;
            int conta = 0;
            for (v = 0; v < G->V; v++) dist[v] = INFINITO;
            QUEUEinit( G->V);
            dist[s] = 0;
            QUEUEput( s); 
            while (!QUEUEempty( )) {
               v = QUEUEget( ); 
               for (w = 0; w < G->V; w++)
                  if (G->adj[v][w] == 1 && dist[w] == INFINITO) { 
                     dist[w] = conta;
                     QUEUEput( w);
                  }
               conta++;
            }
         }
    
  5. [Com implementação da fila]  Escreva uma função que receba um vértice  s  de um digrafo  G  (representado por suas listas de adjacência) e armazene num vetor global  dist  as distâncias de  s  a cada um dos demais vértices.   Sua função não deve invocar outras funções.  Escreva código simples, sem variáveis e instruções supérfluas.
  6. Seja s um vértice de um grafo conexo G.  Suponha dado um vetor dist tal que dist[v] é a distância de s a v para cada vértice v.  Escreva uma função que receba um vértice w e imprima um caminho mínimo de ws.

Arborescência de distâncias

Se acrescentar ao código de DIGRAPHdist o cálculo de um vetor de pais parent, teremos uma representação da arborescência de busca em largura.  É claro que para cada arco v-w da arborescência tem-se dist[w]-dist[v] = 1.

Essa arborescência pode ser chamada arborescência das distâncias pois, para cada vértice t, o único caminho de s a t na arborescência é um caminho mínimo no digrafo  (a não ser, é claro, que dist[t] seja INFINITO).

O fragmento de código abaixo imprime o inverso do caminho de s a t na arborescência:

     for (v = t; v != s; v = parent[v])
        printf( "%d-", v)
     printf( "%d\n", s)

Exercícios 3

  1. Considere o grafo definido pelas arestas abaixo. Represente o grafo por uma matriz de adjacência.
         8-9 3-7 1-4 7-8 0-5 5-2 3-8 2-9 0-6 4-9 2-6 6-4
    

    Calcule a distância do vértice 0 a cada um dos demais vértices. Faça um desenho da arborescência de distâncias.   Repita o exercício supondo que o grafo é representado por listas de adjacência construídas inserindo as arestas, na ordem dada, num grafo inicialmente vazio.

  2. Escreva uma versão de DIGRAPHdist que calcule uma arborescência de distâncias. Escreva código simples e "enxuto", sem o vetor dist.
  3. Depois de executar a função DIGRAPHdist com argumentos Gs, seja  X  o conjunto dos vértices v para os quais dist[v] != INFINITO.  Descreva o conjunto dos arcos que atravessam o corte (X,X).

Exercícios 4

  1. [Sedgewick 18.55, p.124]  Denote por dist(v,x) a distância entre dois vértices v e x num grafo conexo.  O diâmetro de um grafo conexo é a valor máximo da expressão  dist(v,x)  com v e x variando no conjunto de todos os vértices.  Escreva uma função que calcule o diâmetro de qualquer grafo conexo.
  2. Um vértice v de um grafo é central se minimiza maxx dist(vx).  Escreva uma função que calcule todos os vértices centrais de qualquer grafo dado.

Perguntas e respostas

Valid HTML 4.01 Strict Valid CSS!