Este exercício-programa consiste na implementação do método dos caminhos de aumento que recebe um grafo com capacidades nos arcos e dois vértices r e s e devolve um fluxo de r a s de intensidade máxima e um separador de capacidade mínima.
O seu programa C (documento CWEB) deve chamar-se fluxo.c (fluxo.w, repectivamente). O programa deve utilizar a biblioteca do SGB para a manipulação de grafos.
Nota. A descrição do método de dos caminhos de aumento é baseada no livro:
R.E. Tarjan, Data Structures and Network Algorithms, CBMS-NSF Regional Conference Series in Applied Mathematics, Philadelphia, Pennysylvania, 1983.
A capacidade a->cap de um arco a de um grafo é um número inteiro não-negativo.
Sejam g um grafo com capacidades nos arcos e r e s vértices de g. Uma atribuição de um número inteiro não-negativo a->flx a cada arco a de g é um fluxo de r a s se
Um grafo com capacidades nos arcos junto com dois vértices especiais é muitas vezes chamado de uma rede (network). Em outras palavras, uma rede é um quarteto (g, cap, r, s).
A intensidade ou valor de um fluxo flx de r a s é o número
val(flx) == r->flxs - r->flxe.Em geral, mas nem sempre, r->flxe == 0. A figura abaixo mostra um fluxo de valor 5.
![]() |
PROBLEMA (do fluxo máximo): Dado um grafo com capacidades nos arcos e vértices r e s, encontrar um fluxo de r a s de intensidade máxima.
Um separador de rs é um conjunto X de vértices tal que r está em X e s está fora de X. A capacidade de um conjunto X de vértices é o número
cap(X)que é soma das capacidades dos arcos que saem de X. [O conjunto dos arcos que saem de X é dito corte determinado por X.] Uma observação fácil de verificar é o lema a seguir.
Lema 1 (da dualidade): Se flx é um fluxo de r a s e X é um separador de rs, então
val(flx) <= cap(X).Ademais, se val(flx) == cap(X), então flx é um fluxo de intensidade máxima e X é um separador de capacidade mínima.
Demonstração. Pela definição de valor de um fluxo tem-se que
val(flx) == flxs(r) - flxe(r) == flxs(r) - flxe(r) + flxs(X-{r}) - flxe(X-{r}) == flxs(X) - flxe(X) <= flxs(X) <= cap(X).A primeira igualdade segue da lei de conservação do fluxo.
A solução do problema do fluxo máximo mostrará que a recíproca da segunda parte do lema acima também vale. O algoritmo que resolve o problema do fluxo máximo tem a seguinte especificação: ao receber vértices r e s, o algoritmo devolve
um fluxo flx e um separador X de rs tais que val(flx) == cap(X).
Seja g um grafo com capacidade nos arcos e flx um fluxo de um vértice r a um vértice s. Associamos a g e flx um grafo h com capacidade nos arcos. O grafo h é definido da seguinte maneira:
a->cap - a->flxse a é arco de g ou
a->irmão->flxse a->irmão é um arco de g.
O grafo h é dito grafo residual para g e flx.
Suponha que h é o grafo residual para g e flx. A capacidade residual res(P) de um caminho P em h é a menor capacidade de um arco em P.
Um caminho em h com ponta inicial em r e de capacidade
residual positiva é dito um caminho alternante.
Um caminho de aumento (augmenting path) é um caminho alternante com ponta final em s.
![]() |
Lema 2 : Seja flx é um fluxo de r a s e P é um caminho de aumento. Para cada arco a de g defina:
a->flx' := a->flx + res(P), se a é um arco em P;A atribuição do valor a->flx' a cada arco a de g é um fluxo de r a s de intensidade val(flx) + res(P).
a->flx' := a->flx - res(P), se a->irmão é um arco em P; e
a->flx' := a->flx, em caso contrário;
Demonstração. Exercício.
. (Veja Figura 3.)
![]() |
Lema 3 : Se flx é um fluxo de r a s e não existe um caminho de aumento, então existe um separador X de rs tal que
val(flx) == cap(X).
Demonstração. Seja X o conjunto do vértices que são terminos de caminhos alternantes. Como não existe caminho de aumento, r está em X e s está fora de X. Assim, X é um separador de rs. Ademais, da definição de X segue que
a->flx == 0 (1)para cada arco a que entra em X e
a->flx == a->cap (2)para cada arco a que sai de X. Portanto,
val(flx) == flxs(r) - flxe(r) == flxs(r) - flxe(r) + flxs(X-{r}) - flxe(X-{r}) == flxs(X) - flxe(X) == flxs(X) == cap(X).A quarta igualdade é devida a (1) e a quinta igualdade é devida a (2).
O fato central desta parte da disciplina é o seguinte teorema devido a Ford e Fulkerson [7], Elias, Feinstein e Shannon [6] e Kotzig [?]. (Nesta penúltima referência os autores afirmam que o resultado era conhecido na área de communication theory.)
Teorema 1 (do fluxo máximo e corte mínimo): A maior intensidade de um fluxo de r a s é igual a menor capacidade de um separador de rs.
Demonstração. O teorema é conseqüência dos lemas 1, 2 e 3.
Em um artigo de 1o. de janeiro de 1955, Dantzig e Fulkerson [1955] mostraram que o Teorema do Fluxo Máximo e Corte Mínimo também pode ser deduzido a partir do teorema de dualidade de programação linear e em um artigo de 1o. de abril de 1955 Fulkerson e Dantzig [1955] propuseram um método computacional simples para o problema do fluxo máximo baseado no método simplex.
PASSO DE AUMENTO (AUGMENTING STEP): Encontre um caminho de aumento P para o fluxo corrente. Aumente o valor do fluxo fornecendo res(P) unidades de fluxo `através' do caminho P.Dado um fluxo máximo flx um corte mínimo pode ser encontrado em tempo O(n+m) fazendo-se busca no grafo residual para g e flx.
Suponha que a capacidade dos arcos é inteira. Então o método dos caminhos aumentantes aumenta o valor do fluxo de pelo menos 1 em cada iteração. Logo o método encontra um fluxo máximo depois de no máximo min{cap(X) : X é um separador de rs} passos aumentantes. Além disso o fluxo máximo devolvido pelo algoritmo será inteiro. Dizemos que um fluxo flx é inteiro se a->flx é um número inteiro para cada arco a.
Teorema 2 (da integralidade): Se a->cap é um número inteiro para cada arco a, então existe um fluxo máximo inteiro. Ademais, o método dos caminhos de aumento devolve um fluxo máximo inteiro.
O teorema da integralidade também é conseqüência do Método Simplex de Dantzig [2].
Infelizmente se as capacidades forem inteiros muito grandes o método dos caminhos aumentantes pode fazer muitos passos aumentantes. O número de passos aumentantes do método depende de
C := max{a->cap : a é um arco}Portanto para capacidades inteiras o método é um algoritmo pseudo-polinomial. (Para ser polinomial o número de passos aumentantes necessita ser um polinômio em m, n e log(C).)
![]() |
É desejável um algoritmo onde o número de passos aumentantes seja uma função polinomial de n e m e não dependa de C (strongly polynomial-time algorithm). Outro método sugerido por Edmonds e Karp [5] é sempre escolher um caminho aumentante com o menor número possível de arcos. Esse método é mais eficiente se aumentarmos o fluxo através de caminhos de mesmo comprimento simultaneamente (cf. Dinits [4]).
Teorema 3 : Se os passos de aumento são feitos através de caminhos de com um número mínimo de arcos, então o método dos caminhos de aumento pára depois de (n-1)m passos de aumento e encontra um fluxo máximo em tempo O(nm2).
Observação. Se as capacidades dos arcos forem irracionais o método pode não parar. Pior que isso, pode ocorrer que a seqüência das intensidades do fluxos obtidos convirga para um valor bem diferente do valor do fluxo máximo! Note que essa seqüência certamente converge já que ela é estritamente crescente e limita; a capacidade do corte mínimo é um limitante superior. Um exemplo maldoso de onde isso ocorre pode ser encontrado em Papadimitriou e Steiglitz [10] páginas 124-128.
Neste exercício-programa você implementará o método dos caminhos aumentantes de Ford e Fulkerson [7] para encontrar um fluxo máximo e um corte mínimo em uma rede dada. Os caminhos de aumento devem ser obtidos usando-se a sugestão de Edmonds e Karp [5], isto é, encontre caminhos de aumento com o menor número possível de arcos.
O programa deve receber como entrada, na linha de comando, o nome de um arquivo .gb contendo a representação de um grafo com capacidades nos seus arcos, e os nomes de dois vértices distintos origem e destino e deve fornecer como saída o valor do fluxo máximo do vértice origem ao vértice destino. A saída do seu programa deve ser enviada para a saída padrão (stdout).
meu_prompt> fluxo "Sorocaba, SP" "Sao Paulo, SP" cidades.gb
Exemplo:
meu_prompt> fluxo -v "Sorocaba, SP" "Sao Paulo, SP" cidades.gb
O seu programa deve prever pelo menos os seguintes casos, informando o fato ao usuário com uma mensagem conveniente:
Para encontrar os caminhos de aumento, ou seja, caminhos de r a s no grafo residual para o fluxo corrente, como sugerido por Edmonds e Karp, utilize busca em largura.
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 deverá ser representada como uma lista linear (vetor) de apontadores para Vertex.
O campo len de cada arco a guardará a sua capacidade a->cap ou a sua capacidade residual a->res se o arco a não for um arco do grafo original g, mas apenas de grafo residual h.
#define cap lenAssim,
Assim que o vértice s for alcançado por um caminho alternante P, o seu programa deverá ser capaz de determinar todos os arcos nesse caminho para que o fluxo nesses arcos e nos arcos do grafo original g, sejam alterados comvenientemente de res(P) (capacidade residual do caminho) unidades de fluxo. Para isto sugiro que cada vértices tenha os seguintes campos.
#define pred z.A
v->pred é um apontador para o arco a da árvore de busca em largura tal que a->tip == v.
#define cap_res y.I
v->cap_res é a menor capacidade residual de uma arco no caminho de r a v na árvore de busca em largura. Isto significa que quando um caminho de aumento P é encontrado vale que
res(P) == s->cap_res.
Podemos representar ambos, o grafo dado g e o grafo residual h para g e flx, em um mesmo grafo no SGB.
Para cada arco do grafo original crie um arco reverso e chame estes dois arcos de irmãos. Assim, se a é um arco de u a v então a->irmão é um arco de v a u. Exatamente um dentre a e a->irmão é arco do grafo g e ambos são arcos no grafo residual h. (que também pode ser arco do grafo residual, dependendo se cap - fluxo é ou não maior que zero) e o outro é um arco apenas do grafo residual (com capacidade residual possívelmente nula).
Para cada arco nós necessitamos de:
Sugiro que façamos o seguinte (estou apenas sugerindo...).
#define flx a.Ia->fluxo guarda o fluxo corrente no arco a, caso a seja um arco do grafo g. Se a for apenas um arco do grafo h, então a->flx guarda, digamos, -1.
#define irmão b.Aa->irmão aponta para o arco irmão do arco a. Portanto, a->irmão->irmão é um apontador para o próprio arco a.
#define início irmão->tipa->início aponta para o vértice de onde o arco a está saindo. Portanto, a->início e a->tip são as pontas do arco a.
Pergunta. Como saber se um arco a é um arco do grafo g?
Resposta. Se a->flx é maior ou igual a zero, então a é um arco do grafo g.
Pergunta. Como saber a capacidade a->res de um arco a do grafo residual h?
Resposta. O valor de a->res é mantido implicitamente. Existem duas possibilidades:
a->res == a->cap - a->flxAssim, a->res indica a quantidade de fluxo que ainda pode ser enviada através de a.
a->res == a->irmão->flx
Você deve considerar as respostas às perguntas acima para adaptar a busca em largura que será feita no grafo h. Bem, acho que por enquanto é só.
Copiado do arquivo gb_miles.w:
The subroutine call
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 */ }
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
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.
[...]
Gostaria de destacar que o uso do WEB, segundo o seu criador D. Knuth, tem as seguintes finalidades de
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
cweave, latex,
ctangle
(se você fez em CWEB) e gcc
.
meu_prompt> make fluxo.texgere o documento fluxo.tex correspondente ao seu EP6,
meu_prompt> make fluxo.dviproduza o fluxo.dvi correspondente ao seu EP6,
meu_prompt> make fluxo.ccrie o arquivo fluxo.c correspondente ao seu EP6, e
meu_prompt> make fluxoproduza o executável de nome fluxo correspondente ao seu EP6.
meu_prompt>tar -xvf ep6.tarcrie um diretório que tenha o seu login na rede Linux como nome. Neste diretório devem estar todos os arquivos do seu EP6, 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.