FIFO Push Relabel Algorithm
The push–relabel algorithm (alternatively, pre flow–push algorithm) is an algorithm for computing maximum flows in a flow network. Push-relabel algorithms work in a more localized manner than the Ford Fulkerson method. Rather than examine the entire residual network to find an augmenting path, push-relabel algorithms work on one vertex at a time, looking only at the vertex’s neighbors in the residual network.
Intuition
The push relabels algorithm can be understood in terms of fluid flows. All vertex represent pipe junctions and all directed edges represent pipes having a specific capacity. Each vertex has two special properties. They contain a reservoir to store any excess flow and the vertex along with the reservoir are placed on a platform at a specific height.
We can push flow only downhill, that is, from a vertex at a greater height to one at a lower height. Initially, the source is at a height of V (number of vertices) and the sink is at a height of 0. The height of all the other vertices is 0 and increases as the algorithm progresses. First, we send as much flow as we can from the source to all of its intermediate vertices, where it is stored in their reservoir.
Now all the intermediate vertices of the source are overflowing. We can not push flow forward since they will be located at a height 0. In order to send flow forward, we must increase their height by 1.
The process continues until we reach the sink.
In the end, we empty all the excess fluid stored in any of the vertex reservoirs (if any) by sending flow back to the source. The flow obtained then will be maximum flow.
Operations
- Push: Push operation is applied on an overflowing vertex to push the flow forward. The algorithm finds adjacent vertices of u which are at a lower height than u. For each such adjacent vertex, it sends the maximum possible flow, which is the minimum of excess flow at u and capacity of the edge connecting u with v.
- Relabel: Relabel operation is applied on an overflowing vertex u to increase its height. The algorithm finds adjacent vertex v of u which has the minimum height. It then updates
*** QuickLaTeX cannot compile formula: *** Error message: Error: Nothing to show, formula is empty
Algorithm
The generic push-relabel algorithm uses an initialize–pre flow function. The function is presented below followed by the algorithm.
Initialize-Preflow
1. initialize height and excess flow of every vertex to 0
2. set height of source to number of vertices
3. initialize flow of each edge to 0
4. for each adjacent vertex of source, set its flow and excess flow to capacity of edge connecting them
Generic-Push-Relabel
1. Initialize-Preflow
2. while there exists an applicable push or relabel operation
3. select an applicable push or relabel operation and perform it
4. return flow
FIFO Push-Relabel vs Push-Relabel
FIFO push relabel is an optimization in the original push relabel. Instead of spending linear time finding the overflowing vertex, we organise all the overflowing vertices in a queue. Doing so allows us to find an overflowing vertex in constant time. This reduces the time complexity to O(V3) from O(V2E).
Implementation of FIFO Push-Relabel Algorithm
Example Problem Statement: In below code, a directed graph has been encapsulated in a class DirectedGraph which has been implemented first. It contains a nested class Vertex which encapsulates the edge of the graph. Consider the graph given below –
The edge from 0 -> 1 will be encapsulated by a vertex object V(1, 3) where 1 is the destination and 3 is the weight. It will be stored at index 0 of adjacency matrix. Here is the adjacency matrix of the above graph.
0: [[1, 3], [3, 4]] 1: [[2, 1]] 2: [[3, 2]] 3: []
C
#include <stdio.h> #include <stdbool.h> #include <string.h> #include <limits.h> #define N 6 int min( int a, int b) { return a < b ? a : b; } bool bfs( int rGraph[N][N], int s, int t, int parent[]) { bool visited[N]; memset (visited, 0, sizeof (visited)); visited[s] = true ; parent[s] = -1; int queue[N]; int front = 0, rear = 0; queue[rear++] = s; while (front < rear) { int u = queue[front++]; for ( int v = 0; v < N; v++) { if (!visited[v] && rGraph[u][v] > 0) { visited[v] = true ; parent[v] = u; queue[rear++] = v; } } } return visited[t]; } int fordFulkerson( int graph[N][N], int s, int t) { int rGraph[N][N]; for ( int u = 0; u < N; u++) for ( int v = 0; v < N; v++) rGraph[u][v] = graph[u][v]; int parent[N]; int max_flow = 0; while (bfs(rGraph, s, t, parent)) { int path_flow = INT_MAX; for ( int v = t; v != s; v = parent[v]) { int u = parent[v]; path_flow = min(path_flow, rGraph[u][v]); } for ( int v = t; v != s; v = parent[v]) { int u = parent[v]; rGraph[u][v] -= path_flow; rGraph[v][u] += path_flow; } max_flow += path_flow; } return max_flow; } int main() { int graph[N][N] = { {0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0} }; int s = 0, t = 5; printf ( "Max Flow: %d\n" , fordFulkerson(graph, s, t)); return 0; } |
C++
#include <bits/stdc++.h> using namespace std; class MaxFlow { public : MaxFlow( int n) : n(n), adj(n), cap(n, vector< int >(n)), flow(n, vector< int >(n)) {} void add_edge( int u, int v, int c) { adj[u].push_back(v); adj[v].push_back(u); cap[u][v] += c; } int max_flow( int s, int t) { int total_flow = 0; while ( true ) { vector< int > parent(n, -1); queue<pair< int , int >> q; parent[s] = s; q.push({s, INT_MAX}); while (!q.empty()) { int u = q.front().first, f = q.front().second; q.pop(); for ( int v : adj[u]) { if (parent[v] == -1 && cap[u][v] > flow[u][v]) { parent[v] = u; int new_f = min(f, cap[u][v] - flow[u][v]); if (v == t) { total_flow += new_f; augment_path(s, t, new_f, parent); break ; } q.push({v, new_f}); } } } if (parent[t] == -1) break ; } return total_flow; } private : int n; vector<vector< int >> adj, cap, flow; void augment_path( int s, int v, int f, vector< int >& parent) { if (v == s) return ; augment_path(s, parent[v], f, parent); flow[parent[v]][v] += f; flow[v][parent[v]] -= f; } }; int main() { int n = 6; MaxFlow mf(n); mf.add_edge(0, 1, 16); mf.add_edge(0, 2, 13); mf.add_edge(1, 2, 10); mf.add_edge(2, 1, 4); mf.add_edge(1, 3, 12); mf.add_edge(3, 2, 9); mf.add_edge(2, 4, 14); mf.add_edge(4, 3, 7); mf.add_edge(3, 5, 20); mf.add_edge(4, 5, 4); int s = 0, t = 5; int max_flow = mf.max_flow(s, t); cout << "Max flow: " << max_flow << endl; return 0; } |
Java
import java.util.ArrayList; import java.util.LinkedList; // DirectedGraph class explained above class DirectedGraph { public static class Vertex { // number of the end vertex // weight or capacity // associated with the edge Integer i; Integer w; public Vertex(Integer i, Integer w) { this .i = i; this .w = w; } } final ArrayList<ArrayList<Vertex> > adjacencyList; int vertices; public DirectedGraph( int vertices) { this .vertices = vertices; adjacencyList = new ArrayList<>(vertices); for ( int i = 0 ; i < vertices; i++) adjacencyList.add( new ArrayList<>()); } public void addEdge(Integer u, Integer v, Integer weight) { adjacencyList.get(u) .add( new Vertex(v, weight)); } boolean hasEdge( int u, int v) { if (u >= vertices) return false ; for (Vertex vertex : adjacencyList.get(u)) if (vertex.i == v) return true ; return false ; } // Returns null if no edge // is found between u and v DirectedGraph.Vertex getEdge( int u, int v) { for (DirectedGraph.Vertex vertex : adjacencyList.get(u)) if (vertex.i == v) return vertex; return null ; } } public class MaxFlow { private final int source; private final int sink; private final DirectedGraph graph; private DirectedGraph residualGraph; public MaxFlow(DirectedGraph graph, int source, int sink) { this .graph = graph; this .source = source; this .sink = sink; } private void initResidualGraph() { residualGraph = new DirectedGraph(graph.vertices); // Construct residual graph for ( int u = 0 ; u < graph.vertices; u++) { for (DirectedGraph.Vertex v : graph.adjacencyList.get(u)) { // If forward edge already // exists, update its weight if (residualGraph.hasEdge(u, v.i)) residualGraph.getEdge(u, v.i).w += v.w; // In case it does not // exist, create one else residualGraph.addEdge(u, v.i, v.w); // If backward edge does // not already exist, add it if (!residualGraph.hasEdge(v.i, u)) residualGraph.addEdge(v.i, u, 0 ); } } } public int FIFOPushRelabel() { initResidualGraph(); LinkedList<Integer> queue = new LinkedList<>(); // Step 1: Initialize pre-flow // to store excess flow int [] e = new int [graph.vertices]; // to store height of vertices int [] h = new int [graph.vertices]; boolean [] inQueue = new boolean [graph.vertices]; // set the height of source to V h = graph.vertices; // send maximum flow possible // from source to all its adjacent vertices for (DirectedGraph.Vertex v : graph.adjacencyList.get(source)) { residualGraph.getEdge(source, v.i).w = 0 ; residualGraph.getEdge(v.i, source).w = v.w; // update excess flow e[v.i] = v.w; if (v.i != sink) { queue.add(v.i); inQueue[v.i] = true ; } } // Step 2: Update the pre-flow // while there remains an applicable // push or relabel operation while (!queue.isEmpty()) { // vertex removed from // queue in constant time int u = queue.removeFirst(); inQueue[u] = false ; relabel(u, h); push(u, e, h, queue, inQueue); } return e[sink]; } private void relabel( int u, int [] h) { int minHeight = Integer.MAX_VALUE; for (DirectedGraph.Vertex v : residualGraph.adjacencyList.get(u)) { if (v.w > 0 ) minHeight = Math.min(h[v.i], minHeight); } h[u] = minHeight + 1 ; } private void push( int u, int [] e, int [] h, LinkedList<Integer> queue, boolean [] inQueue) { for (DirectedGraph.Vertex v : residualGraph.adjacencyList.get(u)) { // after pushing flow if // there is no excess flow, // then break if (e[u] == 0 ) break ; // push more flow to // the adjacent v if possible if (v.w > 0 && h[v.i] < h[u]) { // flow possible int f = Math.min(e[u], v.w); v.w -= f; residualGraph.getEdge(v.i, u).w += f; e[u] -= f; e[v.i] += f; // add the new overflowing // immediate vertex to queue if (!inQueue[v.i] && v.i != source && v.i != sink) { queue.add(v.i); inQueue[v.i] = true ; } } } // if after sending flow to all the // intermediate vertices, the // vertex is still overflowing. // add it to queue again if (e[u] != 0 ) { queue.add(u); inQueue[u] = true ; } } public static void main(String[] args) { final int vertices = 6 ; final int source = 0 ; final int sink = 5 ; DirectedGraph dg = new DirectedGraph(vertices); dg.addEdge( 0 , 1 , 16 ); dg.addEdge( 0 , 2 , 13 ); dg.addEdge( 1 , 2 , 10 ); dg.addEdge( 2 , 1 , 4 ); dg.addEdge( 1 , 3 , 12 ); dg.addEdge( 3 , 2 , 9 ); dg.addEdge( 2 , 4 , 14 ); dg.addEdge( 4 , 5 , 4 ); dg.addEdge( 4 , 3 , 7 ); dg.addEdge( 3 , 5 , 20 ); MaxFlow maxFlow = new MaxFlow( dg, source, sink); System.out.println( "Max flow: " + maxFlow.FIFOPushRelabel()); } } |
C#
using System; using System.Collections.Generic; // DirectedGraph class explained above class DirectedGraph { public class Vertex { // number of the end vertex // weight or capacity // associated with the edge public int i; public int w; public Vertex( int i, int w) { this .i = i; this .w = w; } } readonly public List<List<Vertex> > adjacencyList; public int vertices; public DirectedGraph( int vertices) { this .vertices = vertices; adjacencyList = new List<List<Vertex> >(vertices); for ( int i = 0; i < vertices; i++) adjacencyList.Add( new List<Vertex>()); } public void addEdge( int u, int v, int weight) { adjacencyList[u] .Add( new Vertex(v, weight)); } public bool hasEdge( int u, int v) { if (u >= vertices) return false ; foreach (Vertex vertex in adjacencyList[u]) if (vertex.i == v) return true ; return false ; } // Returns null if no edge // is found between u and v public DirectedGraph.Vertex getEdge( int u, int v) { foreach (DirectedGraph.Vertex vertex in adjacencyList[u]) if (vertex.i == v) return vertex; return null ; } } public class MaxFlow { private readonly int source; private readonly int sink; private readonly DirectedGraph graph; private DirectedGraph residualGraph; MaxFlow(DirectedGraph graph, int source, int sink) { this .graph = graph; this .source = source; this .sink = sink; } private void initResidualGraph() { residualGraph = new DirectedGraph(graph.vertices); // Construct residual graph for ( int u = 0; u < graph.vertices; u++) { foreach (DirectedGraph.Vertex v in graph.adjacencyList[u]) { // If forward edge already // exists, update its weight if (residualGraph.hasEdge(u, v.i)) residualGraph.getEdge(u, v.i).w += v.w; // In case it does not // exist, create one else residualGraph.addEdge(u, v.i, v.w); // If backward edge does // not already exist, add it if (!residualGraph.hasEdge(v.i, u)) residualGraph.addEdge(v.i, u, 0); } } } public int FIFOPushRelabel() { initResidualGraph(); List< int > queue = new List< int >(); // Step 1: Initialize pre-flow // to store excess flow int [] e = new int [graph.vertices]; // to store height of vertices int [] h = new int [graph.vertices]; bool [] inQueue = new bool [graph.vertices]; // set the height of source to V h = graph.vertices; // send maximum flow possible // from source to all its adjacent vertices foreach (DirectedGraph.Vertex v in graph.adjacencyList) { residualGraph.getEdge(source, v.i).w = 0; residualGraph.getEdge(v.i, source).w = v.w; // update excess flow e[v.i] = v.w; if (v.i != sink) { queue.Add(v.i); inQueue[v.i] = true ; } } // Step 2: Update the pre-flow // while there remains an applicable // push or relabel operation while (queue.Count!=0) { // vertex removed from // queue in constant time int u = queue[0]; queue.RemoveAt(0); inQueue[u] = false ; relabel(u, h); push(u, e, h, queue, inQueue); } return e[sink]; } private void relabel( int u, int [] h) { int minHeight = int .MaxValue; foreach (DirectedGraph.Vertex v in residualGraph.adjacencyList[u]) { if (v.w > 0) minHeight = Math.Min(h[v.i], minHeight); } h[u] = minHeight + 1; } private void push( int u, int [] e, int [] h, List< int > queue, bool [] inQueue) { foreach (DirectedGraph.Vertex v in residualGraph.adjacencyList[u]) { // after pushing flow if // there is no excess flow, // then break if (e[u] == 0) break ; // push more flow to // the adjacent v if possible if (v.w > 0 && h[v.i] < h[u]) { // flow possible int f = Math.Min(e[u], v.w); v.w -= f; residualGraph.getEdge(v.i, u).w += f; e[u] -= f; e[v.i] += f; // add the new overflowing // immediate vertex to queue if (!inQueue[v.i] && v.i != source && v.i != sink) { queue.Add(v.i); inQueue[v.i] = true ; } } } // if after sending flow to all the // intermediate vertices, the // vertex is still overflowing. // add it to queue again if (e[u] != 0) { queue.Add(u); inQueue[u] = true ; } } public static void Main(String[] args) { int vertices = 6; int source = 0; int sink = 5; DirectedGraph dg = new DirectedGraph(vertices); dg.addEdge(0, 1, 16); dg.addEdge(0, 2, 13); dg.addEdge(1, 2, 10); dg.addEdge(2, 1, 4); dg.addEdge(1, 3, 12); dg.addEdge(3, 2, 9); dg.addEdge(2, 4, 14); dg.addEdge(4, 5, 4); dg.addEdge(4, 3, 7); dg.addEdge(3, 5, 20); MaxFlow maxFlow = new MaxFlow( dg, source, sink); Console.WriteLine( "Max flow: " + maxFlow.FIFOPushRelabel()); } } // This code is contributed by 29AjayKumar |
Python3
class Vertex: def __init__( self , i, w): # number of the end vertex # weight or capacity # associated with the edge self .i = i self .w = w class DirectedGraph: def __init__( self , vertices): self .vertices = vertices self .adjacencyList = [[] for i in range (vertices)] def addEdge( self , u, v, weight): self .adjacencyList[u].append(Vertex(v, weight)) def hasEdge( self , u, v): if u > = self .vertices: return False for vertex in self .adjacencyList[u]: if vertex.i = = v: return True return False def getEdge( self , u, v): for vertex in self .adjacencyList[u]: if vertex.i = = v: return vertex return None class MaxFlow: def __init__( self , graph, source, sink): self .graph = graph self .source = source self .sink = sink def initResidualGraph( self ): self .residualGraph = DirectedGraph( self .graph.vertices) for u in range ( self .graph.vertices): for v in self .graph.adjacencyList[u]: if self .residualGraph.hasEdge(u, v.i): self .residualGraph.getEdge(u, v.i).w + = v.w else : self .residualGraph.addEdge(u, v.i, v.w) if not self .residualGraph.hasEdge(v.i, u): self .residualGraph.addEdge(v.i, u, 0 ) def FIFOPushRelabel( self ): self .initResidualGraph() queue = [] e = [ 0 ] * self .graph.vertices h = [ 0 ] * self .graph.vertices inQueue = [ False ] * self .graph.vertices h[ self .source] = self .graph.vertices for v in self .graph.adjacencyList[ self .source]: self .residualGraph.getEdge( self .source, v.i).w = 0 self .residualGraph.getEdge(v.i, self .source).w = v.w e[v.i] = v.w if v.i ! = self .sink: queue.append(v.i) inQueue[v.i] = True # Step 2: Update the pre-flow # while there remains an applicable # push or relabel operation while queue: #vertex removed from # queue in constant time u = queue.pop( 0 ) inQueue[u] = False self .relabel(u, h) self .push(u, e, h, queue, inQueue) return e[ self .sink] def relabel( self , u, h): minHeight = float ( "inf" ) for v in self .residualGraph.adjacencyList[u]: if v.w > 0 : minHeight = min (minHeight, h[v.i]) h[u] = minHeight + 1 def push( self , u, e, h, queue, in_queue): for v in self .residualGraph.adjacencyList[u]: # after pushing flow if # there is no excess flow, # then break if e[u] = = 0 : break # push more flow to # the adjacent v if possible if v.w > 0 and h[v.i] < h[u]: # flow possible f = min (e[u], v.w) v.w - = f self .residualGraph.getEdge(v.i, u).w + = f e[u] - = f e[v.i] + = f # add the new overflowing # immediate vertex to queue if not in_queue[v.i] and v.i ! = self .source and v.i ! = self .sink: queue.append(v.i) in_queue[v.i] = True # if after sending flow to all the # intermediate vertices, the # vertex is still overflowing. # add it to queue again if e[u] ! = 0 : queue.append(u) in_queue[u] = True vertices = 6 source = 0 sink = 5 dg = DirectedGraph(vertices) dg.addEdge( 0 , 1 , 16 ) dg.addEdge( 0 , 2 , 13 ) dg.addEdge( 1 , 2 , 10 ) dg.addEdge( 2 , 1 , 4 ) dg.addEdge( 1 , 3 , 12 ) dg.addEdge( 3 , 2 , 9 ) dg.addEdge( 2 , 4 , 14 ) dg.addEdge( 4 , 5 , 4 ) dg.addEdge( 4 , 3 , 7 ) dg.addEdge( 3 , 5 , 20 ) maxFlow = MaxFlow(dg, source, sink) print ( "Max flow:" , maxFlow.FIFOPushRelabel()) |
Javascript
// JavaScript code implementation: class Vertex { // number of the end vertex // weight or capacity // associated with the edge constructor(i, w) { this .i = i; this .w = w; } } // DirectedGraph class explained above class DirectedGraph { constructor(vertices) { this .vertices = vertices; this .adjacencyList = new Array(vertices); for (let i = 0; i < vertices; i++) { this .adjacencyList[i] = new Array(); } } addEdge(u, v, weight) { this .adjacencyList[u].push( new Vertex(v, weight)); } hasEdge(u, v) { if (u >= this .vertices) { return false ; } for (const vertex of this .adjacencyList[u]) { if (vertex.i === v) { return true ; } } return false ; } // Returns null if no edge // is found between u and v getEdge(u, v) { for (const vertex of this .adjacencyList[u]) { if (vertex.i === v) { return vertex; } } return null ; } } class MaxFlow { constructor(graph, source, sink) { this .graph = graph; this .source = source; this .sink = sink; } initResidualGraph() { this .residualGraph = new DirectedGraph( this .graph.vertices); // Construct residual graph for (let u = 0; u < this .graph.vertices; u++) { for (const v of this .graph.adjacencyList[u]) { // If forward edge already // exists, update its weight // In case it does not // exist, create one const residualCapacity = v.w - ( this .residualGraph.getEdge(u, v.i) || { w: 0 }).w; this .residualGraph.addEdge(u, v.i, residualCapacity); // If backward edge does // not already exist, add it this .residualGraph.addEdge(v.i, u, 0); } } } FIFOPushRelabel() { this .initResidualGraph(); const queue = []; // Step 1: Initialize pre-flow // to store excess flow const e = new Array( this .graph.vertices).fill(0); // to store height of vertices const h = new Array( this .graph.vertices).fill(0); const inQueue = new Array( this .graph.vertices).fill( false ); // set the height of source to V h[ this .source] = this .graph.vertices; // send maximum flow possible source // to all its adjacent vertices for (const v of this .graph.adjacencyList[ this .source]) { this .residualGraph.getEdge( this .source, v.i).w = 0; this .residualGraph.getEdge(v.i, this .source).w = v.w; // update excess flow e[v.i] = v.w; if (v.i !== this .sink) { queue.push(v.i); inQueue[v.i] = true ; } } // Step 2: Update the pre-flow // while there remains an applicable // push or relabel operation while (queue.length > 0) { // vertex removed from // queue in constant time const u = queue.shift(); inQueue[u] = false ; this .relabel(u, h); this .push(u, e, h, queue, inQueue); } return e[ this .sink]; } relabel(u, h) { let minHeight = Number.MAX_VALUE; for (const v of this .residualGraph.adjacencyList[u]) { if (v.w > 0) { minHeight = Math.min(h[v.i], minHeight); } } h[u] = minHeight + 1; } push(u, e, h, queue, inQueue) { for (const v of this .residualGraph.adjacencyList[u]) { // after pushing flow if // there is no excess flow, // then break if (e[u] === 0) { break ; } // push more flow to // adjacent v if possible if (v.w > 0 && h[v.i] < h[u]) { // flow possible const f = Math.min(e[u], v.w); v.w -= f; this .residualGraph.getEdge(v.i, u).w += f; e[u] -= f; e[v.i] += f; // add the new overflowing // immediate vertex to queue if (!inQueue[v.i] && v.i !== this .source && v.i !== this .sink) { queue.push(v.i); inQueue[v.i] = true ; } } } // if after sending flow to all the // intermediate vertices, the // vertex is still overflowing. // add it to queue again if (e[u] !== 0) { queue.push(u); inQueue[u] = true ; } } } const vertices = 6; const source = 0; const sink = 5; const dg = new DirectedGraph(vertices); dg.addEdge(0, 1, 16); dg.addEdge(0, 2, 13); dg.addEdge(1, 2, 10); dg.addEdge(2, 1, 4); dg.addEdge(1, 3, 12); dg.addEdge(3, 2, 9); dg.addEdge(2, 4, 14); dg.addEdge(4, 5, 4); dg.addEdge(4, 3, 7); dg.addEdge(3, 5, 20); const maxFlow = new MaxFlow(dg, source, sink); conole.log( "Max flow: " + maxFlow.FIFOPushRelabel()); // This code is contributed by sankar. |
Max flow: 23
Time Complexity: O(V3)
Please Login to comment...