Algoritmos para Grafos, via Sedgewick  |  Índice remissivo

Vetor de listas de adjacência

O vetor de listas de adjacência de um digrafo tem uma lista encadeada (= linked list) para cada vértice do digrafo.  A lista do vértice v contém todos os vértices vizinhos de v.

(Esta página foi inspirada na seção 17.4 (Adjacency-Lists Representation), em particular no programa 17.6 (p.28), do livro de Sedgewick.)

Exemplo

Eis o conjunto de arcos de um digrafo e o vetor de listas de adjacência do digrafo:

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

Eis o conjunto de arestas de um grafo e o vetor de listas de adjacência do grafo:

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

Estruturas

Um digrafo é representado por uma struct digraph que contém o número de vértices, o número de arcos e o vetor de listas de adjacência do digrafo.

/* A estrutura digraph representa um digrafo. O campo adj é um ponteiro para o vetor de listas de adjacência do digrafo, o campo V contém o número de vértices e o campo A contém o número de arcos do digrafo. */

struct digraph {
   int V; 
   int A; 
   link *adj; 
};

/* Um objeto do tipo Digraph contém o endereço de um digraph. */

typedef struct digraph *Digraph;

/* A lista de adjacência de um vértice v é composta por nós do tipo node. Um link é um ponteiro para um node. Cada nó da lista contém um vizinho w de v e o endereço do nó seguinte da lista. */

typedef struct node *link;
struct node { 
   Vertex w; 
   link next; 
};

/* A função NEW recebe um vértice w e o endereço next de um nó e devolve um novo nó x com x.w = w e x.next = next. */

link NEW( Vertex w, link next) { 
   link x = malloc( sizeof *x);
   x->w = w; 
   x->next = next;     
   return x;                         
}

[No livro de Sedgewick, o nome do primeiro campo de um node é  v  e não w.  Por vezes, isso torna o código um pouco confuso.]   Uma estrutura análoga será usada para representar grafos:  basta trocar "digraph" e "Digraph" por "graph" e "Graph" e trocar "A" por "E".  As definições de node, link e NEW são as mesmas dos digrafos.

/* A estrutura graph representa um grafo. O campo adj é um ponteiro para o vetor de listas de adjacência do grafo, o campo V contém o número de vértices e o campo E contém o número de arestas do grafo. */

struct graph {
   int V; 
   int E; 
   link *adj; 
};

/* Um objeto do tipo Graph contém o endereço de um graph. */

typedef struct graph *Graph;

Essas estruturas não devem ser consideradas definitivas. Elas poderão ser modificadas e adaptadas adiante, conforme as necessidades.

Algumas funções básicas

Eis algumas funções básicas de construção e manipulação de digrafos representados por listas de adjacência:

/* Esta função devolve (o endereço de) um novo digrafo com vértices 0,..,V-1 e nenhum arco. */

Digraph DIGRAPHinit( int V) { 
   Vertex v;
   Digraph G = malloc( sizeof *G);
   G->V = V; 
   G->A = 0;
   G->adj = malloc( V * sizeof (link));
   for (v = 0; v < V; v++) 
      G->adj[v] = NULL;
   return G;
}

/* A função abaixo insere um arco v-w no digrafo G. Se v == w ou o digrafo já tem arco v-w, a função não faz nada. */

void DIGRAPHinsertA( Digraph G, Vertex v, Vertex w) { 
   link p;
   if (v == w) return;
   for (p = G->adj[v]; p != NULL; p = p->next) 
      if (p->w == w) return;
   G->adj[v] = NEW( w, G->adj[v]);
   G->A++;
}

A execução da função DIGRAPHinsertA consome muito tempo, pois percorre toda a lista de vizinhos de v.  Talvez seja melhor substituir o código pelo que está abaixo e transferir a responsabilidade de evitar "laços" e arcos "paralelos" ao cliente/usuário, que provavelmente tem condições de fazer isso de maneira eficiente.

   void DIGRAPHinsertA( Digraph G, Vertex v, Vertex w) { 
      if (v == w) return;
      G->adj[v] = NEW( w, G->adj[v]);
      G->A++;
   }

Para tratar de grafos, basta fazer as adaptações óbvias:

Graph GRAPHinit( int V) { 
   Vertex v;
   Graph G = malloc( sizeof *G);
   G->V = V; 
   G->E = 0;
   G->adj = malloc( V * sizeof (link));
   for (v = 0; v < V; v++) 
      G->adj[v] = NULL;
   return G;
}
void GRAPHinsertE( Graph G, Vertex v, Vertex w) { 
   link p;
   if (v == w) return;
   for (p = G->adj[v]; p != NULL; p = p->next) 
      if (p->w == w) return;
   G->adj[v] = NEW( w, G->adj[v]);
   G->adj[w] = NEW( v, G->adj[w]);
   G->E++;
}

Exercícios 1

A maior parte dos exercícios abaixo envolve grafos. Convido o leitor a refazer todos os exercícios depois de trocar "grafo simétrico" e "aresta" por "digrafos" e "arco" respectivamente.

  1. [Importante]  Considere o digrafo definido pelos arcos abaixo.  Faça uma figura do vetor de listas de adjacência do digrafo quando os arcos acima são inseridas por DIGRAPHinsertA, na ordem dada acima, em um digrafo inicialmente vazio.
         3-7 1-4 7-8 0-5 5-2 3-8 2-9 0-6 4-9 2-6 6-4
    
  2. [Importante[Sedgewick 17.26, p.30]  Considere o grafo definido pelas arestas abaixo.  Faça uma figura do vetor de listas de adjacência do grafo quando as arestas acima são inseridas por GRAPHinsertE, na ordem dada acima, em um grafo inicialmente vazio.
         3-7 1-4 7-8 0-5 5-2 3-8 2-9 0-6 4-9 2-6 6-4
    
  3. [Sedgewick 17.27, p.30]  Escreva uma versão da função DIGRAPHshow apropriada para listas de adjacência. (Veja a função que escrevemos para matriz de adjacência.)
  4. Escreva uma função que confira a consistência da representação de um digrafo por listas de adjacência.  Ao receber um digrafo G, a função deve devolver 1 se as listas de adjacência não contêm "laços" nem arcos "paralelos" e o valor de G->A é consistente com o vetor G->adj.  Caso contrário, a função deve devolver 0.
  5. Escreva uma função que confira a consistência da representação de um grafo por listas de adjacência.  Ao receber um grafo G, a função deve devolver 1 se o grafo é simétrico, as listas de adjacência não contêm "laços" nem arestas "paralelas", e o valor de G->E é consistente com o vetor G->adj.  Caso contrário, a função deve devolver 0.
  6. [Sedgewick 17.40, p.38]  Escreva uma função GRAPHdeg que receba um grafo representado por listas de adjacência e um vértice v e devolva o grau de v.
  7. Escreva uma função DIGRAPHoutdeg que receba um digrafo representado por listas de adjacência e um vértice v e devolva o grau de saída de v.  Escreva uma função DIGRAPHindeg que devolva o grau de entrada de um vértice v num digrafo representado por listas de adjacência.
  8. [Sedgewick 17.28, p.30]  Escreva uma função GRAPHremoveE que remova uma aresta v-w de um grafo G dado por suas listas de adjacência. (Veja DIGRAPHremoveA.) Se v-w não for aresta de G, a função não deve fazer nada.
  9. [Sedgewick 17.29, p.30]  Escreva uma função DIGRAPHcopy que receba um digrafo G, crie uma cópia de G, e devolva (o endereço d)a cópia. Suponha que G representado por um vetor de listas de adjacência.
  10. [Sedgewick 17.24, p.27]  Escreva uma função de DIGRAPHdestoy que destrua um digrafo representado por suas listas de adjacência (ou seja, libere o espaço que o digrafo ocupa na memória).
  11. [Sedgewick 17.30, p.30]  Dê um exemplo simples de um vetor de listas de adjacência de um grafo que não pode ser gerado, a partir do grafo vazio, pelo uso repetido da função GRAPHinsertE.  
  12. [Sedgewick 17.31, p.30]  A figura abaixo sugere o vetor de listas de adjacência de um grafo.  De quantas maneiras diferentes essas listas poderiam ser reescritas sem deixar de representar o mesmo grafo?
          0: 6 5 1 2
          1: 0
          2: 0
          3: 5 4
          4: 6 5 3
          5: 3 0 4
          6: 0 4
          7: 8
          8: 7
          9: 12 11 10
         10: 9
         11: 12 9
         12: 9 11
    
  13. [Sedgewick Prog 19.1, p.146]  Escreva uma função que receba um grafo representado por suas listas de adjacência e inverta todas essas listas.  Por exemplo, suponha que os 4 vizinhos de um certo vértice u aparecem na lista adj[u] na ordem v, w, x, y.  Depois da aplicação da função, a lista deve conter os mesmos vértices na ordem y, x, w, v.

Exercícios 2

A maior parte dos exercícios abaixo envolve grafos. Convido o leitor a refazer todos os exercícios depois de trocar "grafo" e "aresta" por "digrafo" e "arco" respectivamente.

  1. Escreva uma função que receba um grafo representado por matriz de adjacência e construa a correspondente representação por listas de adjacência.
  2. Escreva uma função que receba um grafo representado por listas de adjacência e construa a correspondente representação por matriz de adjacência.
  3. Considere o problema de decidir se dois vértices são adjacentes num grafo representado por suas listas de adjacência. Quanto tempo consome a solução do problema? Dê sua resposta em função do número de vértices.
  4. Escreva uma função que receba dois grafos, digamos G1 e G2, e decida se eles são iguais.
  5. Considere o problema de decidir se um vértice é isolado num grafo representado por suas listas de adjacência. Quanto tempo consome a solução do problema? Dê sua resposta em função do número de vértices.
  6. [Sedgewick 17.25, p.27]  Uma clique num grafo é um conjunto de vértices dois-a-dois adjacentes.  Uma panela é uma clique formada por vértices "consecutivos", ou seja, uma clique da forma {i+1,i+2,..,i+k}.  Escreva uma função que encontre uma panela máxima.  Faça duas versões de sua função: uma para matriz de adjacência e outra para listas de adjacência. Sua função deve consumir tempo proporcional a no máximo.

Construção de grafos e digrafos

A maior parte dos exercícios abaixo envolve grafos. Convido o leitor a refazer todos os exercícios depois de trocar "grafo simétrico" e "aresta" por "digrafos" e "arco" respectivamente.

  1. [Entrada de dados]  Digamos que um arquivo é gráfico se sua primeira linha contém um inteiro V e cada uma das demais linha contém dois inteiros pertencentes ao intervalo [0..V-1] separados por um caractere '-'.  Eis um exemplo:
         7
         0-1 
         0-5 
         1-0 
         1-5 
         2-4 
         3-1
    

    (É claro que o último caractere do arquivo é um \n.)  Se interpretarmos cada linha do arquivo como uma aresta, podemos dizer que o arquivo define um grafo com vértices 0..V-1.  Escreva uma função GRAPHconstruct que receba um arquivo gráfico e construa aas listas de adjacência do grafo. Use as funções GRAPHinit e GRAPHinsertE

  2. [Entrada de dados]  Digamos que um arquivo é gráfico se sua primeira linha contém um inteiro V e cada uma das V linhas subsequentes contém um inteiro seguido de zero ou mais inteiros (todos os números da linha estão entre 0V-1).  Eis um exemplo:
         7
         0   1 5 1
         1   0 5 3
         2   4
         3   1
         4   2
         5   0 1
         6
    

    (É claro que o último caractere do arquivo é um \n.)  Um arquivo gráfico representa um grafo com vértices 0..V-1. As últimas V linhas do arquivo definem as listas adjacência do grafo.  Escreva uma função GRAPHconstruct que receba um arquivo gráfico e construa as listas de adjacência do grafo. Use as funções GRAPHinit e GRAPHinsertE.

  3. Uma grade m-por-n é um grafo com m×n vértices distribuídos em m linhas e n colunas com arestas ligando vértices "vizinhos" na horizontal e na vertical da maneira óbvia.  A figura abaixo sugere uma grade 3-por-4.
         o--o--o--o
         |  |  |  |
         o--o--o--o
         |  |  |  |
         o--o--o--o
    

    Escreva uma função que construa uma grade m×n. Faça duas versões: uma gera uma representação por matriz de adjacência e outra gera uma representação por listas de adjacência.

  4. Dado um grafo G, o grafo L(G) é definido assim:  cada vértice de L(G) representa uma aresta de G e dois vértices de L(G) são adjacentes se as correspondentes arestas de G têm uma ponta em comum.  O grafo L(G) é conhecido como grafo das arestas (= line graph) de G.  Escreva uma função que receba um grafo G e construa o grafo das arestas de G.

Perguntas e respostas

Valid HTML 4.01 Strict Valid CSS!