Este documento discute o problema de fluxo a custo mínimo em grafos. Ele resume o problema, explica como identificar se uma solução é viável, e descreve o algoritmo Successive Shortest Path (SSP) para resolver o problema em até 3 frases:
O documento discute o problema de fluxo a custo mínimo em grafos, explicando como transformar o grafo para garantir que o fluxo de entrada é igual ao de saída e como usar o algoritmo SSP para encontrar sucessivamente os caminhos de menor custo entre os vértices origem e destino até que não h
1. Pontifícia Universidade Católica do Rio de Janeiro
Departamento de Informática
Projeto e Análise de Algoritmos
Fluxo a Custo Mínimo
2008/02
Andrea Weberling Carvalho
Ernesto Augusto Thorp de Almeida
Gustavo Souza Queiroz
Paulo da Silva Silveira
Rafael Silva Pereira
1. Introdução
O problema de Fluxo a Custo Mínimo consiste em descobrir os caminhos de menor custo
para passar um determinado fluxo em uma rede (grafo). Este problema pode ser
determinado da seguinte forma:
Seja G = (V,E,u,c,b) um grafo direcional definido por um conjunto de
vértices V (nós) e um conjunto de arestas (arcos) E. Para cada aresta
temos associada uma capacidade uij que denota o fluxo máximo
suportado pela aresta. Além disso, cada uma possui um custo associado cij
que denota o custo por unidade de fluxo que trafega na aresta. Finalmente,
para cada nó podemos ter um fluxo entrante ou sainte bi. Considerando xij,
como sendo o fluxo que passa por uma aresta , o problema de fluxo
a custo mínimo tem como objetivo minimzar:
2. Para determinar os caminhos possíveis, se é que eles existem, temos ainda que seguir a
seguinte condição:
Basicamente, a condição acima nos diz que a quantidade de fluxo que entra em um nó
deve ser exatamente igual a quantidade de fluxo que sai deste nó, considerando o fluxo
bi .
Encontrar uma solução para este problema, ou seja, uma solução ótima, que garanta o
custo mínimo, significa a resolução de um outro problema: se existe, ou não, uma
solução viável para a passagem de fluxo.
2. Identificando se o problema possui ou não uma solução
A maneira mais básica de identificar se um problema de fluxo a custo mínimo tem ou
não solução é avaliar o fluxo entrante e sainte. Baseado no conceito de que a quantidade
de fluxo que entra em um nó deve ser igual a quantidade de fluxo que saí, temos que
caso:
então o problema não tem solução. Esta situação poderia ser solucionada se
acrescentarmos um nó especial r com o fluxo entrante ou sainte de valor , de
acordo com cada caso. Se , então, para cada nó com b > 0, adicionamos um
arco com capacidade infinita e custo zero. Caso contrário, adicionamos um arco
com as mesmas propriedades. Esta adaptação pode ser bastante útil em casos reais,
sendo que devemos analisar individualmente as situações onde podemos ou não utilizá-
la.
Entretanto, mesmo em grafos onde não podemos garantir que existe uma solução
viável. Uma maneira de descobrir se o problema possui uma solução viável consiste em
transformá-lo em um problema de fluxo máximo, adicionando dois nós à rede, da
seguinte forma:
Para cada nó com bi > 0, vamos adicionar uma aresta com a
capacidade bi e custo 0. Para cada nó com bi < 0, nós adicionamos
uma aresta com capacidade -bi e custo 0. Ignorando os custos,
devemos então solucionar o problema de Fluxo Máximo de s para t. Caso
todas as arestas criadas tenham seu fluxo saturado, então o problema
possui uma solução viável, uma vez que a conservação entre fluxo entrante
e sainte é garantida.
3. Identificado o fluxo máximo do grafo, podemos eliminar os nós criados, sendo que agora
devemos determinar se a solução viável é ou não uma solução ótima, que minimiza o
custo de tráfego de fluxo na rede.
Além destes pontos, devemos levar em conta algumas premissas básicas:
• Todos os parâmetros (capacidade, custo e direção do fluxo), devem ser números
inteiros. Caso seja necessário, teremos que converter números racionais em
inteiros, como utilizamos o computador isso não é nada restritivo.
• O grafo deve ser orientado, caso não seja teremos que transformá-lo.
• Todos os custos associados as arestas devem ser não negativos. Caso tenhamos
algum ciclo negativo no grafo será preciso retirá-lo, pois somente assim
poderemos calcular caminhos mais curtos.
3. Conceitos relacionados ao Fluxo a Custo Mínimo em Grafos
Existem diversos algoritmos que nos permitem obter o fluxo a custo mínimo em grafos,
entre eles o Successive Shortest Path (SSP) e o ACM, que são o foco de nosso estudo.
Porém, antes de detalharmos os algoritmos, vejamos alguns conceitos importante
utilizados por eles.
3.1 Redes Residuais
O conceito de rede residual definido pelo Cormen na teoria de fluxo máximo, pode ser
revista para fluxo mínimo. Suponha que uma aresta (i, j) em E transporta xij unidades
de fluxo.
A capacidade residual da aresta (i, j), é definida como r = uij – xij (capacidade – fluxo),
com isso podemos definir que ainda poderemos enviar um adicional rij unidades de fluxo
do vértice i ao vértice j.
Também podemos cancelar o fluxo existente xij sobre a aresta, se nós enviarmos o fluxo
xij, a partir de j até i (sentido contrário), sobre a aresta (i, j).
Com isso, podemos observar que em uma mesma aresta, o fluxo no sentido i para j irá
aumentar o custo, enquanto que no sentido contrario (j para i), iremos diminuir o custo
de fluxo.
4. A construção da rede residual para fluxo a custo mínimo em grafos se dá da seguinte
forma:
• Em um grafo G=(V,E), teremos a rede residual definida por Gx=(V,Ex), onde Ex
é o conjunto de arestas com capacidade residual positiva, definida por:
• Cada aresta (i,j) em E, é substituído por duas arestas (i,j) e (j,i).
• A aresta (i,j) tem custo cij e capacidade residual rij=uij – xij;
• A aresta (j,i) tem custo –cij e capacidade residual rji = xij
A construção da rede residual para problemas de grafo com custo mínimo, pode
apresentar duas particularidades:
1. O grafo contém arestas nos dois sentidos, com isso teríamos quatro arestas na
rede residual; Podemos resolver isso utilizando lista de adjacências que tratam
de arcos paralelos, ou ate mesmo duas matrizes de adjacência caso seja
conveniente.
2. O grafo onde temos mais de uma aresta com custos diferentes e um mesmo
vértice, não podemos simplesmente somar os custos das arestas, teremos que
manter estruturas de dados diferentes para cara aresta.
3.2 Ciclos Negativos em Grafos
Teremos que identificar e remover os ciclos negativos do grafo, para isso podemos
utilizar o algoritmo de Bellman-Ford com uma pequena alteração que nos permita
realizar o backtracking, identificando as arestas que fazem parte do ciclo negativo. Para
isto, basta armazenar o nó predecessor responsável por atualizar a distância para um
outro nó.
procedure BellmanFord(list vertices, list edges, vertex source)
// This implementation takes in a graph, represented as lists of vertices
// and edges, and modifies the vertices so that their distance and
// predecessor attributes store the shortest paths.
// Step 1: Initialize graph
for each vertex v in vertices:
if v is source then v.distance := 0
else v.distance := infinity
v.predecessor := null
// Step 2: relax edges repeatedly
for i from 1 to size(vertices)-1:
for each edge uv in edges: // uv is the edge from u to v
u := uv.source
v := uv.destination
if v.distance > u.distance + uv.weight:
v.distance := u.distance + uv.weight
v.predecessor := u
// Step 3: check for negative-weight cycles
for each edge uv in edges:
u := uv.source
v := uv.destination
if v.distance > u.distance + uv.weight:
5. error "Graph contains a negative-weight cycle"
backtracking to find edges of the negative cycle
4. Successive Shortest Path (SSP)
4.1 O Algoritmo e a Prova de Corretude
O algoritmo Successive Shortest Path tem como objetivo obter o fluxo máximo de custo
mínimo através da busca sucessiva por caminhos que minimizem o custo. Para isto,
devemos transformar o grafo, garantindo que o fluxo de entrada é equivalente ao fluxo
de saída:
• Temos como objetivo encontrar um fluxo ótimo em todo o grafo, com isso
podemos adicionar um novo vértice de origem “s”, e um novo vértice de saída
“t”.
• Para cada vértice i em V, com bi>0, nós adicionamos uma aresta de origem (s,i)
com capacidade bi e custo 0.
• Para cada vértice i em V, com bi<0, nós adicionamos uma aresta de saída (i,t)
com capacidade –bi e custo 0.
Então, não utilizamos a técnica de obter o fluxo máximo, e sim nós enviamos fluxo de s
para t, ao longo do caminho mais curto (em relação aos custos dos arcos). Em seguida,
atualizamos a rede residual e buscamos outro caminho mais curto que aumenta o fluxo e
assim por diante. O algoritmo termina quando o grafo da rede residual não contém
caminho de s até t.
Uma vez que o fluxo é máximo, tendo como referencia o caminho mais curto através dos
custos, teremos o algoritmo de Successive Shortest path.
O SSP pode ser usado quando o Grafo não possui ciclos de custo negativos. Se existir
um ciclo negativo o conceito de caminho mais curto não fará sentido.
Suponha que após algumas interações, tenhamos um fluxo x ao longo de um caminho P
e surge um ciclo de custo negativo W na rede residual. Isso significa que havia uma
aresta (i,j) em P ou em um trecho (i, ... , j) de P e um caminho ao contrário (j,i) ( ou (j,
..., i) ) que fechava o ciclo W, antes da interação (o ciclo negativo não é criado).
Com as iterações de Bellman-Ford para arestas e ciclos negativos, e Dijkstra para busca
de caminhos mais curtos sucessivos para o grafo modificado, precisamos garantir que o
custo não será negativo a cada interação, para isso utilizamos o procedimento “reduzir
custos”, descrito a seguir:
Reduzir_custos (πi)
1 para cada aresta (i,j) pertencente a Ex faça
2 cij := cij + πi – πj
3 crev(i,j) := 0
Para cada vértice i pertencente a V, o πi é igual ao comprimento dos menores caminhos
de s até t. Depois de reduzir o custo de cada aresta, veremos que através do caminho
mais curto de s até t, para i arestas teremos custo zero, enquanto que para as arestas
que encontram-se fora de qualquer caminho mais curto, para qualquer vértice, teremos
6. um custo positivo.
Na linha 3 do procedimento “Reduzir_custos” atribuímos a aresta reversa, o valor 0.
Considerando que o grafo pode conter as arestas (i,j) e (j,i), quando definimos o valor 0
para a aresta reversa (crev=0), evitamos que essa aresta reversa apareça na rede
residual.
4.2 Pseudocódigo e Complexidade Teórica
Pseudocódigo para fluxo de custo mínimo utilizando SSP:
1. Transformar o grafo, acrescentado o vértice de origem s e o vértice
de saída t
2. Fluxo inicial x é zero
3. Bellman-Ford para identificar ciclos negativos e obter custos não
negativos
4. Reduzir_custo(π)
5. Enquanto Gx contem um caminho “P” de s até t faça
6. Dijkstra para identificar caminhos mais curtos (tendo como
referência os custos) sucessivos de s até t
7. Reduzir_custo(π)
8. Aumentar o fluxo atual x ao caminho P
9. Atualizar Gx
Para transformar o grafo adicionando os novos vértices s e t teremos que, no pior caso,
percorrer todos os vértices, já que devemos considerar todos os nós fonte e sumidouros.
Este processo teria uma complexidade O(n). Em seguida, devemos passar um fluxo
inicial de zero, o que significa que devemos percorrer todas as arestas para estabelecer
seu fluxo, o que implicaria em uma complexidade O(m).
Após adicionar os vértices e inicializar o fluxo, efetuamos um Bellman-Ford para obter as
distâncias de s para todos os nós, e para obter caminho de s a t. Como vimos, para este
procedimento devemos percorrer todos os vértices, e todas a arestas, o que resulta em
uma complexidade O(nm). Em seguida, realizamos a redução de custos, sendo que, para
esta etapa, também é necessário percorrer todas as arestas, ou seja, a complexidade é
O(m).
Concluída a inicialização, entramos no loop onde obtemos os caminhos mais curtos de
forma sucessiva, utilizando o algoritmo de Dijkstra. Este algoritmo pode ser executado
2
em tempo O(n ) utilizando uma representação através de matrizes, ou em O(nlogn) se
utilizarmos uma heap binária. Como nosso foco não está em detalhar os algoritmos de
caminho mais curto, não entraremos em detalhes de como estas complexidades são
obtidas.
Ainda dentro do loop, executamos novamente a redução dos custos, e aumentamos o
fluxo do caminho obtido, o que no pior caso é O(n), já que devemos percorrer o
caminho.
Finalmente a execução do loop é realizada nB vezes, onde B é o valor do maior fluxo
3
entrante da rede. Desta forma, nosso algoritmo terá uma complexidade total O(n B),
sendo esta complexidade consequência de nB execuções do algoritmo de Dijkstra
2
(O(n )).
7. Resumindo:
Inicialização: O(n) + Bellman-Ford O(nm) + O(n) + O(m)
2
Busca do Caminho Mais Curto: Dijkstra O(n )
Iterações de busca: O(nB)
3
Complexidade total O(n B)
4.3 Implementação Prática
Para validar a complexidade teórica e avaliar a corretude do pseudo código apresentado
de forma prática, realizamos uma implementação do algoritmo SSP em C. Esta
implementação possui uma limitação de 6000 nós na rede, dado que utilizamos alocação
estática de matrizes para facilitar o desenvolvimento, apesar de reduzir a performance
do algoritmo.
Após executar o algoritmo para as diversas instâncias de grafos propostas, obtivemos os
seguintes resultados:
Tempo Tempo
Total de Médio Total de Custo
Grafo Nº de Nós Arcos Execução de cada Interações Mínimo
em CMC em de CMC Obtido
segundos segundos
stndrd1.net 200 1300 0.550000 0 319 196587626
stndrd2.net 200 1500 0.570000 0 301 194072029
stndrd38.net 3000 35000 592.17 0.44 1180 7265734372
cap1.net 1000 10000 2.440000 0.04 47 2572055650
cap2.net 1000 30000 5.080000 0.11 42 868553404
cap3.net 1000 40000 6.180000 0.136921 40 835752895
big5.net 5000 80000 7452.35 1.590638 4154 15817090
big6.net 5000 60000 6042.450 1.279956 4199 15864843
big7.net 5000 40000 4221.440 0.946387 3799 13970599
Como podemos ver, o tempo de execução do procedimento de caminho mais curto
utilizando Dijkstra aumenta de acordo com o aumento do números de nós. Apesar de,
teoricamente, não levar em consideração a quantidade de arestas em sua complexidade,
vimos que em nossa implementação o tempo de execução para grafos com a mesma
quantidade de nós varia de acordo com a quantidade de arestas. Isto porque, para
obtermos quais arestas estão conectadas em um determinado nó, devemos percorrer
todas as arestas do grafo. Certamente, este seria um ponto que poderia ser melhorado
em nossa implementação para que nossa complexidade prática ficasse mais próxima da
teórica. Abaixo podemos verificar como o tempo de execução do algoritmo de Dijkstra
cresce em relação a quantidade de nós do grafo.
8. Com relação a complexidade final do algoritmo, podemos verificar que o tempo de
execução está fortemente atrelado ao tempo de execução do algoritmo de Dijkstra, e à
quantas iterações de busca de CMC realizamos. É possível perceber que o tempo de
execução possui um crescimento considerável à medida que aumentamos a quantidade
de nós do grafo, como podemos verificar a seguir:
A corretude da nossa implementação foi validada utilizando os arquivos de solução
obtidos através do software LPSolve (http://sourceforge.net/projects/lpsolve), que gera
o arquivo de solução de uma determinada entrada, através do comando:
9. lp_solve -rxli xli_DIMACS mincostflow.net -wxlisol xli_DIMACS
mincostflow.sol
Após comparar os resultados obtidos com as soluções apresentadas pelo LPSolve, para
todas as instâncias de entrada, concluímos que nossa implementação está correta.
5. ACM
5.1 O Algoritmo e a Prova de Corretude
O Algoritmo ACM tem como objetivo encontrar o fluxo a custo mínimo através da
eliminação de ciclos negativos da rede residual. Segundo este algoritmo, seja x* um
fluxo viável em G, então x* é uma solução ótima, ou seja, que minimiza o custo, se e
somente se a rede residual Gx* não possui nenhum ciclo negativo. Desta forma, este
algoritmo tem como objetivo inicial estabelecer um fluxo viável (que pode ser obtido
através de uma alteração no fluxo máximo), para então eliminar os ciclos negativos da
rede residual até encontrar uma solução ótima.
Para garantir que esta abordagem é correta, devemos analisar dois pontos importantes:
1. Se uma vez terminado, teremos o fluxo de custo mínimo estabelecido;
2. Se o critério para finalizar o algoritmo é correto;
Para verificar que a cada iteração teremos uma redução no custo de passagem de fluxo,
podemos utilizar o lemma de Decomposição da Circulação de fluxo, que diz que, dada
uma rede de circulação G=(V,E,c,b,k), sem capacidades negativas, e sendo f um fluxo
viável em G, então f pode ser escrito como a soma dos ciclos de fluxo:
onde cada ciclo de fluxo é também uma circulação viável em G, ou seja, as restrições
de capacidade não são violadas.
Assim, dado um grafo G, e f, um fluxo viável em G, se f não for um fluxo de custo
mínimo então sempre irá existir um ciclo de aumento em Gf. Isto porque, se
considerarmos f* como sendo um fluxo ótimo em G, então podemos dizer que g = f* - f
é uma circulação na rede residual Gf, e também que custo(g) = custo(f*) - custo(f).
Desde que f não seja o fluxo a custo mínimo, então custo(g) é estritamente negativo.
Como vimos, podemos escrever g como um somatório de ciclos de fluxos, onde cada um
deles é um ciclo de fluxo viável em Gf. Considerando que o custo de g é o somatório de
todos os custos dos ciclos de fluxo, e que o custo de g é estritamente negativo (dado
que a solução ainda não é ótima), então um dos ciclos de fluxo também possui um custo
estritamente negativo.
Assim, quando eliminamos todos os ciclos de fluxo estritamente negativos, teremos uma
rede onde f = f*, ou seja, teremos o fluxo estabelecido com o menor custo possível.
5.2 Pseudocódigo e Complexidade Teórica
Este algoritmo pode ser expresso de forma bastante simples da seguinte forma:
10. 1 Estabeleça um fluxo viável x em G
2 Enquanto Gx possuir um ciclo negativo faça:
3 Identifique o ciclo negativo
4 δ ← min{rij : i,j ∈ Ciclo Negativo}
5 Aumente δ unidades de fluxo ao longo do ciclo
6 Atualize Gx
Para estabelecer um fluxo viável (1), podemos utilizar algoritmos de Fluxo Máximo
realizando uma pequena alteração em nosso grafo. Basicamente, basta adicionar dois
novos vértices. Um vértice s conectado à todos os nós que possuem um fluxo entrante
(bi > 0), através de arestas si de custo zero e capacidade bi; e um vértice t conectado à
todas os nós que possuem fluxo sainte (bi < 0), através de arestas it de custo zero e
capacidade -bi.
A partir daí, estabelecendo um fluxo máximo entre s e t, teremos um fluxo viável em
nosso grafo original, se e somente se este fluxo não for inferior ao somatório de todos os
fluxos entrantes. este fluxo máximo pode ser obtido utilizando o algoritmo de Ford-
Fulkerson, que pode ser executado em O(Ef) (O(mf)), onde f é o fluxo máximo na rede.
Uma vez obtido o fluxo máximo, devemos considerar este como sendo um fluxo viável e
então encontramos a rede residual, com seus respectivos custos e capacidades. Em
seguida, devemos encontrar e identificar um ciclo negativo em Gx. Esta etapa pode ser
realizada através da aplicação do algoritmo de Floyd-Warshall na rede residual, que
calcula a distância (custo) entre todos os nós do grafo. Para detectar os ciclos negativos,
bastaria verificar se existe algum valor na diagonal da matriz de distâncias com um valor
3
negativo. Este procedimento custaria O(n ). Entretanto, para grafos menos densos,
podemos utilizar o algoritmo de Bellman-Ford a partir de um nó fonte, o que nos daria
3
uma complexidade O(nm), que, nestes casos é melhor que O(n ).
Para recuperar o ciclo negativo, no caso do Bellman-Ford, por exemplo, basta armazenar
o nó predecessor responsável por atualizar a distância para um outro nó. Percorrendo
este vetor de predecessores a partir da detecção do ciclo negativo, podemos identificar
as arestas que o compõe.
Para obter o menor fluxo dentre as arestas que fazem parte do ciclo negativo, teremos
simplesmente que percorrer as arestas do ciclo e avaliar seu fluxo, o que poderia ser
realizado no pior caso em O(m).
Finalmente, para aumentar o valor do fluxo nas arestas do ciclo negativo e para atualizar
a rede residual, precisamos, no pior caso, percorrer todas as arestas, o que também nos
dá uma complexidade O(m).
Desta forma, os passos 3, 4, 5 e 6 do algoritmo, juntos, nos dão uma complexidade
teórica O(mn) + O(m) + O(m), o que equivale à O(mn), complexidade está dominada
pela execução do algoritmo de caminho mais curto para detecção de ciclos negativos.
Entretanto, para obter a complexidade total do algoritmo, devemos avaliar quantas
iterações, no pior caso, são realizadas até que todos os ciclos negativos sejam
eliminados. Neste caso, podemos dizer que este método é bastante semelhante ao
proposto no algoritmo de Ford-Fulkerson para fluxo máximo, sendo que, além da
capacidade, temos o custo envolvido nesta situação. Assim, podemos dizer que no pior
caso serão necessárias mUC iterações, onde C é o valor absoluto máximo para o custo e
U é a maior capacidade encontrada nas arestas, o que nos daria uma complexidade final
2
O(nm UC).
11. Resumindo:
Fluxo Máximo: Ford-Fulkerson em O(mf)
Obter um Ciclo Negativo: Bellman-Ford em O(mn)
Total de iterações: mUC
2
Complexidade Total: O(nm UC)
5.3 Implementação Prática
Para validar a complexidade teórica e avaliar a corretude do pseudo código apresentado
de forma prática, realizamos uma implementação do algoritmo ACM em C. Esta
implementação lê um arquivo de entrada contendo o problema proposto, ou seja, um
grafo, com capacidades e custos, e retorna o custo mínimo para a passagem do fluxo
determinado, bem como a quantidade de fluxo passada em cada aresta que possui uma
capacidade.
Para tornar a implementação mais simples, dadas as restrições de tempo, utilizamos a
representação do grafo através de matrizes de adjacência, sendo uma matriz para as
capacidades, uma para os custos, uma para os fluxos, e assim sucessivamente. Além
disso, utilizamos alocação estática de memória, o que limita a capacidade do programa
em trabalhar com grafos de até 6000 nós, assim como na implementação do SSP vista
anteriormente. Maiores detalhes sobre a implementação podem ser vistas no código em
anexo.
Após executar o algoritmo para diferentes instâncias de grafos, temos o seguinte
resultado, com relação ao tempo de execução, o que nos revela uma indicação de
complexidade:
Tempo Tempo
Total de Médio de Total de Custo
Nº de
Grafo Nº de Nós Execução cada CMC Iterações Mínimo
Arestas
em em de CMC Obtido
segundos segundos
stndrd1.net 200 1300 1.1760 0.003845 270 196587626
stndrd2.net 200 1500 1.3060 0.004274 278 194072029
cap1.net 1000 10000 11.610 0.113853 95 2572055650
cap2.net 1000 30000 28.310 0.292617 94 868553404
cap3.net 1000 40000 42.528 0.384778 108 835752895
stndrd38.net 3000 35000 2066.710 1.250286 1574 7265734372
big7.net 5000 40000 7397.340 2.766853 2464 13970599
big6.net 5000 60000 13813.879 3.745891 3586 15864843
big5.net 5000 80000 19274.191 4.682024 4023 15817090
Como podemos ver, o tempo de execução total, bem como o tempo para a obtenção dos
ciclos negativos através do Bellman-Ford aumenta de acordo com o aumento da
quantidade de nós e arestas, o que era absolutamente esperado. Uma análise mais
detalhada nos mostra que o tempo de execução do Bellman-Ford cresce de forma linear
em relação ao produto de aresta e nós, o que comprova sua complexidade O(mn). No
gráfico abaixo esta análise é mais evidente:
12. Com relação à complexidade total do algoritmo, uma análise gráfica nos mostra que a
relação entre o tempo de execução e o produto entre vértices e arestas não é linear, o
que, de certa forma, nos induz a acreditar que a complexidade prática é semelhante à
teórica.
13. A corretude da nossa implementação foi validada utilizando os arquivos de solução
obtidos, assim como no SSP, e todas as saídas estão com os valores de acordo com o
esperado.
Finalmente, temos que nossa implementação do algoritmo ACM para determinar o Fluxo
a Custo Mínimo apresentou um comportamento esperado, porém, sua performance
poderia ser bastante melhorada utilizando estruturas de dados mais eficientes e
otimizando o código de modo que as alocações estáticas fossem minimizadas. Também
poderíamos otimizar o processo de execução das operações, de modo que as constantes
envolvidas fossem reduzidas.
6. Comparativo entre o SSP e o ACM
Para avaliar qual das duas opções seria uma melhor implementação para o cálculo do
fluxo a custo mínimo, realizamos uma comparação entre os algoritmos, principalmente
com relação ao componente que obtém o caminho mais curto/ciclo negativo (Dijkstra no
SSP e Bellman-Ford no ACM). Esta comparação é relevante a medida que estes dois
influenciam bastante na complexidade total do algoritmo final.
Neste caso, foi possível ratificar que nas situações onde o número de arestas é maior
que a quantidade de nós, o tempo para execução do Bellman-Ford é maior do que
aquele necessário para obter o CMC com Dijkstra. Para grafos bastante densos, onde o
2
número de arestas tende a n , temos que o algoritmo de Bellman-Ford é bastante pior
que o Dijkstra. Nestas circunstâncias, o uso de Bellman-Ford só seria justificável para
grafos que admitem arestas com custo negativo. Além disso, para grafos bastante
densos seria até interessante o uso de Floyd-Warshall, pela sua simplicidade de
implementação. Também poderíamos utilizar o Floyd-Warshall de forma eficiente se
fosse necessário obter os CMC's entre todos os pares de vértices, o que teria uma
2
complexidade O(n m) para o Bellman-Ford.
Uma observação importante que podemos fazer é com relação à quantidade de iterações
de CMC realizadas em cada algoritmo. Podemos verificar que elas são equivalentes em
14. alguns casos, o que aumenta ainda mais a importância deste procedimento para o
tempo total de execução dos algoritmos, já que, na maior parte do tempo, o algoritmo
de Fluxo a Custo Mínimo está realizando cálculos de CMC.
Outro ponto importante é a obtenção do fluxo máximo no ACM como forma de
inicialização. Este procedimento, por si só, já é responsável por adicionar um tempo de
execução considerável, principalmente em redes com grandes quantidades de nós e
arestas, apesar de não alterar a complexidade assintótica do algoritmo.
7. Conclusões
O problema de obtenção de um fluxo á custo mínimo é um dos problemas clássicos na
teoria de Grafos e está presente em várias situações do dia-a-dia. Imagine um simples
problema de uma empresa que precisa enviar produtos de fábricas para armazéns
passando por rotas fixas entre vários pontos intermediários, cujas rotas existe um custo
e uma capacidade máxima de produtos que podem ser enviados entre cada ponto. Pode-
se ver claramente que este é um exemplo da classe de problemas em rede chamada
Fluxo a Custo Mínimo - FCM e demonstra sua grande importância em muitas situações
práticas.
Neste trabalho foi feito o estudo e implementação de dois algoritmos que resolvem o
FCM. O primeiro SSP que é baseado no algoritmo do Dijkstra e usa a estratégia de
colocar fluxo nos caminhos mais curtos de um produtor para um consumidor, ou,
analogamente, de uma fábrica para os armazéns. O segundo ACM é baseado no
algoritmo de Ford-Fulkerson e Bellman-Ford e usa a estratégia de encontrar um fluxo
máximo viável e após isso realiza um esquema de cancelamento de ciclos.
Também foram feitas as respectivas análises de funcionamento e fundamentação destes,
além é claro da análise de eficiência atrelada ao cálculo de suas complexidades. Neste
ponto vale ressaltar que foi realizada uma comparação entre os algoritmos, tanto com
relação a eficiência, tempos e execução, quando com relação as suas complexidades
teóricas.
Conceitos importantes em grafos como redes residuais foram assimilados e postos em
prática na implementação dos agoritmos.
Analisando os resultados obtidos na prática que foram condizentes com a complexidade
teórica, vemos que o algoritmo SSP se apresenta mais eficiente que que o algoritmo
ACM.