Graph Theory - Ford-Fulkerson Algorithm



Ford-Fulkerson Algorithm

The Ford-Fulkerson Algorithm is used to find the maximum flow in a flow network. It works by repeatedly finding augmenting paths in the residual graph and increasing the flow until no more augmenting paths can be found.

An augmenting path is a path in a residual graph from the source to the sink such that every edge along the path has a positive residual capacity. This path represents a potential route where more flow can be added to the network. The residual capacity of an edge is the difference between its original capacity and the current flow through the edge.

A residual graph is a transformed version of the original flow network used in flow algorithms like Ford-Fulkerson to track the remaining capacities of edges, where more flow can be pushed through.

It reflects the "leftover" capacity after accounting for the current flow, and it is important for finding augmenting paths in the process of maximizing flow.

Overview of Ford-Fulkerson Algorithm

The Ford-Fulkerson algorithm is designed to find the maximum flow in a directed graph, where each edge has a capacity and a flow. The flow must satisfy the following constraints −

  • Capacity Constraint: The flow on any edge cannot exceed the capacity of the edge.
  • Flow Conservation: The total flow into any vertex (except for the source and sink) must equal the total flow out of the vertex.
  • Non-Negativity: Flow cannot be negative on any edge.

The Ford-Fulkerson algorithm uses the following basic steps to solve the maximum flow problem:

  • Initialization: Start with an initial flow of 0 on all edges.
  • Augment Flow: Find an augmenting path from the source to the sink in the residual graph. Augment the flow along this path.
  • Update Residual Graph: Update the residual capacities of the edges after augmenting the flow.
  • Repeat: Continue finding augmenting paths and updating the flow until no augmenting paths exist in the residual graph.

Properties of Ford-Fulkerson Algorithm

Ford-Fulkerson algorithm has several important properties and characteristics, such as −

  • Greedy Algorithm: The algorithm is greedy in nature, as it chooses augmenting paths to increase the flow at each step.
  • Termination: The algorithm terminates when no more augmenting paths can be found in the residual graph, indicating that the maximum flow has been reached.
  • Optimal Solution: The Ford-Fulkerson algorithm guarantees that the flow obtained is the maximum possible flow for the network.
  • Non-deterministic: The algorithm does not guarantee an optimal augmenting path selection, which can affect performance in some cases. The flow found may depend on the order in which augmenting paths are chosen.

Steps of Ford-Fulkerson Algorithm

To better understand how the Ford-Fulkerson algorithm works, let us break it down into individual steps −

  • Step 1: Initialize Flow: Start with an initial flow of 0 on all edges of the graph. Set the initial flow value on each edge to zero.
  • Step 2: Residual Graph Construction: Construct the residual graph, which represents the remaining capacities of the edges in the network. The residual capacity of an edge is the original capacity minus the flow already sent along that edge.
  • Step 3: Find Augmenting Path: Search for an augmenting path from the source to the sink in the residual graph. An augmenting path is a path where the residual capacity is positive for all edges along the path.
  • Step 4: Augment Flow: Increase the flow along the found augmenting path by the minimum residual capacity of the edges in the path.
  • Step 5: Update Residual Graph: Update the residual capacities of the edges in the augmenting path. The forward edges are reduced by the flow, and reverse edges are increased by the flow to allow for potential flow reversals in future iterations.
  • Step 6: Repeat: Repeat the process of finding augmenting paths and updating the flow until no more augmenting paths can be found.

Example of Ford-Fulkerson Algorithm

Consider the following graph −

Ford-Fulkerson Algorithm

We want to find the maximum flow from the source (S) to the sink (T) using the Ford-Fulkerson algorithm.

Step 1: Initialize Flow

Start with an initial flow of 0 on all edges:

flow = {(S, A): 0, (S, B): 0, (A, T): 0, (B, T): 0, (A, B): 0}

Step 2: Find Augmenting Path

In the first iteration, we find the augmenting path: S A T. The minimum residual capacity along this path is 3 (the capacity of the edge S A).

Step 3: Augment Flow

We augment the flow along this path by 3. After augmenting, the flow becomes:

flow = {(S, A): 3, (S, B): 0, (A, T): 3, (B, T): 0, (A, B): 0}

Step 4: Update Residual Graph

The residual capacities are updated as follows:

  • Residual Capacity of (S, A): 5 - 3 = 2
  • Residual Capacity of (A, T): 3 - 3 = 0

Step 5: Find Augmenting Path Again

In the next iteration, we find a new augmenting path: S B T. The minimum residual capacity is 2 (the capacity of the edge S B).

Step 6: Augment Flow

We augment the flow by 2 along the path S B T:

flow = {(S, A): 3, (S, B): 2, (A, T): 3, (B, T): 2, (A, B): 0}

Step 7: Update Residual Graph

Update the residual capacities:

  • Residual Capacity of (S, B): 4 - 2 = 2
  • Residual Capacity of (B, T): 2 - 2 = 0

Step 8: Termination

At this point, no more augmenting paths exist, and the algorithm terminates. The maximum flow in the network is the total flow that has been augmented from the source to the sink. In this case, the maximum flow is 5 (3 + 2).

Complete Python Implementation

Following is the complete Python implementation of the Ford-Fulkerson algorithm −

class Graph:
   def __init__(self, vertices):
      self.V = vertices
      self.graph = {}

   def add_edge(self, u, v, w):
      if u not in self.graph:
         self.graph[u] = {}
      self.graph[u][v] = w
      if v not in self.graph:
	     # Ensure that v is also represented in the graph
         self.graph[v] = {}  

   def bfs(self, source, sink, parent):
      # Include all vertices in visited, even those with no outgoing edges
      visited = {key: False for key in self.graph}
      visited[source] = True
      queue = [source]

      while queue:
         u = queue.pop(0)

         for v in self.graph[u]:
            if not visited[v] and self.graph[u][v] > 0:
               queue.append(v)
               visited[v] = True
               parent[v] = u
               if v == sink:
                  return True
      return False

   def ford_fulkerson(self, source, sink):
      parent = {}
      max_flow = 0

      while self.bfs(source, sink, parent):
         path_flow = float("Inf")
         s = sink
         while s != source:
            path_flow = min(path_flow, self.graph[parent[s]][s])
            s = parent[s]

         max_flow += path_flow
         v = sink
         while v != source:
            u = parent[v]
            self.graph[u][v] -= path_flow
            if v not in self.graph:
               self.graph[v] = {}
            self.graph[v][u] = self.graph[v].get(u, 0) + path_flow
            v = parent[v]

      return max_flow


# Create a graph and add edges
g = Graph(6)
g.add_edge('S', 'A', 10)
g.add_edge('S', 'B', 5)
g.add_edge('A', 'T', 10)
g.add_edge('B', 'T', 5)
g.add_edge('A', 'B', 15)

# Find the maximum flow
max_flow = g.ford_fulkerson('S', 'T')
print("The maximum possible flow is", max_flow)

Following is the output obtained −

The maximum possible flow is 15

Complexity Analysis

The time complexity of the Ford-Fulkerson algorithm depends on the method used to find augmenting paths:

  • Naive Implementation: The time complexity is O(max_flow * E), where E is the number of edges in the graph, and max_flow is the maximum flow in the network.
  • Edmonds-Karp Implementation: When using breadth-first search (BFS) to find augmenting paths, the time complexity is O(V * E2), where V is the number of vertices and E is the number of edges.

The space complexity is O(V + E) due to the storage required for the residual graph and the parent array.

Advertisements