SlideShare uma empresa Scribd logo
1 de 14
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:
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.
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.
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:
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
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 )).
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.
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:
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:
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).
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:
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.
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
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.

Mais conteúdo relacionado

Mais procurados

Relatório de física resistência e resistividade
Relatório de física   resistência e resistividadeRelatório de física   resistência e resistividade
Relatório de física resistência e resistividadeVictor Said
 
Capítulo 29 fundamentos da física 3 - halliday 8ªed.
Capítulo 29   fundamentos da física 3 - halliday 8ªed.Capítulo 29   fundamentos da física 3 - halliday 8ªed.
Capítulo 29 fundamentos da física 3 - halliday 8ªed.Swéle Rachel
 
Método dos mínimos quadrados
Método dos mínimos quadradosMétodo dos mínimos quadrados
Método dos mínimos quadradosKleber Jacinto
 
Física experimental - Aula 1.pptx
Física experimental - Aula 1.pptxFísica experimental - Aula 1.pptx
Física experimental - Aula 1.pptxssuser3d1cd51
 
Hidrostática - Física
Hidrostática - FísicaHidrostática - Física
Hidrostática - FísicaSlides de Tudo
 
Expressão analítica de uma função quadrática
Expressão analítica de uma função quadráticaExpressão analítica de uma função quadrática
Expressão analítica de uma função quadráticaPaulo Mutolo
 
3º Ano Geometria Espacial
3º Ano Geometria Espacial3º Ano Geometria Espacial
3º Ano Geometria EspacialLeosmar Tavares
 
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)José Junior Barreto
 
Sentenças abertas, implicações e equivalências lógicas
Sentenças abertas, implicações e equivalências lógicasSentenças abertas, implicações e equivalências lógicas
Sentenças abertas, implicações e equivalências lógicasSérgio de Castro
 
Corrente eletrica
Corrente eletricaCorrente eletrica
Corrente eletricacon_seguir
 
RELATORIO DE FÍSICA - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...
RELATORIO DE FÍSICA  - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...RELATORIO DE FÍSICA  - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...
RELATORIO DE FÍSICA - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...Gilsilene Choplin .
 
Elasticidade e suas aplicações
Elasticidade e suas aplicaçõesElasticidade e suas aplicações
Elasticidade e suas aplicaçõesLuciano Pires
 

Mais procurados (20)

Trabalho e Energia Slide
Trabalho e Energia SlideTrabalho e Energia Slide
Trabalho e Energia Slide
 
Relatório de física resistência e resistividade
Relatório de física   resistência e resistividadeRelatório de física   resistência e resistividade
Relatório de física resistência e resistividade
 
Capítulo 29 fundamentos da física 3 - halliday 8ªed.
Capítulo 29   fundamentos da física 3 - halliday 8ªed.Capítulo 29   fundamentos da física 3 - halliday 8ªed.
Capítulo 29 fundamentos da física 3 - halliday 8ªed.
 
Método dos mínimos quadrados
Método dos mínimos quadradosMétodo dos mínimos quadrados
Método dos mínimos quadrados
 
Física experimental - Aula 1.pptx
Física experimental - Aula 1.pptxFísica experimental - Aula 1.pptx
Física experimental - Aula 1.pptx
 
Hidrostática - Física
Hidrostática - FísicaHidrostática - Física
Hidrostática - Física
 
Expressão analítica de uma função quadrática
Expressão analítica de uma função quadráticaExpressão analítica de uma função quadrática
Expressão analítica de uma função quadrática
 
Associação de resistores
Associação de resistoresAssociação de resistores
Associação de resistores
 
Estudo dos geradores
Estudo dos geradoresEstudo dos geradores
Estudo dos geradores
 
3º Ano Geometria Espacial
3º Ano Geometria Espacial3º Ano Geometria Espacial
3º Ano Geometria Espacial
 
eletrostática
eletrostáticaeletrostática
eletrostática
 
Relatório de física 3 lei de ohm
Relatório de física 3  lei de ohmRelatório de física 3  lei de ohm
Relatório de física 3 lei de ohm
 
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)
QUESTÕES DO ENEM (LEITURA DE GRÁFICOS)
 
Matriz e Determinante
Matriz e DeterminanteMatriz e Determinante
Matriz e Determinante
 
Sentenças abertas, implicações e equivalências lógicas
Sentenças abertas, implicações e equivalências lógicasSentenças abertas, implicações e equivalências lógicas
Sentenças abertas, implicações e equivalências lógicas
 
Corrente eletrica
Corrente eletricaCorrente eletrica
Corrente eletrica
 
Moda, Média e Mediana
Moda, Média e MedianaModa, Média e Mediana
Moda, Média e Mediana
 
RELATORIO DE FÍSICA - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...
RELATORIO DE FÍSICA  - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...RELATORIO DE FÍSICA  - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...
RELATORIO DE FÍSICA - CONDICIONANDO UM CORPO RÍGIDO AO EQUILIBRIO DE TRANSLA...
 
Campo elétrico
Campo elétricoCampo elétrico
Campo elétrico
 
Elasticidade e suas aplicações
Elasticidade e suas aplicaçõesElasticidade e suas aplicações
Elasticidade e suas aplicações
 

Semelhante a Fluxo a Custo Mínimo

O problema do transporte aplicado à grafos
O problema do transporte aplicado à grafosO problema do transporte aplicado à grafos
O problema do transporte aplicado à grafosEduardo Souza
 
Fluxos em Rede e Associação
Fluxos em Rede e AssociaçãoFluxos em Rede e Associação
Fluxos em Rede e AssociaçãoIorgama Porcely
 
Análise de Algoritmos - Método Guloso
Análise de Algoritmos - Método GulosoAnálise de Algoritmos - Método Guloso
Análise de Algoritmos - Método GulosoDelacyr Ferreira
 
Caminho Mínimo em Grafos - Algoritmo de Bellman-Ford
Caminho Mínimo em Grafos - Algoritmo de Bellman-FordCaminho Mínimo em Grafos - Algoritmo de Bellman-Ford
Caminho Mínimo em Grafos - Algoritmo de Bellman-FordGabriel Albuquerque
 
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...Joana Pinto
 
Árvores Espalhadas Mínimas
Árvores Espalhadas MínimasÁrvores Espalhadas Mínimas
Árvores Espalhadas MínimasDiego Cavalca
 
Manual solucoes redes_tanenbaum
Manual solucoes redes_tanenbaumManual solucoes redes_tanenbaum
Manual solucoes redes_tanenbaumredesinforma
 
2 fluxo bidimensional novo
2   fluxo bidimensional novo2   fluxo bidimensional novo
2 fluxo bidimensional novoraphaelcava
 
O caixeiro viajante é np completo
O caixeiro viajante é np completoO caixeiro viajante é np completo
O caixeiro viajante é np completoMarcelo Carvalho
 
Exame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solutionExame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solutionMarcosPacheco65
 
Exame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solutionExame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solution17535069649
 
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto Valores
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto ValoresDecomposições de matrizes utilizando conceitos de Auto Vetores e Auto Valores
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto ValoresFelipe Schimith Batista
 
Introdução aos grafos: Principais conceitos
Introdução aos grafos: Principais conceitosIntrodução aos grafos: Principais conceitos
Introdução aos grafos: Principais conceitosssusera0fc94
 
09 problemas de grafos np-completos
09 problemas de grafos np-completos09 problemas de grafos np-completos
09 problemas de grafos np-completosYuri Passos
 

Semelhante a Fluxo a Custo Mínimo (20)

O problema do transporte aplicado à grafos
O problema do transporte aplicado à grafosO problema do transporte aplicado à grafos
O problema do transporte aplicado à grafos
 
Fluxos em Rede e Associação
Fluxos em Rede e AssociaçãoFluxos em Rede e Associação
Fluxos em Rede e Associação
 
Análise de Algoritmos - Método Guloso
Análise de Algoritmos - Método GulosoAnálise de Algoritmos - Método Guloso
Análise de Algoritmos - Método Guloso
 
I WPC - Artigo
I WPC - ArtigoI WPC - Artigo
I WPC - Artigo
 
Caminho Mínimo em Grafos - Algoritmo de Bellman-Ford
Caminho Mínimo em Grafos - Algoritmo de Bellman-FordCaminho Mínimo em Grafos - Algoritmo de Bellman-Ford
Caminho Mínimo em Grafos - Algoritmo de Bellman-Ford
 
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...
MACS - grafos, trajetos e circuitos eulerianos; circuitos eulerianos...
 
Árvores Espalhadas Mínimas
Árvores Espalhadas MínimasÁrvores Espalhadas Mínimas
Árvores Espalhadas Mínimas
 
Exercicio de Modelagem de Suspensão Dinamica
Exercicio de Modelagem de Suspensão DinamicaExercicio de Modelagem de Suspensão Dinamica
Exercicio de Modelagem de Suspensão Dinamica
 
Manual solucoes redes_tanenbaum
Manual solucoes redes_tanenbaumManual solucoes redes_tanenbaum
Manual solucoes redes_tanenbaum
 
2 fluxo bidimensional novo
2   fluxo bidimensional novo2   fluxo bidimensional novo
2 fluxo bidimensional novo
 
O caixeiro viajante é np completo
O caixeiro viajante é np completoO caixeiro viajante é np completo
O caixeiro viajante é np completo
 
Flexibilidade
FlexibilidadeFlexibilidade
Flexibilidade
 
Exame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solutionExame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solution
 
Exame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solutionExame unificado fisica 2010 1 solution
Exame unificado fisica 2010 1 solution
 
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto Valores
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto ValoresDecomposições de matrizes utilizando conceitos de Auto Vetores e Auto Valores
Decomposições de matrizes utilizando conceitos de Auto Vetores e Auto Valores
 
Introdução aos grafos: Principais conceitos
Introdução aos grafos: Principais conceitosIntrodução aos grafos: Principais conceitos
Introdução aos grafos: Principais conceitos
 
Grafos_1.pptx
Grafos_1.pptxGrafos_1.pptx
Grafos_1.pptx
 
Camada rede
Camada redeCamada rede
Camada rede
 
09 problemas de grafos np-completos
09 problemas de grafos np-completos09 problemas de grafos np-completos
09 problemas de grafos np-completos
 
Grafos
GrafosGrafos
Grafos
 

Fluxo a Custo Mínimo

  • 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.