Skip to content
Snippets Groups Projects
Select Git revision
  • 3880645395c4250bfb8d6dd8cfb3fcbdad2b3419
  • master default protected
2 results

FordFulkersonAlgorithm.cs

Blame
  • FordFulkersonAlgorithm.cs 7.16 KiB
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace FlowForge;
    
    public class FordFulkersonAlgorithm
    {
        public enum SearchStrategy
        {
            BreadthFirstSearch,
            DepthFirstSearch
        }
        
        private readonly FlowGraph _flowGraph;
    
        public FordFulkersonAlgorithm(FlowGraph flowGraph)
        {
            _flowGraph = flowGraph;
        }
    
        public double Run(string sourceId, string targetId, SearchStrategy strategy=SearchStrategy.BreadthFirstSearch)
        {
            // get source and target nodes
            FlowNode? source = _flowGraph.GetVertexById(sourceId);
            FlowNode? target = _flowGraph.GetVertexById(targetId);
    
            if (source == null || target == null)
                throw new ArgumentException("Invalid source or target node!");
    
            double maxFlow = 0.0;
            var pathFlow = new Dictionary<FlowEdge, double>();
    
    
            var allEdges = _flowGraph.GetEdges();
            foreach (var edge in allEdges)
            {
                edge.Residual = edge.MaxFlow;
            }
            
            Console.WriteLine("Start of Ford-Fulkerson Algorithm...");
    
            // execute as long as there is an augmenting path
            while (FindAugmentingPath(source, target, pathFlow, strategy))
            {
                PrintOrderedPath(pathFlow);
                
                // calculate bottleneck in augmenting path
                double pathMinFlow = double.MaxValue;
                foreach (var edge in pathFlow.Keys)
                {
                    pathMinFlow = Math.Min(pathMinFlow, pathFlow[edge]);
                }
                Console.WriteLine($"Bottleneck (minimum flow in path): {pathMinFlow}");
    
                // add flow along the augmenting path
                foreach (var edge in pathFlow.Keys)
                {
                    edge.Residual -= pathMinFlow;
                    Console.WriteLine($"Updating forward edge {edge.Source.Id} -> {edge.Target.Id}, New Residual: {edge.Residual}");
    
    
                    // check if backwards edge exists
                    FlowEdge? reverseEdge;
                    if (!_flowGraph.Graph.TryGetEdge(edge.Target, edge.Source, out reverseEdge))
                    {
                        // create a backwards edge if it doesn't exist
                        reverseEdge = new FlowEdge(edge.Target, edge.Source, edge.MaxFlow);
    
                        reverseEdge.Residual = 0;
                        reverseEdge.IsBackwards = true;
                        
                        _flowGraph.Graph.AddEdge(reverseEdge);
                        
                        Console.WriteLine($"Creating reverse edge {reverseEdge.Source.Id} -> {reverseEdge.Target.Id}");
                    }
    
                    if (reverseEdge != null)
                    {
                        reverseEdge.Residual += pathMinFlow;
                        
                        Console.WriteLine($"Updating reverse edge {reverseEdge.Source.Id} -> {reverseEdge.Target.Id}, New Residual: {reverseEdge.Residual}");
                    }
                }
    
                // add to total flow
                maxFlow += pathMinFlow;
                Console.WriteLine($"Current max flow: {maxFlow}\n");
            }
    
            Console.WriteLine("No Augmenting Path found anymore!\n");
    
            Console.WriteLine($"Max flow found: {maxFlow}\n");
            // return maximum flow after no augmenting paths were found anymore
            return maxFlow;
        }
        
        private bool FindAugmentingPath(FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow, SearchStrategy strategy)
        {
            // parent map to walk back path
            var parentMap = new Dictionary<FlowNode, FlowEdge>();
            
            // Choose the appropriate data structure based on the strategy
            IEnumerable<FlowNode> nodesToVisit;
            if (strategy == SearchStrategy.BreadthFirstSearch)
            {
                var queue = new Queue<FlowNode>();
                queue.Enqueue(source);
                nodesToVisit = queue;
            }
            else
            {
                var stack = new Stack<FlowNode>();
                stack.Push(source);
                nodesToVisit = stack;
            }
            
            // map to store visited nodes
            var visited = new HashSet<FlowNode> {source};
    
            while (nodesToVisit.Any())
            {
                var current = strategy == SearchStrategy.BreadthFirstSearch ? ((Queue<FlowNode>)nodesToVisit).Dequeue() : ((Stack<FlowNode>)nodesToVisit).Pop();
    
                Console.WriteLine("Current: " + current);
                var outEdges = _flowGraph.Graph.OutEdges(current).ToList();
                
                List<FlowEdge> sortedOutEdges;
                if (strategy == SearchStrategy.BreadthFirstSearch)
                {
                    sortedOutEdges = outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList();
                }
                else
                {
                    sortedOutEdges = outEdges.OrderByDescending(edge => int.Parse(edge.Target.Id)).ToList();
                }
                
                // go through all outgoing edges
                foreach (FlowEdge currentEdge in sortedOutEdges)
                {
                    if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue;
                    
                    visited.Add(currentEdge.Target);
                    parentMap.Add(currentEdge.Target, currentEdge);
                    
                    // if we reached the target node
                    if (currentEdge.Target.Equals(target))
                    {
                        BuildAugmentingPath(source, target, parentMap, pathFlow);
                        return true;
                    }
                    
                    // search further
                    if (strategy == SearchStrategy.BreadthFirstSearch)
                    {
                        ((Queue<FlowNode>)nodesToVisit).Enqueue(currentEdge.Target);
                    }
                    else
                    {
                        ((Stack<FlowNode>)nodesToVisit).Push(currentEdge.Target);
                    }
                }
            }
            
            return false;
        }
        
        private static void BuildAugmentingPath(FlowNode source, FlowNode target, Dictionary<FlowNode, FlowEdge> parentMap, Dictionary<FlowEdge, double> pathFlow)
        {
            FlowNode currentNode = target;
            pathFlow.Clear();
    
            while (currentNode != source)
            {
                FlowEdge pathEdge = parentMap[currentNode];
                pathFlow[pathEdge] = pathEdge.Residual;
                currentNode = pathEdge.Source;
            }
        }
        
        public static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow)
        {
            Console.WriteLine("Augmenting Path found:");
            
            // Step 1: Find the starting edge
            FlowEdge startEdge = pathFlow.Keys
                .First(edge => !pathFlow.Keys.Any(e => e.Target.Id == edge.Source.Id));
    
            // Step 2: Follow the path from start to end
            List<FlowEdge> orderedPath = new List<FlowEdge> { startEdge };
            while (true)
            {
                var currentEdge = orderedPath.Last();
                var nextEdge = pathFlow.Keys.FirstOrDefault(edge => edge.Source.Id == currentEdge.Target.Id);
                
                if (nextEdge == null) break; // End of path reached
                orderedPath.Add(nextEdge);
            }
    
            // Step 3: Output the ordered path
            foreach (var edge in orderedPath)
            {
                Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Residual Capacity: {pathFlow[edge]}");
            }
        }
    }