diff --git a/BreadthFirstSearchStrategy.cs b/BreadthFirstSearchStrategy.cs new file mode 100644 index 0000000000000000000000000000000000000000..6637dc4e455777f73ed97bcebbb320cad1fb82d8 --- /dev/null +++ b/BreadthFirstSearchStrategy.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FlowForge; + +public class BreadthFirstSearchStrategy : ISearchStrategy +{ + public bool FindAugmentingPath(FlowGraph flowGraph, FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow, bool forceWorstCase = false) + { + Console.WriteLine("Breadth Depth First Search..."); + // parent map to walk back path + var parentMap = new Dictionary<FlowNode, FlowEdge>(); + + Queue<FlowNode> nodesToVisit = new Queue<FlowNode>(); + nodesToVisit.Enqueue(source); + + // map to store visited nodes + var visited = new HashSet<FlowNode> {source}; + + while (nodesToVisit.Any()) + { + var current = nodesToVisit.Dequeue(); + + Console.WriteLine("Current Node: " + current); + var outEdges = flowGraph.Graph.OutEdges(current).ToList(); + + List<FlowEdge> sortedOutEdges = forceWorstCase ? outEdges.OrderBy(edge => edge.Residual).ToList() : outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList(); + + Console.WriteLine("Sorted out edges for node:"); + foreach (FlowEdge edge in sortedOutEdges) + { + Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Current Flow: {edge.CurrentFlow}"); + } + + Console.WriteLine("Looping through:"); + + List<FlowNode> nodesToAdd = new List<FlowNode>(); + + // go through all outgoing edges + foreach (FlowEdge currentEdge in sortedOutEdges) + { + + if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue; + + Console.WriteLine($"{currentEdge.Source.Id} -> {currentEdge.Target.Id}, Current Flow: {currentEdge.CurrentFlow}"); + + visited.Add(currentEdge.Target); + parentMap.Add(currentEdge.Target, currentEdge); + + // if we reached the target node + if (currentEdge.Target.Equals(target)) + { + ISearchStrategy.BuildAugmentingPath(source, target, parentMap, pathFlow); + return true; + } + + nodesToAdd.Add(currentEdge.Target); + } + + foreach (var node in nodesToAdd.OrderBy(n => int.Parse(n.Id))) + { + nodesToVisit.Enqueue(node); + } + } + + return false; + } +} diff --git a/DepthFirstSearchStrategy.cs b/DepthFirstSearchStrategy.cs new file mode 100644 index 0000000000000000000000000000000000000000..126a9f6ae3a103d0925caf6eeef8c9066c83d47f --- /dev/null +++ b/DepthFirstSearchStrategy.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FlowForge; + +public class DepthFirstSearchStrategy : ISearchStrategy +{ + public bool FindAugmentingPath(FlowGraph flowGraph, FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow, bool forceWorstCase = false) + { + if (forceWorstCase) + { + FlowNode? node2 = flowGraph.GetVertexById("2"); + FlowNode? node3 = flowGraph.GetVertexById("3"); + flowGraph.Graph.TryGetEdge(node2 ?? throw new InvalidOperationException(), node3 ?? throw new InvalidOperationException(), out var edgeFrom2To3); + + WorstCaseSearch worstCaseSearch = new WorstCaseSearch(); + return worstCaseSearch.FindAugmentingPathWithEdge(flowGraph, source, target, edgeFrom2To3 ?? throw new InvalidOperationException(), pathFlow); + } + + // parent map to walk back path + var parentMap = new Dictionary<FlowNode, FlowEdge>(); + + Stack<FlowNode> nodesToVisit = new Stack<FlowNode>(); + nodesToVisit.Push(source); + + // map to store visited nodes + var visited = new HashSet<FlowNode>(); + + while (nodesToVisit.Any()) + { + var current = nodesToVisit.Pop(); + Console.WriteLine("Current Node: " + current); + + // if we reached the target node + if (current.Equals(target)) + { + ISearchStrategy.BuildAugmentingPath(source, target, parentMap, pathFlow); + return true; + } + + var outEdges = flowGraph.Graph.OutEdges(current).ToList(); + List<FlowEdge> sortedOutEdges = outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList(); + + Console.WriteLine("Sorted out edges for node:"); + foreach (FlowEdge edge in sortedOutEdges) + { + Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Current Flow: {edge.CurrentFlow}"); + } + + Console.WriteLine("Looping through:"); + + List<FlowNode> nodesToAdd = new List<FlowNode>(); + + // go through all outgoing edges + foreach (FlowEdge currentEdge in sortedOutEdges) + { + if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue; + + Console.WriteLine($"{currentEdge.Source.Id} -> {currentEdge.Target.Id}, Current Flow: {currentEdge.CurrentFlow}"); + + parentMap[currentEdge.Target] = currentEdge; + nodesToAdd.Add(currentEdge.Target); + } + + foreach (var node in nodesToAdd.OrderByDescending(n => int.Parse(n.Id))) + { + nodesToVisit.Push(node); + } + + visited.Add(current); + } + + return false; + } +} diff --git a/FlowEdge.cs b/FlowEdge.cs index d8a61017b3aac44f9d1b63adc01c7c101d75ef38..ba130c330832502e9dd93a834b6f334e194bc12f 100644 --- a/FlowEdge.cs +++ b/FlowEdge.cs @@ -8,13 +8,7 @@ public class FlowEdge : Edge<FlowNode> public double CurrentFlow { get; set; } - public double Residual - { - get => MaxFlow - CurrentFlow; - //set => CurrentFlow = MaxFlow - value; - //make the setter private - private set => CurrentFlow = MaxFlow - value; - } + public double Residual => MaxFlow - CurrentFlow; public bool IsBackwards { get; set; } public bool IsHighlighted { get; set; } diff --git a/FordFulkersonAlgorithm.cs b/FordFulkersonAlgorithm.cs index 3809be4dd62a3086d0c9e7c10327a579a391c5d2..4c9589c9b32b657c986dc4235d5694c4560f2076 100644 --- a/FordFulkersonAlgorithm.cs +++ b/FordFulkersonAlgorithm.cs @@ -26,7 +26,7 @@ public class FordFulkersonAlgorithm this.TargetId = targetId; } - public double Run(SearchStrategy strategy=SearchStrategy.BreadthFirstSearch) + public double Run(ISearchStrategy strategy, bool forceWorstCase = false) { // get source and target nodes FlowNode? source = _flowGraph.GetVertexById(SourceId); @@ -37,6 +37,10 @@ public class FordFulkersonAlgorithm throw new ArgumentException("Invalid source or target node!"); } Console.WriteLine("Start of Ford-Fulkerson Algorithm..."); + if (forceWorstCase) + { + Console.WriteLine("Using worst case scenario for edge selection."); + } double maxFlow = 0.0; var pathFlow = new Dictionary<FlowEdge, double>(); @@ -47,7 +51,7 @@ public class FordFulkersonAlgorithm SaveGraphState(); // execute as long as there is an augmenting path - while (strategy == SearchStrategy.BreadthFirstSearch ? FindAugmentingPathBfs(source, target, pathFlow) : FindAugmentingPathDfs(source, target, pathFlow)) + while (strategy.FindAugmentingPath(_flowGraph, source, target, pathFlow, forceWorstCase)) { PrintOrderedPath(pathFlow); @@ -131,138 +135,7 @@ public class FordFulkersonAlgorithm Console.WriteLine($"Bottleneck (minimum flow in path): {pathMinFlow}"); return pathMinFlow; } - - private bool FindAugmentingPathBfs(FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow) - { - // parent map to walk back path - var parentMap = new Dictionary<FlowNode, FlowEdge>(); - - Queue<FlowNode> nodesToVisit = new Queue<FlowNode>(); - nodesToVisit.Enqueue(source); - - // map to store visited nodes - var visited = new HashSet<FlowNode> {source}; - - while (nodesToVisit.Any()) - { - var current = nodesToVisit.Dequeue(); - - Console.WriteLine("Current Node: " + current); - var outEdges = _flowGraph.Graph.OutEdges(current).ToList(); - - List<FlowEdge> sortedOutEdges = outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList(); - - Console.WriteLine("Sorted out edges for node:"); - foreach (FlowEdge edge in sortedOutEdges) - { - Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Current Flow: {edge.CurrentFlow}"); - } - - Console.WriteLine("Looping through:"); - - List<FlowNode> nodesToAdd = new List<FlowNode>(); - - // go through all outgoing edges - foreach (FlowEdge currentEdge in sortedOutEdges) - { - - if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue; - - Console.WriteLine($"{currentEdge.Source.Id} -> {currentEdge.Target.Id}, Current Flow: {currentEdge.CurrentFlow}"); - - 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; - } - - nodesToAdd.Add(currentEdge.Target); - } - - foreach (var node in nodesToAdd.OrderBy(n => int.Parse(n.Id))) - { - nodesToVisit.Enqueue(node); - } - } - - return false; - } - - private bool FindAugmentingPathDfs(FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow) - { - // parent map to walk back path - var parentMap = new Dictionary<FlowNode, FlowEdge>(); - - Stack<FlowNode> nodesToVisit = new Stack<FlowNode>(); - nodesToVisit.Push(source); - - // map to store visited nodes - var visited = new HashSet<FlowNode>(); - - while (nodesToVisit.Any()) - { - var current = nodesToVisit.Pop(); - Console.WriteLine("Current Node: " + current); - - // if we reached the target node - if (current.Equals(target)) - { - BuildAugmentingPath(source, target, parentMap, pathFlow); - return true; - } - - var outEdges = _flowGraph.Graph.OutEdges(current).ToList(); - List<FlowEdge> sortedOutEdges = outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList(); - - Console.WriteLine("Sorted out edges for node:"); - foreach (FlowEdge edge in sortedOutEdges) - { - Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Current Flow: {edge.CurrentFlow}"); - } - - Console.WriteLine("Looping through:"); - - List<FlowNode> nodesToAdd = new List<FlowNode>(); - - // go through all outgoing edges - foreach (FlowEdge currentEdge in sortedOutEdges) - { - if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue; - - Console.WriteLine($"{currentEdge.Source.Id} -> {currentEdge.Target.Id}, Current Flow: {currentEdge.CurrentFlow}"); - - parentMap[currentEdge.Target] = currentEdge; - nodesToAdd.Add(currentEdge.Target); - } - - foreach (var node in nodesToAdd.OrderByDescending(n => int.Parse(n.Id))) - { - nodesToVisit.Push(node); - } - - visited.Add(current); - } - - 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; - } - } - private static void PrintOrderedPath(Dictionary<FlowEdge, double> pathFlow) { Console.WriteLine("Augmenting Path found:"); diff --git a/ISearchStrategy.cs b/ISearchStrategy.cs new file mode 100644 index 0000000000000000000000000000000000000000..2ed45734d3d464199734128995fcecb29e142514 --- /dev/null +++ b/ISearchStrategy.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace FlowForge; + +public interface ISearchStrategy +{ + bool FindAugmentingPath(FlowGraph flowGraph, FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow, bool forceWorstCase = false); + + public 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; + } + } + +} diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 5099425a41d3c23e0498d20a84084c030905376b..062db1162ff5cfcbf9e8ea532334d5e8bbb30d00 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -155,7 +155,8 @@ namespace FlowForge //SaveNodePositions(_gViewer.Graph); // Save positions before running the algorithm - double maxFlow = _fordFulkerson.Run(FordFulkersonAlgorithm.SearchStrategy.DepthFirstSearch); + bool forceWorstCase = WorstCaseCheckbox.IsChecked ?? false; + double maxFlow = _fordFulkerson.Run(new DepthFirstSearchStrategy(), forceWorstCase); _isInitialized = true; DisplayGraph(); @@ -173,7 +174,9 @@ namespace FlowForge if (_gViewer != null) _gViewer.NeedToCalculateLayout = true; SaveNodePositions(_gViewer?.Graph); // Save positions before running the algorithm - double maxFlow = _fordFulkerson.Run(FordFulkersonAlgorithm.SearchStrategy.BreadthFirstSearch); + + bool forceWorstCase = WorstCaseCheckbox.IsChecked ?? false; + double maxFlow = _fordFulkerson.Run(new BreadthFirstSearchStrategy(), forceWorstCase); _isInitialized = true; DisplayGraph(); diff --git a/WorstCaseSearch.cs b/WorstCaseSearch.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b8d672ecac9e3ea4257a71dcd137d0a65fd4b82 --- /dev/null +++ b/WorstCaseSearch.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FlowForge; + +public class WorstCaseSearch +{ + public bool FindAugmentingPathWithEdge(FlowGraph flowGraph, FlowNode source, FlowNode target, FlowEdge requiredEdge, Dictionary<FlowEdge, double> pathFlow) + { + Console.WriteLine($"Searching for augmenting path that includes edge {requiredEdge.Source.Id} -> {requiredEdge.Target.Id}"); + + // Determine if the required edge is valid in either direction + FlowEdge? validEdge = null; + bool isReversed = false; + + if (requiredEdge.Residual > 0) + { + validEdge = requiredEdge; + } + else if (flowGraph.Graph.TryGetEdge(requiredEdge.Target, requiredEdge.Source, out validEdge)) + { + if (validEdge.Residual > 0) + { + isReversed = true; + } + else + { + validEdge = null; + } + } + else + { + validEdge = null; + } + + if (validEdge == null) + { + Console.WriteLine("No valid residual capacity on the required edge in either direction."); + return FindAugmentingPath(flowGraph, source, target, pathFlow); + } + + // Phase 1: Find a path from source to the starting node of the valid edge + var phase1Path = new Dictionary<FlowEdge, double>(); + var phase1Success = FindAugmentingPath(flowGraph, source, isReversed ? validEdge.Target : validEdge.Source, phase1Path); + + if (!phase1Success) + { + Console.WriteLine("No path found from source to the required edge."); + return FindAugmentingPath(flowGraph, source, target, pathFlow); + } + + // Phase 2: Find a path from the ending node of the valid edge to the target + var phase2Path = new Dictionary<FlowEdge, double>(); + var phase2Success = FindAugmentingPath(flowGraph, isReversed ? validEdge.Source : validEdge.Target, target, phase2Path); + + if (!phase2Success) + { + Console.WriteLine("No path found from the required edge to the target."); + return FindAugmentingPath(flowGraph, source, target, pathFlow); + } + + // Combine the results + pathFlow.Clear(); + foreach (var kvp in phase1Path) + { + pathFlow[kvp.Key] = kvp.Value; + } + pathFlow[validEdge] = validEdge.Residual; // Include the required edge + foreach (var kvp in phase2Path) + { + pathFlow[kvp.Key] = kvp.Value; + } + + Console.WriteLine("Augmenting path including the required edge found!"); + return true; + } + + private bool FindAugmentingPath(FlowGraph flowGraph, FlowNode source, FlowNode target, Dictionary<FlowEdge, double> pathFlow) + { + // parent map to walk back path + var parentMap = new Dictionary<FlowNode, FlowEdge>(); + + Stack<FlowNode> nodesToVisit = new Stack<FlowNode>(); + nodesToVisit.Push(source); + + // map to store visited nodes + var visited = new HashSet<FlowNode>(); + + while (nodesToVisit.Any()) + { + var current = nodesToVisit.Pop(); + Console.WriteLine("Current Node: " + current); + + // if we reached the target node + if (current.Equals(target)) + { + ISearchStrategy.BuildAugmentingPath(source, target, parentMap, pathFlow); + return true; + } + + var outEdges = flowGraph.Graph.OutEdges(current).ToList(); + List<FlowEdge> sortedOutEdges = outEdges.OrderBy(edge => int.Parse(edge.Target.Id)).ToList(); + + Console.WriteLine("Sorted out edges for node:"); + foreach (FlowEdge edge in sortedOutEdges) + { + Console.WriteLine($"{edge.Source.Id} -> {edge.Target.Id}, Current Flow: {edge.CurrentFlow}"); + } + + Console.WriteLine("Looping through:"); + + List<FlowNode> nodesToAdd = new List<FlowNode>(); + + // go through all outgoing edges + foreach (FlowEdge currentEdge in sortedOutEdges) + { + if (currentEdge.Residual <= 0 || visited.Contains(currentEdge.Target)) continue; + + Console.WriteLine($"{currentEdge.Source.Id} -> {currentEdge.Target.Id}, Current Flow: {currentEdge.CurrentFlow}"); + + parentMap[currentEdge.Target] = currentEdge; + nodesToAdd.Add(currentEdge.Target); + } + + foreach (var node in nodesToAdd.OrderByDescending(n => int.Parse(n.Id))) + { + nodesToVisit.Push(node); + } + + visited.Add(current); + } + + return false; + } +} \ No newline at end of file