Fluxo a Custo Mínimo

  • 4,851 views
Uploaded on

This paper describes several approaches that can be used to find the minimum cost flow through a network

This paper describes several approaches that can be used to find the minimum cost flow through a network

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • Bem detalhado
    Are you sure you want to
    Your message goes here
  • libera ae pra download
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
4,851
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
0
Comments
2
Likes
7

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

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