Algoritmos para Grafos | Índice
Este capítulo descreve a camada interna da implementação do algoritmo de Ford-Fulkerson. Essa camada interna consiste na função AugmPath(), que tem a missão de calcular um caminho aumentador num grafo expandido.
A versão da função AugmPath() discutida neste capítulo produz um caminho aumentador de comprimento mínimo. Edmonds e Karp mostraram (1972) que, com essa versão de AugmPath(), o consumo de tempo de GRAPHmaxFlow() não depende das capacidades dos arcos, ao contrário do que acontece com a versão genérica do algoritmo.
A versão ShrtAugmPath() da função AugmPath() calcula um caminho aumentador com o menor número possível de arcos. A função recebe um grafo capacitado G e um fluxo com vértice inicial s e vértice final t. O grafo é representado por listas de adjacência com nós expandidos. Cada nó expandido representa um arco do grafo que tem uma certa capacidade cap e carrega um certo fluxo flow que respeita a capacidade.
Alguns arcos de G são originais enquanto outros são artificiais, mas a função ignora essa distinção (em particular, ignora os campos type e twin dos nós) e toma conhecimento apenas da capacidade residual cap - flow de cada arco. Essa capacidade residual é sempre positiva, embora o fluxo nos arcos artificiais seja negativo ou nulo. A função ShrtAugmPath() limita-se a calcular um caminho de s a t que tenha capacidade residual estritamente positiva e seja o mais curto possível sob essa restrição. O caminho é armazenado no par de vetores pa[] e parc[]. (Os nomes dos vetores são abreviaturas de parent e parent arc respectivamente.)
A função ShrtAugmPath() é uma mera adaptação da função GRAPHbfs() de busca em largura. O código usa uma fila de vértices manipulada pelas funções QUEUEinit(), QUEUEempty(), QUEUEput() e QUEUEget(), já discutidas.
/* Esta é a versão ShrtAugmPath() da função AugmPath(). */
#define AugmPath ShrtAugmPath
/* Esta função recebe um grafo capacitado G e um fluxo registrado nos campos flow dos nós das listas de adjacência. Calcula um caminho de comprimento mínimo de s a t dentre os que têm capacidade residual estritamente positiva e armazena o caminho nos vetores pa[] e parc[]. Devolve a capacidade residual do caminho; se nenhum caminho de s a t tem capacidade residual estritamente positiva, devolve 0. A função supõe que todos os arcos têm capacidade menor que uma constante M. */
int ShrtAugmPath( Graph G, vertex s, vertex t, vertex pa[], link parc[]) { bool visited[1000]; for (vertex v = 0; v < G->V; ++v) visited[v] = false; QUEUEinit( G->V); visited[s] = true; QUEUEput( s); while (!QUEUEempty( )) { vertex v = QUEUEget( ); if (v == t) break; for (link a = G->adj[v]; a != NULL; a = a->next) { // a pode ser original ou artificial int residual = a->c - a->flow; if (residual > 0 && !visited[a->w]) { visited[a->w] = true; pa[a->w] = v; parc[a->w] = a; QUEUEput( a->w); } } } if (!visited[t]) return 0; int delta = M; for (vertex w = t; w != s; w = pa[w]) { link a = parc[w]; delta = min( delta, a->c - a->flow); } QUEUEfree( ); return delta; }
As últimas linhas do código calculam a capacidade residual, delta, do caminho armazenado em pa[] e parc[].
Exemplo A. Considere o seguinte grafo capacitado com vértice inicial 0 e vértice final 5. (Os arcos artificiais estão destacados em vermelho.) As capacidades estão indicadas na segunda linha. Um fluxo de intensidade 1 está indicado na terceira linha.
0-1 1-0 0-2 2-0 1-3 3-1 1-4 4-1 2-3 3-2 2-4 4-2 3-5 5-3 4-5 5-4 2 0 3 0 3 0 1 0 1 0 1 0 2 0 3 0 0 0 1 -1 0 0 0 0 0 0 1 -1 0 0 1 -1
A função ShrtAugmPath() ignora a distinção entre arcos vermelhos e pretos e produz o caminho aumentador
0-2-3-5
que tem capacidade residual 1. (Nesse exemplo, todos os arcos do caminho aumentador são originais.)
Exemplo B. Considere o grafo capacitado abaixo. A tabela mostra apenas os arcos originais e suas capacidades. Os vértices inicial e final são 0 e 13 respectivamente.
cap 0-1 10 0-2 6 0-3 9 0-4 6 1-5 7 1-6 3 1-7 3 2-3 2 2-8 3 2-9 3 3-5 7 3-8 3 3-10 3 4-1 2 4-6 3 4-11 3 5-12 6 6-11 3 6-7 5 7-13 9 8-9 3 8-10 5 9-13 6 10-13 9 11-13 6 12-7 7 12-10 7
A função GRAPHmaxFlow() combinada com ShrtAugmPath() produz a seguinte sequência de caminhos aumentadores a partir do fluxo nulo. Os arcos artificiais estão indicados em vermelho. (Mas ShrtAugmPath() ignora a distinção entre arcos artificiais e originais.) Cada caminho aumentador tem comprimento mínimo. O comprimento e a capacidade residual de cada caminho estão registrados na segunda e terceira colunas respectivamente.
caminho compr delta 0-4-11-13 3 3 0-3-10-13 3 3 0-2-9-13 3 3 0-1-7-13 3 3 0-4-6-7-13 4 3 0-3-8-10-13 4 3 0-2-8-10-13 4 2 0-2-8-9-13 4 1 0-1-6-7-13 4 2 0-1-6-11-13 4 1 0-3-5-12-10-13 5 1 0-3-5-12-7-13 5 1 0-3-5-12-10=8-9-13 7 1 0-1-5-12-10=8-9-13 7 1 0-1-5-12-7=6-11-13 7 2
Esses caminhos aumentadores produzem a seguinte sequência de fluxos
(leia na vertical, com -
representando 0):
0-1 - - - - 3 3 3 3 3 5 6 6 6 6 7 9 0-2 - - - 3 3 3 3 5 6 6 6 6 6 6 6 6 0-3 - - 3 3 3 3 6 6 6 6 6 7 8 9 9 9 0-4 - 3 3 3 3 6 6 6 6 6 6 6 6 6 6 6 1-5 - - - - - - - - - - - - - - 1 3 1-6 - - - - - - - - - 2 3 3 3 3 3 3 1-7 - - - - 3 3 3 3 3 3 3 3 3 3 3 3 2-3 - - - - - - - - - - - - - - - - 2-8 - - - - - - - 2 3 3 3 3 3 3 3 3 2-9 - - - 3 3 3 3 3 3 3 3 3 3 3 3 3 3-5 - - - - - - - - - - - 1 2 3 3 3 3-8 - - - - - - 3 3 3 3 3 3 3 3 3 3 3-10 - - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4-1 - - - - - - - - - - - - - - - - 4-6 - - - - - 3 3 3 3 3 3 3 3 3 3 3 4-11 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 5-12 - - - - - - - - - - - 1 2 3 4 6 6-11 - - - - - - - - - - 1 1 1 1 1 3 6-7 - - - - - 3 3 3 3 5 5 5 5 5 5 3 7-13 - - - - 3 6 6 6 6 8 8 8 9 9 9 9 8-9 - - - - - - - - 1 1 1 1 1 2 3 3 8-10 - - - - - - 3 5 5 5 5 5 5 4 3 3 9-13 - - - 3 3 3 3 3 4 4 4 4 4 5 6 6 10-13 - - 3 3 3 3 6 8 8 8 8 9 9 9 9 9 11-13 - 3 3 3 3 3 3 3 3 3 4 4 4 4 4 6 12-7 - - - - - - - - - - - - 1 1 1 3 12-10 - - - - - - - - - - - 1 1 2 3 3
A tabela mostra apenas os arcos originais. O fluxo final tem intensidade 30. Veja as duas margens de um corte de capacidade 30:
0 1 3 5 2 4 6 7 8 9 10 11 12 13
Portanto, o fluxo é máximo e o corte é mínimo.
0-1 1-2 2-4 0-3 3-4 3-5 5-6 6-7 4-7 3 3 3 2 1 2 2 2 3
0-1 1-2 2-4 0-3 3-4 3-5 5-6 6-7 4-7 4 4 4 2 1 3 3 3 3
O consumo de tempo da função GRAPHmaxFlow() é proporcional ao número de caminhos aumentadores necessário para atingir o fluxo máximo. Quantos caminhos a função ShrtAugmPath() calcula, no pior caso?
Número de caminhos aumentadores: O número de caminhos aumentadores usados pela combinação de GRAPHmaxFlow() com ShrtAugmPath() nunca é maior que VA/2, sendo V o número de vértices e A o número de arcos originais.
A prova dessa propriedade depende do conceito de arco crítico. Um arco de um caminho aumentador é crítico se tiver capacidade residual mínima no caminho. Ou seja, um arco é crítico se sua capacidade residual é igual à capacidade residual, delta, do caminho. Depois da operação de envio de delta unidades de fluxo ao longo do caminho aumentador, a capacidade dos arcos críticos é reduzida a zero e portanto o arco não poderá ser usado pelo caminho aumentador seguinte. (Mas poderá vir a ser usado por caminhos aumentadores subsequentes se sua capacidade residual aumentar nesse meio tempo.)
A prova da propriedade segue de duas observações:
Essas duas observações não são óbvias; sua prova exige algum esforço e será omitida.
Como todo caminho aumentador tem comprimento menor que V, cada arco do grafo pode ser crítico em não mais que V/2 caminhos aumentadores. Portanto, o número total de caminhos aumentadores é menor que VA/2, como queríamos provar. (Em geral, o número de caminhos aumentadores é muito menor que VA/2.)
Segue daí que o desempenho da combinação da função GRAPHmaxFlow() com a função ShrtAugmPath() é (fortemente) polinomial.