Algoritmos para Grafos, via Sedgewick  |  Índice remissivo

Pontes e grafos aresta-biconexos

Esta página trata de grafos (ou seja, digrafos simétricos) que deixam de ser conexos quando perdem uma de suas arestas.  Vamos nos restringir a grafos pois os conceitos discutidos não fazem sentido em digrafos não simétricos.

[A página é um resumo da primeira parte da seção 18.6 (Separability and Biconnectivity), p.106-114, do capítulo 18 (Graph Search) do livro de Sedgewick.]

Pontes em grafos

Uma aresta de um grafo é uma  ponte  (= bridgeseparation edge)  se ela é a única aresta que atravessa algum corte do grafo.  [Pontes são também conhecidas com arestas de corte, mas nós não vamos usar essa terminologia.]  Em outras palavras, uma ponte é uma aresta cuja remoção aumenta o número de componentes do grafo.

Problema:  Encontrar as pontes de um grafo dado.

Para resolver o problema, pode-se aplicar cegamente a definição de ponte.  Mas é possível fazer coisa mais eficiente.   O primeiro passo nessa direção é o seguinte fato básico:  em qualquer grafo,

uma aresta é uma ponte  se e somente se  ela não pertence a um ciclo não trivial.

Em outras palavras, toda aresta é de um e apenas um de dois tipos: ou ela é uma ponte ou pertence a um ciclo não trivial.

coelho-2011/bridges-2-coelho.png

Exercícios 1

  1. Critique o seguinte algoritmo para encontrar as pontes de um grafo:  para cada aresta v-w, verifique (com auxílio de DIGRAPHpath talvez) se a remoção da aresta aumenta o número de componentes do grafo.  Quanto tempo esse algoritmo consome?   Basta, na verdade, aplicar a veirificação aos arcos que fazem parte de uma floresta DFS (por que?).  Quanto tempo o algoritmo consome nesse caso?
  2. [Dicotomia pontes/ciclos]  Classifique as arestas do grafo definido a seguir:  para cada aresta, diga se ela é uma ponte ou pertence a ciclo não trivial.
         0-1 1-2 2-0 1-3 1-4 4-5 5-6 6-4
    
  3. Mostre que todas as arestas de qualquer floresta são pontes.  A recíproca é verdadeira?

Pontes e busca em profundidade

Cada aresta de um grafo é um par de arcos antiparalelos. Se o par de arcos v-w e w-v é uma ponte, diremos que cada arco do par faz parte da ponte.   Se fizermos uma busca em profundidade no grafo, um dos dois arcos que fazem parte de uma ponte será necessariamente um arco de arborescência (por que?). Portanto, basta conferir os arcos da floresta DFS.  Agora observe a seguinte

Propriedade [Property 18.5, p.107, Sedgewick]:  Em qualquer busca em profundidade, um arco v-w da floresta DFS faz parte de uma ponte  se e somente se  o único arco de retorno que liga um descendente de w a um ancestral de v é o arco w-v.

Essa propriedade permite determinar todas as pontes do grafo com uma única busca DFS.  Para fazer isso, é preciso introduzir a ideia de menor número de pré-ordem acessível a partir de um vértice.

coelho-2011/bridges-3-coelho.png

Vermelho: floresta DFS. Roxo: arcos descendentes. Verde: arcos de retorno.
[Copiado dos slides de José Coelho de Pina, 2011.]

Menor pre ao alcance de um vértice

Tome uma floresta DFS no grafo e considere a numeração dos vértices em pré-ordem.  Para tirar proveito da propriedade acima, vamos calcular, para cada vértice v, o menor número de pré-ordem (= lowest preorder number) que pode ser alcançado a partir de v usando qualquer arco de retorno exceto o arco-pai de v (ou seja, o arco de retorno que vai de v ao pai de v).  Esse número será denotado por

low[v].

Explico melhor:  Digamos, para efeito desta explicação, que um caminho com origem v é interessante se

  1. tem comprimento 0  ou
  2. tem comprimento 1 e consiste em um arco de retorno diferente do arco-pai de v  ou
  3. tem comprimento maior que 1, o seu último arco é de retorno, e todos os demais arcos pertencem à floresta DFS.

(No caso 3, o último arco do caminho é fatalmente diferente do arco-pai de v.)  Por exemplo, o caminho de comprimento 0 de v a v é interessante.  Outro exemplo: se v-w é um arco de retorno e w é diferente do pai de v então o caminho v-w é interessante.  Mais um exemplo: se v-w é um arco da floresta DFS e w-x é um arco de retorno então o caminho  v-w-x  é interessante (mesmo que x seja o pai de v).

Digamos agora que o valor de um caminho interessante é o número de pré-ordem do último vértice do caminho.  Podemos dizer então que

low[v]  é o valor de um caminho interessante de valor mínimo.

É claro que  low[v] ≤ pre[v]  para todo vértice v,  sendo pre[v] o número de pré-ordem de v.  Além disso, para todo arco  v-w  do grafo,

Essas observações permitem calcular low recursivamente (ao mesmo tempo que pre e o vetor de pais). É o que faremos abaixo.

Exemplo A

Considere o grafo com vértices  a b c d e f g h i j  definido pelas listas de adjacência a seguir.

     a: b          
     b: e c a      
     c: j e d b    
     d: e c        
     e: h g f d c b
     f: g e        
     g: f e        
     h: j i e      
     i: h          
     j: h c        

Uma busca DFS produz a seguinte floresta DFS (veja vetor parent) e numeração em pré-ordem (veja vetor pre):

            v    a  b  c  d  e  f  g  h  i  j
     parent[v]   a  a  j  c  b  g  e  e  h  h
        pre[v]   0  1  5  6  2  9  8  3  7  4

Segue uma figura da floresta DFS (que tem uma só arborescência) e uma cópia da figura que mostra os números pre dos vértices.  Os arcos de retorno, que não aparecem nas figuras, são  c-b c-e d-e f-e .

             a                        (0)	    
             |                         |	    
             b                        (1)	    
             |                         |	    
             e                        (2)	    
            / \                       / \	    
           h   g                    (3) (8)	    
          / \   \                   / \   \	    
         j   i   f                (4) (7) (9)   
        /                         /		   
       c                        (5)		   
      /                         /		   
     d                        (6)

Eis o menor número pre ao alcance de cada vértice v:

            v    a  b  c  d  e  f  g  h  i  j
        low[v]   0  1  1  2  1  2  2  1  7  1

A seguir, os caminhos interessantes que explicam os valores de low:

                 a
                 b
               c-b
               d-e
         e-h-j-c-b
               f-e
             g-f-e
           h-j-c-b
                 i
             j-c-b

Exemplo B

Considere o grafo definido pela lista de arestas:  0-2 0-3 2-3 2-8 3-8 8-1 8-10 10-4 10-7 4-7 4-9 9-5 9-6 5-6 .  Uma busca DFS no grafo produz a numeração pre dada a seguir.

         v   0  1  2  3  4  5  6  7  8  9 10
     pre[v]  0  4  1  2  6  8  9 10  3  7  5

A correspondente floresta DFS é indicada pelos arcos vermelhos na figura.

coelho-2011/bridges-3-coelho.png

O vetor low a seguir dá o menor pre ao alcance de cada vértice.

         v   0  1  2  3  4  5  6  7  8  9 10
     low[v]  0  4  0  0  5  7  7  5  1  7  5

Exercícios 2

  1. Seja G uma árvore.  Escolha uma floresta DFS de G.  Quanto vale low[v] para cada vértice v de G?
  2. Suponha que o grafo G consiste em um único ciclo.  Escolha uma floresta DFS de G.  Quanto vale low[v] para cada vértice v de G?
  3. Escreva uma função que calcule low[v] para cada vértice v de um grafo dado. Faça isso ao mesmo tempo que calcula pre, mas sem usar o vetor parent. Use essa função para imprimir uma tabela com os valores de pre[w] e low[w] para cada w.
  4. Critique a maneira de calcular o vetor low descrita no arquivo anexo.  O algoritmo está correto? É eficiente?
  5. Faça uma busca DFS no grafo definido a seguir (supondo que o grafo é representado por sua matriz de adjacência).
         0-1 1-2 1-4 2-3 2-4 2-9 3-4 4-5 4-6 4-7 5-6 7-8 7-9
    

    Exiba o vetor  pre  definido pela busca.  Exiba o vetor  low  associado à floresta DFS.

Algoritmo das pontes

propriedade acima pode ser reformulada assim: em qualquer floresta DFS de um grafo, um arco v-w da floresta faz parte de uma ponte se e somente se low[w] é igual a pre[w].  Com essa observação, é possível encontrar todas as pontes de um grafo com uma única busca em profundidade.

/* Nosso grafos terão no máximo maxV vértices. */

static int conta, pre[maxV], numpts, low[maxV];
static Vertex parent[maxV];

/* A função abaixo calcula e devolve o número de pontes do grafo G. Também imprime todas as pontes.  (O código foi inspirado no programa 18.7, p.108, de Sedgewick.) */

int GRAPHbridges( Graph G) { 
   Vertex v;
   conta = numpts = 0;
   for (v = 0; v < G->V; v++) 
      pre[v] = -1;
   for (v = 0; v < G->V; v++)
      if (pre[v] == -1) {
         parent[v] = v;
         bridgeR( G, v);
      }
   return numpts;
}
void bridgeR( Graph G, Vertex v) { 
   link p; 
   pre[v] = conta++; 
   low[v] = pre[v];
   for (p = G->adj[v]; p != NULL; p = p->next) {
      Vertex w = p->w;
      if (pre[w] == -1) {
         parent[w] = v;
         bridgeR( G, w); 
         if (low[v] > low[w]) low[v] = low[w];
         if (low[w] == pre[w]) {
            numpts++; 
            printf( "%d-%d\n", v, w);
         }
      }
      else if (w != parent[v] && low[v] > pre[w]) 
         low[v] = pre[w];
   }
}

Desempenho

Como muitas outros algoritmos baseados em busca em profundidade, a função GRAPHbridges é linear:  ela consome tempo proporcional a V+E quando aplicada a um grafo com V vértices e E arestas.  A variante dessa função para matrizes de adjacência consome tempo proporcional a V2.

Exercícios 3

  1. Aplique a função GRAPHbridges a uma floresta.  Explique o que acontece em cada linha do código de bridgeR.
  2. Rastreie a aplicação do algoritmo GRAPHbridges ao grafo definido a seguir (supondo representação por sua matriz de adjacência).
         0-1 1-2 1-4 2-3 2-4 2-9 3-4 4-5 4-6 4-7 5-6 7-8 7-9
    
  3. [Sedgewick figura 18.17]  Execute a função GRAPHbridges para encontrar todas as pontes do grafo definido pelo seguinte conjunto de arestas.
    Sedgewick-part5-java/bridges-fig-18-16.png
         0-6 0-1 0-5 1-2 2-6 3-5 4-5 4-11 4-9 4-3 6-7 7-8 7-10 9-11 10-8 11-12
    

    (Adote a representação por listas de adjacência e insira as arestas, na ordem dada, num grafo inicialmente vazio.)  Faça um desenho da floresta DFS do grafo.

  4. Escreva a documentação da função bridgeR, ou seja, diga o que a função faz.
  5. A seguinte variante da função bridgeR, está correta?
          void bridgeR( Graph G, Vertex v) { 
             link p; 
             pre[v] = conta++; 
             low[v] = pre[v];
             for (p = G->adj[v]; p != NULL; p = p->next) {
                Vertex w = p->w;
                if (pre[w] == -1) {
                   parent[w] = v;
                   bridgeR( G, w); 
                   if (low[v] > low[w]) low[v] = low[w];
                }
                else if (w != parent[v] && low[v] > pre[w]) 
                   low[v] = pre[w];
             }
             if (low[v] == pre[v]) {
                numpts++; 
                printf( "%d-%d\n", parent[v], v);
             }
          }
    

Aresta-biconexão

Um grafo é  aresta-biconexo (= edge-connected)  ou  2-aresta-conexo  se for conexo e não tiver pontes.  Portanto, é preciso remover pelo menos duas arestas de um grafo aresta-biconexo para que ele deixe de ser conexo.  (Portanto, grafos desse tipo são "tolerantes a falhas".)

Fato básico importante:  Um grafo é aresta-biconexo se e somente se, para cada par (s,t) de seus vértices, existem (pelo menos) dois caminhos de s a t sem arestas em comum.

Uma componente aresta-biconexa  (= edge-connected component)  de um grafo é qualquer componente do grafo que se obtém depois que todas as pontes são removidas.

coelho-2011/aresta-biconexo-coelho.png

Exercícios 4

  1. [Sedgewick 18.33 modificado, p.113]  Faça um desenho da floresta DFS do grafo a seguir;
         8-9 3-7 1-4 7-8 0-5 5-2 3-8 2-9 0-6 4-9 2-6 6-4
    

    (Adote a representação por listas de adjacência e insira as arestas, na ordem dada, num grafo inicialmente vazio.) Execute a função GRAPHbridges para encontrar todas as pontes do grafo. Encontre todos as componentes aresta-biconexas.

  2. [Sedgewick 18.45, p.114]  Escreva uma função que receba um grafo G e devolva 1 se o grafo é aresta-biconexo e devolva 0 em caso contrário. (Sugestão: modifique o código de all_briges e bridgeR.)
  3. Seja G um grafo aresta-biconexo. Sejam s e t dois vértices de G. Prove que existem dois caminhos de s e t sem arestas em comum.
  4. Qual o número mínimo de arestas de um grafo aresta-biconexo que tem V vértices?

Valid HTML 4.01 Strict Valid CSS!