MAC0328 Algoritmos em Grafos

Quarto Exercício-Programa. Passeios Mínimos

"Highways, telephone lines, electric power systems, computer chips,
water delivery systems, and rail lines: these physical networks, and
many others, are familiar to all of us. In each of these problem settings,
we often wish to send some good(s) (vehicles, messages, electricity, or
water) from one point to another, typically as efficiently as possible --
that is, along a shortest route or via some minimum cost flow pattern.''

- Ahuja, Magnanti, Orlin, and Reddy


Objetivos

  1. Implementar um algoritmo para o Problema dos Passeios Mínimos: Algoritmo de Bellman-Ford-Moore.
  2. Uso de busca em largura para obtenção de passeios. Veja também busca em largura versão 2001.

Descrição

Introdução

Este exercício-programa consiste na implementação do algoritmo de Bellman-Ford-Moore que recebe um grafo g com comprimentos nos arcos e um vértice r e devolve um ciclo negativo acessível a partir de r ou um passeio de comprimento mínimo de r a v, para cada vértice v do grafo. O comprimento len(a) de cada arco a de g é um número inteiro (possivelmente negativo). Se

P=<r=v0,a1,v1,a2,v3,...,vk-1,ak,vk=v>
é um passeio de um vértice r a um vértice v então seu comprimento len(P) é
len(a1) + len(a2) + ... + len(ak).
Assim, o comprimento de um passeio é a soma dos comprimentos do arcos no passeio; levando-se em conta o número de vezes que cada arco ocorre no passeio. Um passeio P é dito (de comprimento) mínimo se len(P) <= len(P') para todo passeio P' com a mesma ponta inicial e a mesma ponta final de P. A distância de um vértice r a um vértice v é o menor comprimento de um caminho de r a v.

O seu programa C (documento CWEB) deve chamar-se bellman.c (bellman.w, respectivamente). O programa deve utilizar a biblioteca do SGB para representação e recuperação um grafo, como descrito a seguir.

Nota. A descrição do algoritmo de Bellman-Ford-Moore que está abaixo foi extraída do livro:

R.E. Tarjan, Data Structures and Network Algorithms, CBMS-NSF Regional Conference Series in Applied Mathematics, Philadelphia, Pennysylvania, 1983.


Algoritmo de Bellman-Ford-Moore

Seja g um grafo com comprimentos nos arcos e r um vértice de g. Denotaremos por n e m o número de vértices e de arcos de g.

Passeios e caminhos mínimos

Nem sempre existe um passeio mínimo de um vértice r a um vértice v.

Teorema 0. Sejam r e v vértices de um grafo com comprimentos nos arcos. Suponha que v é acessível a partir de r. Vale uma, e apenas uma, das seguintes afirmação:

  1. existe um passeio mínimo de r a v; ou
  2. existe um passeio de r a v que percorre um ciclo (de comprimento) negativo.

Demonstração. Exercício.

É evidente que se v é acessível a partir de r, então existe um caminho mínimo de r a s. Entretanto, encontrar caminhos mínimos é um problema computacionamente difícil (a menos, é claro, que não tenhamos pressa em encontrar o caminho). Encontrar um caminho mínimo de r a v é tão difícil quanto encontrar um caminho hamiltoniano de r a v (um problema difícil pra cachorro).

Felizmente (ou curiosamente) existem algoritmos eficientes para encontrar-se passeios mínimos quando estes existem. Mais ainda, os passeios (mínimos) encontrados pelos algoritmos são caminhos. Uma maneira compacta de representar passeios/caminhos mínimos de um dado vértice r a todos os demais vértices de um grafo é através de uma certa árvore com raiz r.

Árvore de caminhos mínimos

Um árvore (ou arborescência) com raiz r é dita uma árvore de caminhos mínimos se todo vértice acessível a partir de r está na árvore e para cada vértice v na árvore o caminho de r a v na árvore é um passeio mínimo no grafo.

Teorema 1. Seja g um grafo com comprimentos nos arcos e r um de seus vértices. Vale uma, e apenas uma, das seguintes afirmação:

  1. g possui uma árvore de caminhos mínimos com raiz r; ou
  2. g possui um ciclo negativo acessível a partir de r.

Demonstração. Exercício. (Este teorema é uma das conseqüências da correção do algoritmo de Bellman-Ford-Moore.)

Se t é uma árvore (ou arborescência) de g com raiz r e v é um vértice de g, então definimos dist[v] como sendo o comprimento do único caminho de r a v na árvore.  Note que dist[v] não é a distância r a v no grafo, mas sim na árvore; por esta razão dist[v] é muitas vezes chamdo de distância tentativa (ou candidato a distância) de r a v no grafo. Para os vértices que não estão em t definimos dist[v] como sendo INFINITO, onde INFINITO é um número suficientemente grande. Se n é o número de vértices do grafo e L é o valor absoluto do maior comprimento de um arco então, para os nossos propósitos, podemos considerar que INFINITO = n × L + 1. Note que para cada arco uv na árvore vale que

dist[u] + len(uv) = dist[v].

O método descrito a seguir baseia-se no teorema abaixo.

Teorema 2. Uma árvore com raiz r é de caminhos mínimos se e somente se

dist[u] == INFINITO   ou    dist[v] <= dist[u] + len(uv,)   para cada arco uv de g,
onde dist[w] é o comprimento do caminho de r a w na árvore.

Demonstração. Exercício. :-)

O teorema 2 pode ser usado na implementação de um trecho de código que em tempo linear verifica se uma suposta árvore de caminhos mínimos é de fato uma árvore de caminhos mínimo Esta vérificação será usada na implementação do algoritmo de Bellman-Ford-Moore, veja a seção ``comportamento do se programa''.

EXERCÏCIO :  Escreva uma função

          int distancias_ok (Graph *g, Vertex *r){...}
que recebe um grafo  g  com comprimentos nos arcos (possivelmente negativos) e um vértice r e devolve 1 se para cada vértice v o valor v->dist é distância de r a v, em caso contrário a função devolve 0. O consumo de tempo de sua função deve ser linear.

Labeling method

O teorema 2 sugeri o seguinte método iterativa, o chamado labeling method, para tentar construir uma árvore de caminhos mínimos com raiz r.

No início de cada iteração tem-se uma árvore t com raiz r e o comprimento dist[v] do caminho de r a v na árvore, para cada vértice v do grafo. Para representar a árvore t tem-se uma função predecessor que para cada vértice v fornce o vértice pred[v] predecessor (ou pai) de v na árvore.
No início da primeira iteração pred[v] = NULL para cada vértice v, dist[r] = 0 e dist[v] = INFINITO para os demais vértices.
Itere o seguinte passo até que dist[u] + len(u,v) >= dist[v] para cada arco uv do grafo:

LABELING STEP (L.R. Ford). Selecione um arco uv  tal que  dist[v] > dist[u] + len(uv). Faça

dist[v] = dist[u] + len(u,v)   e   pred[v] = u.

O labeling method não pára caso o grafo contenha um ciclo negativo. Ademais, mesmo que o grafo não contenha ciclos negativos e o método pare, ele pode demorar muito. Nosso primeiro refinamento será evitar que arcos sejam examinados desnecessariamente.

EXERCÏCIO :  Mostre um grafo com comprimentos arbitrários e sem ciclos negativos em que o LABELING STEP consome tempo exponecial.

Labeling and scanning method

O labeling and scanning method mantém uma partição dos vértices em três estados: NÃO-VISTO, VISITADO e EXAMINADO. Os vértices no estado VISITADO ou EXAMINADO são exatamente aqueles que têm `distância tentativa' finita. Inicialmente r é VISITADO e os demais vértices são NÃOVISTOS. O método consiste em iterar o seguinte passo até que não hajam vértices com estado VISITADO.

SCANNING STEP. Selecione um vértice VISITADO u e examine-o, mudando o seu estado para EXAMINADO, através da aplicação do labeling step a cada arco uv tal que dist[u] + len(u,v) < dist[v] e convertendo o estado de v para VISITADO (não importa se o estado de v era NÃO-VISTO ou EXAMINADO).

O labeling and scanning method, a exemplo do labeling method, é ineficiente e não pára caso o grafo contenha um ciclo negativo acessível a partir de r. Entretanto, podemos transformar este método em um algoritmo eficiente através de uma escolha apropriado da ordem em que os vértices no estado VISITADO são examinados, como descrito logo a seguir.

Ordem da busca em largura

E.F. Moore e, independentemente, R.E. Bellman propuseram que os vértices com estado VISITADO fossem examinados de acordo com uma ordem de busca em largura (breadth-first order): entre os vértices com estado VISITADO examine aquele que está nesse estado a mais tempo. Para implementar a busca em largura representamos o conjunto de vértices no estado VISITADO através de uma fila. Nesta implementação surge a questão do que fazer com um vértice no estado VISITADO que é visitado novamente. Um interpretação estrita de busca em largura requer que nós movamos um tal vértice no estado VISITADO para o fim da fila. Entretanto, aplicaremos uma interpretação mais livre, deixaremos o vértice no estado VISITADO na sua posição corrente na fila. A descrição abaixo implementa esta versão do labeling and scanning method usando busca em largura. Note que o método, como descrito, ainda não pára se o grafo contém ciclos negativos.










10 
11 
12 
13 
14 
15 
16 
17 
BELLMAN-FORD-MOORE (Grafo g, Vértice r)
  para cada vértice v de g faça
      estado[v] := NÃO-VISTO
      dist[v] := INFINITO
      pred[v] := NULL
  estado[r] := VISITADO
  dist[r] := 0
  Q := INICIALIZA-FILA(r)
  enquanto Q não está vazia faça
      u := PRIMEIRO-DA-FILA(Q)
      para cada uv em Adj[u] faça
          se dist[u] + len(u,v) < dist[v] então
            estado[v] := VISITADO
            dist[v] := dist[u]+len(u,v)
            pred[v] := u
            se v não está em Q então
              INSIRA-NA-FILA(Q,v)
      estado[u] := EXAMINADO
  devolva dist e pred

Ao longo do algoritmo, Q é uma fila de vértices; essa fila é o segredo do funcionamento do algoritmo. O comando INICIALIZA-FILA(r) cria uma fila com um só elemento, o vértice r. O comando PRIMEIRO-DA-FILA(Q) devolve e remove o primeiro elemento da fila Q. O comando INSIRA-NA-FILA(Q,v) insere v no fim da fila Q.

Para analisar o desempenho deste método divide-se a sua execução em passos. O passo zero consiste do exame do vértice r. Para j > 0, o passo j consiste do exame de todos os vértices na fila ao final do passo j - 1. Cada passo pode ser executado em tempo O(n+m) pois durante cada passo cada vértice e cada arco é examinado no máximo uma vez. (Hmmm, esse O(n+m) pode ser trocado por O(m). Por quê?) O teorema a seguir fornece um limitante superior para o número total de passos.

Teorema 3. Se g é um grafo com n vértices,m arcos e sem ciclos negativos acessíveis a partir de r, então o método de BELLMAN-FORD-MOORE gasta tempo O(n+nm) e não mais do que n-1 passos são realizados.

Demonstração. Pode-se mostrar por indução em k que se existe um passeio mínimo de r a um vértice v usando k arcos, então no início do passo k a distância tentativa dist[v] é menor ou igual ao comprimento deste passeio mínimo.

Ciclos negativos

Para transforma o método de Bellman-Ford-Moore acima em um algoritmo deve-se fazer com que ele pare mesmo na presença de ciclos negativos. Uma maneira fácil de fazer isto é contar o número de passos executados. Inclua duas variáveis ao método, um inteiro passo e uma váriavel último indicando o último vértice que foi examinado durante o presente passo do método. Inicialmente passo = 0 e último = r. Depois que o vértice último é examinado incrementa-se de 1 a variável passo e último passa a ser o último vértice da fila Q. Se passo recebe o valor n com a fila Q não vazia, o (agora) algoritmo deve parar e anunciar a presença de um ciclo negativo no grafo. Com este contador de passos o algoritmo gasta tempo O(n + nm) não importando se o grafo tem ou não um ciclo negativo. Usando o lema a seguir pode-se localizar um ciclo negativo, caso um tal ciclo exista. Se v é um vértice, então

pred1[v] = pred[v]
pred2[v] = pred[pred[v]]
pred3[v] = pred[pred[pred[v]]]
[...]
e assim por diante.

Lema 4. Se a fila Q não está vazia no final do passo n-1 então predk[v] = v para algum vértice v e inteiro k e o correspondente ciclo em g é negativo.

Demonstração. Suponha que o algoritmo é executado até que um vértice w seja examinado no passo n. Defina passo[v] de um vértice v como sendo o máximo j tal que v é examinado durante o passo j. Se passo[v] é definido e positivo então pred[v] e passo[pred[v]] são definidos e passo[pred[v]] >= passo[v] - 1. Temos que passo[w]=n. Seguindo os apontadores pred a partir de w nos devemos, em algum momento, repetir algum vértice, pois existem somente n vértices e o passo de cada vértice decresce de no máximo um em cada passo.

Método de Bellman-Ford-Moore e programação dinâmica

Uma descrição alternativa do método de Bellman-Ford-Moore, mais próxima de programação dinâmica, é a que passamos a apresentar.

Defina para cada vértice v o valor dk(v) como sendo o menor comprimento de um passeio de r a v que possui no máximo k arcos, faça dk(v) = INFINITO se não existe um tal passeio.

Claramente, se não existe um ciclo negativo acessível a partir de r, a distância de r a v é igual a dn(v) (na verdade, dn-1(v)).

Algoritmicamente, a função d0 é fácil de ser computada: d0(r) = 0 e d0(v) = INFINITO se v é um vértice distinto de r. Em seguida, d1, d2, d3,... podem ser computadas sucessivamente através da seguinte regra:

dk+1(v) = min{dk(v), min(dk(u)+len(uv): uv arco)}
para cada vértice v.

Este método fornece a distância de r a v, para cada vértice v. Como vimos anteriormente não é difícil adaptar o médoto para construir uma árvore de caminhos mínimo com raiz r; se o grafo não possui ciclo negativo é claro.

Teorema 5. Dados um grafo com comprimentos nos arcos e um vértice r tais que todo ciclo acessível a partir de r tem comprimento não-negativo, uma árvore de caminhos mínimos com raiz em r pode ser encontrada em tempo O(n2+nm).

Demonstração. Existem no máximo n iterações e cada uma consome tempo O(n+m).

Um ciclo negativo pode ser determinado de maneira semelhante.

Teorema 6. Dados um grafo com comprimentos nos arcos e um vértice r, um ciclo negativo acessível a partir de r pode ser encontrado em tempo O(n2+nm).

Demonstração. Se dn != dn-1, então dn(v) < dn-1(v) para algum vértice v. Logo, o algoritmo encontra um passei P de r a v de comprimento dn(v), que atravessa n arcos. Como P atravessa n arcos, então P contém um ciclo C. Removendo C de P obtém-se um passeio P' de r a v com menos do que n arcos. Assim,

len(P') >= dn-1(v) > dn(v) = len(P) = len(P') + len(C)
e portanto len(C) < 0.
Se dn = dn-1, então não existe ciclo negativo acessível a partir de r.

Tendo em vista o algoritmo de programação dinâmica dsecrito acima pode-se facilmente implementar uma função

void bellman-ford-moore (Graph *g, Vertex *r) {...}
que recebe um grafo g com comprimentos arbitrários nos arcos e um vértice r e se o grafo não tem um ciclo negativo acessível a partir de r, então devolve um caminho de comprimento mínimo de r a v para cada vértice v do grafo. Eis a função
void
bellman-ford-moore (Graph *g, Vertex *r)
{
  Vertex *u0 = g->vertices;
  Vertex *un = g->vertices + g->n;
  long passo;

  for (u = u0; u < un; u++) 
    {
      u->dist = infinito;
      u->pred = NULL;
    }

  r->dist = 0;
  for (passo = 0; passo < n; passo++)
    {
      for (u = u0; u < un; u++)
        {
          if (u->dist != infinito) 
           {
             Arc *a;
            
             for (a = u->arcs; a; a = a->next)
               {
                 Vertex *v = a=>tip;
                 long d = u->dist + a->len;

                 if (v->dist > d) 
                   {
                     v->dist = d;
                     v->pred = u;
                   }
               }
           }
        }
    }
}

Este trecho de código exibe a simplicidade do método. O consumo de tempo da função bellman-ford-moore é O(n2 + nm). Do ponto de vista de consumo de tempo assintótico de pior caso, esta função é tão eficiente quanto a que será implementada neste exercício-programa. Entretanto, a implementação sugerida para este exercício-programa é mais eficiente na prática.

EXERCÏCIO (Análise experimental) :  Compare o consumo de tempo do seu exercício-programa com o consumo de tempo da função acima.

Comportamento do seu programa

Neste exercício-programa você implementará o Algoritmo de Bellman-Ford-Moore para encontrar passeios mínimos em grafos com arcos de comprimentos arbitrários (possivelmente negativos). Você pode escrever o programa em C cru ou em CWEB.  Documente corretamente todas as funções do programa.

O programa deve receber como entrada um grafo, um vértices origem e um vértice destino e deve fornecer como saída um passeio mínimo do vértice origem ao vértice destino.

O nome do seu programa deverá ser bellman e poderá ser chamado das seguintes formas (você pode implementar mais funcionalidades de desejar):

bellman

Como saída o programa deverá imprimir uma mensagem explicando o seu funcionamento.

bellman -n "nome do vértice origem" "nome do vértice destino" nome_do_grafo[.gb]

O seu programa deve determinar o passeio mínimo no grafo nome_do_grafo do vértice de nome "nome do vértice origem" ao vértice de nome "nome do vértice destino". As aspas são importantes para a determinação correta dos nomes dos vértices.

bellman número_vertice_origem número_vertice_destino nome_do_grafo[.gb]

O seu programa deve determinar o passeio mínimo no grafo nome_do_grafo do vértice de número número_vertice_origem ao vértice de número número_vertice_destino. Vamos convencionar que o primeiro vértice do grafo tem número 0.

bellman -t ...

Os "..." representam uma das duas opções anteriores. Se a opção -t (todos) é usada, o seu programa deve determinar um passeio mínimo no grafo nome_do_grafo.gb do vértice de nome vértice origem a cada um dos demais vértices. Neste caso, o nome ou número de vértice destino são superfluos.

bellman -v ...

Os "..." representam uma das três opções anteriores. Quando o usuário usar a opção -v (verbose) forneça também os comprimentos dos passeios que foram encontrados antes do passeio mínimo desejado. Se você desejar forneça, além dos comprimentos dos passeios mínimos e os vértices de suas extremidades, as seqüências completas de vértices que definem o passeio.
[Creio que isto deve ajudar na fase de implemetação.]

O seu programa deve prever pelo menos os seguintes casos, informando o fato ao usuário com uma mensagem conveniente:

  1. o programa é chamado com parâmetros incorretos;
  2. o grafo nome_do_grafo.gb não é encontrado;
  3. algum dos vértices não é encontrado; e
  4. não existe passeio do vértice origem ao vértice destino;
  5. Se o grafo contém um ciclo negativo acessível a partir do vértice origem, então o seu programa deverá exibir um ciclo negativo.

Não deixe de incluir algumas funções de auto-teste em seu programa: Se o usuário digitar a opção "-teste", essas funções farão testes em vários pontos do programa. Por exemplo, verificarão se um suposto caminho é de fato um caminho, se uma suposta árvore de caminhos mínimos é de fato uma árvore de caminhos mínimo, etc Se a opção "-teste" estiver ativa, é uma boa idéia exibir a seqüência de vértices sendo examinados.

Sugestões são bem-vindas!

Saída do seu programa

A saída do seu programa deve ser enviada para a saída padrão (stdout).

Os comprimentos dos passeios mínimos encontrados devem ser fornecido, bem como o passeio do vértice origem ao(s) vértice(s) destino(s). Se o programa é chamado com os nomes dos vértices, os passeios mínimos fornecidos na saída devem listar os nomes dos vértices. Caso o programa seja chamado usando os números dos vértices, os passeios fornecidos devem listar os números dos vértices.


Detalhes de implementação

Você pode implementar o algoritmo de Bellman-Ford-Moore como uma função em seu programa, de forma a permitir que, no futuro, a mesma função possa ser usada inalterada por outros programas que você escrever, nesta ou em outras disciplinas.

O algoritmo precisa de uma estrutura de dados auxiliar que permita as operações INICIALIZA-FILA, PRIMEIRO-DA-FILA, INSIRA-NA-FILA, e REMOVA-DA-FILA. A fila pode ser implementada como uma lista duplamente ligada com cabeça de vértices (estruturas do tipo Vertex). Como fizemos para o algoritmo de Dijkstra.

Agora, vamos imaginar que um dia você queira usar, ou testar, uma outra estrutura de dados para representar está fila, talvez mais rápida. Levando isso em consideração, o uso da fila em seu programa deve ser de tal forma modularizada que permita, sem necessidade de mudanças na função principal do bellman, que outra estrutura de dados substitua em seu programa as rotinas de fila que você escrever.

Se você está em busca de inspiração, então dê uma olhada no modulo GB_DIJK (gb_dijk.w) do SGB para ver uma implementação do Algoritmo Dijkstra. O módulo MILES_SPAN (miles_span.w) contém uma implementação de uma fila através de um heap.

Grafo de cidades brasileiras

O professor José Augusto fez programas para criar grafos (.gb) de cidades brasileiras. Veja os dados, grafos e os programas em http://www.ime.usp.br/~coelho/grafos/sgb/cidades/. Você pode usar este grafo de cidades brasileiras para brincar com o seu programa.

Módulo GB_MILES

O módulo GB_MILES (gb_miles.w) do SGB contém a subrotina miles que você pode usar para testar e brincar com o seu programa bellman.

Copiado do arquivo gb_miles.w:

The subroutine call

miles(n,north_weight,west_weight,pop_weight,max_distance,max_degree,seed)

constructs a graph based on the information in miles.dat. Each vertex of the graph corresponds to one of the 128 cities whose name is alphabetically greater than or equal to `Ravenna, Ohio' in the 1949 edition of Rand McNally & Company's Standard Highway Mileage Guide. Edges between vertices are assigned lengths representing distances between cities, in miles. In most cases these mileages come from the Rand McNally Guide, but several dozen entries needed to be changed drastically because they were obviously too large or too small; in such cases an educated guess was made. Furthermore, about 5% of the entries were adjusted slightly in order to ensure that all distances satisfy the ``triangle inequality'': The graph generated by miles has the property that the distance from u to v plus the distance from v to w always exceeds or equals the distance from u to w.

The constructed graph will be ``complete''---that is, it will have edges between every pair of vertices---unless special values are given to the parameters max_distance or max_degree. If max_distance!=0, edges with more than max_distance miles will not appear; if max_degree!=0, each vertex will be limited to at most max_degree of its shortest edges.

Um exemplo de uso da função miles pode ser encontrado no módulo MILES_SPAN (miles_span.w), como mostrado abaixo.

#include "gb_miles.h" /* the |miles| routine */
[...]
main(argc,argv)
  int argc; /* the number of command-line arguments */
  char *argv[]; /* an array of strings containing those arguments */
{@+unsigned long n=100; /* the desired number of vertices */
  unsigned long n_weight=0; /* the |north_weight| parameter */
  unsigned long w_weight=0; /* the |west_weight| parameter */
  unsigned long p_weight=0; /* the |pop_weight| parameter */
  unsigned long d=10; /* the |max_degree| parameter */
  long s=0; /* the random number seed */
  unsigned long r=1; /* the number of repetitions */
  char *file_name=NULL; /* external graph to be restored */
  @;
  while (r--) {
    if (file_name) g=restore_graph(file_name);
    else g=miles(n,n_weight,w_weight,p_weight,0L,d,s);
    if (g==NULL || g->n<=1) {
      fprintf(stderr,"Sorry, can't create the graph! (error code %ld)\n",
               panic_code); /* panic_code is set nonzero if graph
                             * generator panics. 
                             * panic_code indica o problema que ocorreu,
                             * veja gb_graph.w
                             */
      return -1; /* error code 0 means the graph is too small */
    }

Módulo GB_RAND

Copiado do arquivo módulo GB_RAND (gb_rand.w):

This GraphBase module provides two external subroutines called random_graph and random_bigraph, which generate graphs in which the arcs or edges have been selected ``at random.'' A third subroutine, random_lengths, randomizes the lengths of the arcs of a given graph. The performance of algorithms on such graphs can fruitfully be compared to their performance on the nonrandom graphs generated by other GraphBase routines.

[...]

The procedure

random_graph(n,m,multi,self,directed,dist_from,dist_to, min_len,max_len,seed)

is designed to produce a pseudo-random graph with n vertices and m arcs or edges, using pseudo-random numbers that depend on seed in a system-independent fashion. The remaining parameters specify a variety of options:
multi!=0 permits duplicate arcs;
self!=0 permits self-loops (arcs from a vertex to itself);
directed!=0 makes the graph directed; otherwise each arc becomes an undirected edge;
dist_from and dist_to specify probability distributions on the arcs;
min_len and max_len bound the arc lengths, which will be uniformly distributed between these limits.

If dist_from or dist_to are NULL, the probability distribution is uniform over vertices; otherwise the dist parameter points to an array of n nonnegative integers that sum to 230, specifying the respective probabilities (times 230) that each given vertex will appear as the source or destination of the random arcs.

[...]

Sugestão

Brinque com o seu programa, brinque muito. Aplique-o ao grafo de cidades brasileiras e a vários grafos do Stanford GraphBase (use as funções miles e random_graph. Durante a fase de testes, use grafos pequenos. Faça um desenho do grafo num pedaço de papel para conferir a resposta do seu programa.

Uso de WEB

Gostaria de destacar que o uso do WEB, segundo o seu criador D. Knuth, tem as seguintes finalidades de

Como conseqüência, Knuth comenta que fica mais divertido escrever programas.

Mudando de assunto, um comentário sobre comentários: ``Antes de cada função [e bloco] diga o que a função [e bloco] faz. Procure responder as perguntas que um usuário faria: que dados essa função recebe? que suposições faz sobre esses dados? que resultados devolve? Não perca tempo tentando dizer como a função faz o que ela faz.''(Paulo Feofiloff) Se o tamanho de seus blocos é razoavelmente pequeno, como deve ser, o conselho é extremamente útil.

Observação. Cópiei o comentário acima da página de grafos do prof. José Augusto


Entrega e Prazos

  1. Este exercício-programa vale 20 pontos.
  2. Como sempre, somente entregue seu programa se o mesmo não apresentar erros de compilação. O processo de compilação aqui deve ser entendido como todo o processo cweave, latex, ctangle (se você fez em CWEB) e gcc.
  3. Junto com o seu programa você deve entregar um Makefile de tal forma que
    meu_prompt> make bellman.tex
    gere o documento bellman.tex correspondente ao seu EP4,
    meu_prompt> make bellman.dvi
    produza o Make.dvi correspondente ao seu EP4,
    meu_prompt> make bellman.c
    crie o arquivo bellman.c correspondente ao seu EP4, e
    meu_prompt> make bellman
    produza o executável de nome bellman correspondente ao seu EP4.
  4. O Panda não costuma aceitar a entrega de vários arquivos. Por isto, você deve criar um arquivo ep4.tar.gz com todo o seu trabalho. Espera-se que
    meu_prompt>tar -xvf ep4.tar
    crie um diretório que tenha o seu login na rede Linux como nome. Neste diretório devem estar todos os arquivos do seu EP3, inclusive o Makefile que você usou. Se você achar necessários coloque junto um arquivo 00-leia-me, onde você descreve algo que achar necessário: como limitações e bugs no seu programa.
  5. O seu EP4s deve ser depósitado no panda até o 24:00 do dia 12 MAI 2003.

[ Página principal de algoritmos em grafos. | Exercícios-programas]
Last modified: Sun May 4 22:19:21 EST 2003