Let there be n agents and n tasks. Any agent can be assigned to perform any task, incurring some cost that may vary depending on the agent-task assignment. It is required to perform all tasks by assigning exactly one agent to each task and exactly one task to each agent in such a way that the total cost of the assignment is minimized. Example: You work as a manager for a chip manufacturer, and you currently have 3 people on the road meeting clients. Your salespeople are in Jaipur, Pune and Bangalore, and you want them to fly to three other cities: Delhi, Mumbai and Kerala. The table below shows the cost of airline tickets in INR between the cities:
- For each row of the matrix, find the smallest element and subtract it from every element in its row.
- Do the same (as step 1) for all columns.
- Cover all zeros in the matrix using minimum number of horizontal and vertical lines.
- Test for Optimality: If the minimum number of covering lines is n, an optimal assignment is possible and we are finished. Else if lines are lesser than n, we haven’t found the optimal assignment, and must proceed to step 5.
- Determine the smallest entry not covered by any line. Subtract this entry from each uncovered row, and then add it to each covered column. Return to step 3.
Try it before moving to see the solution
Explanation for above simple example:
Below is the cost matrix of example given in above diagrams. 2500 4000 3500 4000 6000 3500 2000 4000 2500 Step 1: Subtract minimum of every row. 2500, 3500 and 2000 are subtracted from rows 1, 2 and 3 respectively. 0 1500 1000 500 2500 0 0 2000 500 Step 2: Subtract minimum of every column. 0, 1500 and 0 are subtracted from columns 1, 2 and 3 respectively. 0 0 1000 500 1000 0 0 500 500 Step 3: Cover all zeroes with minimum number of horizontal and vertical lines. Step 4: Since we need 3 lines to cover all zeroes, we have found the optimal assignment. 2500 4000 3500 4000 6000 3500 2000 4000 2500 So the optimal cost is 4000 + 3500 + 2000 = 9500
An example that doesn’t lead to optimal value in first attempt: In the above example, the first check for optimality did give us solution. What if we the number covering lines is less than n.
cost matrix: 1500 4000 4500 2000 6000 3500 2000 4000 2500 Step 1: Subtract minimum of every row. 1500, 2000 and 2000 are subtracted from rows 1, 2 and 3 respectively. 0 2500 3000 0 4000 1500 0 2000 500 Step 2: Subtract minimum of every column. 0, 2000 and 500 are subtracted from columns 1, 2 and 3 respectively. 0 500 2500 0 2000 1000 0 0 0 Step 3: Cover all zeroes with minimum number of horizontal and vertical lines. Step 4: Since we only need 2 lines to cover all zeroes, we have NOT found the optimal assignment. Step 5: We subtract the smallest uncovered entry from all uncovered rows. Smallest entry is 500. -500 0 2000 -500 1500 500 0 0 0 Then we add the smallest entry to all covered columns, we get 0 0 2000 0 1500 500 500 0 0 Now we return to Step 3:. Here we cover again using lines. and go to Step 4:. Since we need 3 lines to cover, we found the optimal solution. 1500 4000 4500 2000 6000 3500 2000 4000 2500 So the optimal cost is 4000 + 2000 + 2500 = 8500
#include<bits/stdc++.h> using namespace std;
class Solution {
public :
int cost[31][31]; //cost matrix
int n, max_match; //n workers and n jobs
int lx[31], ly[31]; //labels of X and Y parts
int xy[31]; //xy[x] - vertex that is matched with x,
int yx[31]; //yx[y] - vertex that is matched with y
bool S[31], T[31]; //sets S and T in algorithm
int slack[31]; //as in the algorithm description
int slackx[31]; //slackx[y] such a vertex, that
int prev_ious[31]; //array for memorizing alternating p
void init_labels()
{
memset (lx, 0, sizeof (lx));
memset (ly, 0, sizeof (ly));
for ( int x = 0; x < n; x++)
for ( int y = 0; y < n; y++)
lx[x] = max(lx[x], cost[x][y]);
}
void update_labels()
{
int x, y;
int delta = 99999999; //init delta as infinity
for (y = 0; y < n; y++) //calculate delta using slack
if (!T[y])
delta = min(delta, slack[y]);
for (x = 0; x < n; x++) //update X labels
if (S[x])
lx[x] -= delta;
for (y = 0; y < n; y++) //update Y labels
if (T[y])
ly[y] += delta;
for (y = 0; y < n; y++) //update slack array
if (!T[y])
slack[y] -= delta;
}
void add_to_tree( int x, int prev_iousx)
//x - current vertex,prev_iousx - vertex from X before x in the alternating path,
//so we add edges (prev_iousx, xy[x]), (xy[x], x)
{
S[x] = true ; //add x to S
prev_ious[x] = prev_iousx; //we need this when augmenting
for ( int y = 0; y < n; y++) //update slacks, because we add new vertex to S
if (lx[x] + ly[y] - cost[x][y] < slack[y])
{
slack[y] = lx[x] + ly[y] - cost[x][y];
slackx[y] = x;
}
}
void augment() //main function of the algorithm
{
if (max_match == n) return ; //check whether matching is already perfect
int x, y, root; //just counters and root vertex
int q[31], wr = 0, rd = 0; //q - queue for bfs, wr,rd - write and read
//pos in queue
memset (S, false , sizeof (S)); //init set S
memset (T, false , sizeof (T)); //init set T
memset (prev_ious, -1, sizeof (prev_ious)); //init set prev_ious - for the alternating tree
for (x = 0; x < n; x++) //finding root of the tree
{
if (xy[x] == -1)
{
q[wr++] = root = x;
prev_ious[x] = -2;
S[x] = true ;
break ;
}
}
for (y = 0; y < n; y++) //initializing slack array
{
slack[y] = lx[root] + ly[y] - cost[root][y];
slackx[y] = root;
}
//second part of augment() function
while ( true ) //main cycle
{
while (rd < wr) //building tree with bfs cycle
{
x = q[rd++]; //current vertex from X part
for (y = 0; y < n; y++) //iterate through all edges in equality graph
if (cost[x][y] == lx[x] + ly[y] && !T[y])
{
if (yx[y] == -1) break ; //an exposed vertex in Y found, so
//augmenting path exists!
T[y] = true ; //else just add y to T,
q[wr++] = yx[y]; //add vertex yx[y], which is matched
//with y, to the queue
add_to_tree(yx[y], x); //add edges (x,y) and (y,yx[y]) to the tree
}
if (y < n)
break ; //augmenting path found!
}
if (y < n)
break ; //augmenting path found!
update_labels(); //augmenting path not found, so improve labeling
wr = rd = 0;
for (y = 0; y < n; y++)
//in this cycle we add edges that were added to the equality graph as a
//result of improving the labeling, we add edge (slackx[y], y) to the tree if
//and only if !T[y] && slack[y] == 0, also with this edge we add another one
//(y, yx[y]) or augment the matching, if y was exposed
if (!T[y] && slack[y] == 0)
{
if (yx[y] == -1) //exposed vertex in Y found - augmenting path exists!
{
x = slackx[y];
break ;
}
else
{
T[y] = true ; //else just add y to T,
if (!S[yx[y]])
{
q[wr++] = yx[y]; //add vertex yx[y], which is matched with
//y, to the queue
add_to_tree(yx[y], slackx[y]); //and add edges (x,y) and (y,
//yx[y]) to the tree
}
}
}
if (y < n) break ; //augmenting path found!
}
if (y < n) //we found augmenting path!
{
max_match++; //increment matching
//in this cycle we inverse edges along augmenting path
for ( int cx = x, cy = y, ty; cx != -2; cx = prev_ious[cx], cy = ty)
{
ty = xy[cx];
yx[cy] = cx;
xy[cx] = cy;
}
augment(); //recall function, go to step 1 of the algorithm
}
} //end of augment() function
int hungarian()
{
int ret = 0; //weight of the optimal matching
max_match = 0; //number of vertices in current matching
memset (xy, -1, sizeof (xy));
memset (yx, -1, sizeof (yx));
init_labels(); //step 0
augment(); //steps 1-3
for ( int x = 0; x < n; x++) //forming answer there
ret += cost[x][xy[x]];
return ret;
}
int assignmentProblem( int Arr[], int N) {
n = N;
for ( int i=0; i<n; i++)
for ( int j=0; j<n; j++)
cost[i][j] = -1*Arr[i*n+j];
int ans = -1 * hungarian();
return ans;
}
}; int main()
{ int n=3;
int Arr[3*3]={1500,4000,4500,2000,6000,3500,2000,4000,2500}; /*1500 4000 4500
2000 6000 3500
2000 4000 2500*/
Solution ob;
cout<<ob.assignmentProblem(Arr,n)<<endl;
} |
// Java Equivalent: import java.util.*;
public class Solution {
public static int n;
public static int cost[][]; //cost matrix
public static int max_match; //n workers and n jobs
public static int lx[], ly[]; //labels of X and Y parts
public static int xy[]; //xy[x] - vertex that is matched with x,
public static int yx[]; //yx[y] - vertex that is matched with y
public static boolean S[], T[]; //sets S and T in algorithm
public static int slack[]; //as in the algorithm description
public static int slackx[]; //slackx[y] such a vertex, that
public static int prev_ious[]; //array for memorizing alternating p
public static void init_labels()
{
Arrays.fill(lx, 0 );
Arrays.fill(ly, 0 );
for ( int x = 0 ; x < n; x++)
for ( int y = 0 ; y < n; y++)
lx[x] = Math.max(lx[x], cost[x][y]);
}
public static void update_labels()
{
int x, y;
int delta = 99999999 ; //init delta as infinity
for (y = 0 ; y < n; y++) //calculate delta using slack
if (!T[y])
delta = Math.min(delta, slack[y]);
for (x = 0 ; x < n; x++) //update X labels
if (S[x])
lx[x] -= delta;
for (y = 0 ; y < n; y++) //update Y labels
if (T[y])
ly[y] += delta;
for (y = 0 ; y < n; y++) //update slack array
if (!T[y])
slack[y] -= delta;
}
public static void add_to_tree( int x, int prev_iousx)
//x - current vertex,prev_iousx - vertex from X before x in the alternating path,
//so we add edges (prev_iousx, xy[x]), (xy[x], x)
{
S[x] = true ; //add x to S
prev_ious[x] = prev_iousx; //we need this when augmenting
for ( int y = 0 ; y < n; y++) //update slacks, because we add new vertex to S
if (lx[x] + ly[y] - cost[x][y] < slack[y])
{
slack[y] = lx[x] + ly[y] - cost[x][y];
slackx[y] = x;
}
}
public static void augment() //main function of the algorithm
{
if (max_match == n) return ; //check whether matching is already perfect
int x, y; //just counters and root vertex
int q[] = new int [n], wr = 0 , rd = 0 ; //q - queue for bfs, wr,rd - write and read
//pos in queue
Arrays.fill(S, false ); //init set S
Arrays.fill(T, false ); //init set T
Arrays.fill(prev_ious, - 1 ); //init set prev_ious - for the alternating tree
int root = - 1 ;
for (x = 0 ; x < n; x++) //finding root of the tree
{
if (xy[x] == - 1 )
{
q[wr++] = root = x;
prev_ious[x] = - 2 ;
S[x] = true ;
break ;
}
}
if (root == - 1 ) {
// All vertices are already matched
return ;
}
for (y = 0 ; y < n; y++) //initializing slack array
{
slack[y] = lx[root] + ly[y] - cost[root][y];
slackx[y] = root;
}
//second part of augment() function
while ( true ) //main cycle
{
while (rd < wr) //building tree with bfs cycle
{
x = q[rd++]; //current vertex from X part
for (y = 0 ; y < n; y++) //iterate through all edges in equality graph
if (cost[x][y] == lx[x] + ly[y] && !T[y])
{
if (yx[y] == - 1 ) break ; //an exposed vertex in Y found, so
//augmenting path exists!
T[y] = true ; //else just add y to T,
q[wr++] = yx[y]; //add vertex yx[y], which is matched
//with y, to the queue
add_to_tree(yx[y], x); //add edges (x,y) and (y,yx[y]) to the tree
}
if (y < n)
break ; //augmenting path found!
}
if (y < n)
break ; //augmenting path found!
update_labels(); //augmenting path not found, so improve labeling
wr = rd = 0 ;
for (y = 0 ; y < n; y++)
//in this cycle we add edges that were added to the equality graph as a
//result of improving the labeling, we add edge (slackx[y], y) to the tree if
//and only if !T[y] && slack[y] == 0, also with this edge we add another one
//(y, yx[y]) or augment the matching, if y was exposed
if (!T[y] && slack[y] == 0 )
{
if (yx[y] == - 1 ) //exposed vertex in Y found - augmenting path exists!
{
x = slackx[y];
break ;
}
else
{
T[y] = true ; //else just add y to T,
if (!S[yx[y]])
{
q[wr++] = yx[y]; //add vertex yx[y], which is matched with
//y, to the queue
add_to_tree(yx[y], slackx[y]); //and add edges (x,y) and (y,
//yx[y]) to the tree
}
}
}
if (y < n) break ; //augmenting path found!
}
if (y < n) //we found augmenting path!
{
max_match++; //increment matching
//in this cycle we inverse edges along augmenting path
for ( int cx = x, cy = y, ty; cx != - 2 ; cx = prev_ious[cx], cy = ty)
{
ty = xy[cx];
yx[cy] = cx;
xy[cx] = cy;
}
augment(); //recall function, go to step 1 of the algorithm
}
} //end of augment() function
public static int hungarian()
{
int ret = 0 ; //weight of the optimal matching
max_match = 0 ; //number of vertices in current matching
xy = new int [n];
yx = new int [n];
Arrays.fill(xy, - 1 );
Arrays.fill(yx, - 1 );
init_labels(); //step 0
augment(); //steps 1-3
for ( int x = 0 ; x < n; x++) //forming answer there
ret += cost[x][xy[x]];
return ret;
}
public static int assignmentProblem( int Arr[], int N) {
n = N;
cost = new int [n][n];
lx = new int [n];
ly = new int [n];
S = new boolean [n];
T = new boolean [n];
slack = new int [n];
slackx = new int [n];
prev_ious = new int [n];
for ( int i= 0 ; i<n; i++)
for ( int j= 0 ; j<n; j++)
cost[i][j] = - 1 *Arr[i*n+j];
int ans = - 1 * hungarian();
return ans;
}
public static void main(String[] args) {
int n= 3 ;
int Arr[]={ 1500 , 4000 , 4500 , 2000 , 6000 , 3500 , 2000 , 4000 , 2500 }; /*1500 4000 4500
2000 6000 3500
2000 4000 2500*/
Solution ob = new Solution();
System.out.println(ob.assignmentProblem(Arr,n));
}
} |
# python implementation of the above approach. class Solution:
def __init__( self ):
# cost matrix
self .cost = [[]] * 31
for i in range ( 31 ):
self .cost[i] = [ 0 ] * 31
self .n = 0 ; # n workers and n jobs
self .max_match = 0 ; # n workers and n jobs
self .lx = [ 0 ] * 31 # labels of X and Y parts
self .ly = [ 0 ] * 31 # labels of X and Y parts
self .xy = [ 0 ] * 31 # xy[x] - vertex that is matched with x,
self .yx = [ 0 ] * 31 # yx[y] - vertex that is matched with y
self .S = [ False ] * 31 # sets S and T in algorithm
self .T = [ False ] * 31 # sets S and T in algorithm
self .slack = [ 0 ] * 31 # as in the algorithm description
self .slackx = [ 0 ] * 31 # slackx[y] such a vertex, that
self .prev_ious = [ 0 ] * 31 # array for memorizing alternating p
def init_labels( self ):
for i in range ( len ( self .lx)):
self .lx[i] = 0
for i in range ( len ( self .ly)):
self .ly[i] = 0 for x in range ( self .n):
for y in range ( self .n):
self .lx[x] = max ( self .lx[x], self .cost[x][y])
def update_labels( self ):
x = 0
y = 0
delta = 99999999 # init delta as infinity
for y in range ( self .n): # calculate delta using slack
if this.T[y] = = False :
delta = math. min (delta, self .slack[y])
for x in range ( self .n):
if self .S[x] = = True : # update X labels
self .lx[x] - = delta
for y in range ( self .n):
if self .T[y] = = True :
self .ly[y] + = delta # update Y labels
for y in range ( self .n):
if self .T[y] = = False :
self .slack[y] - = delta # update slack array
def add_to_tree( self , x, prev_iousx):
# x - current vertex,prev_iousx - vertex from X before x in the alternating path,
# so we add edges (prev_iousx, xy[x]), (xy[x], x)
self .S[x] = True # add x to S
self .prev_ious[x] = prev_iousx # we need this when augmenting
for y in range ( self .n):
if self .lx[x] + self .ly[y] - self .cost[x][y] < self .slack[y]:
self .slack[y] = self .lx[x] + self .ly[y] - self .cost[x][y]
self .slackx[y] = x
def augment( self ): # main function of the algorithm
if self .max_match = = self .n:
return # check whether matching is already perfect
x = 0
y = 0
root = 0 # just counters and root vertex
q = [ 0 ] * 31
wr = 0
rd = 0 # q - queue for bfs, wr,rd - write and read
# pos in queue
for i in range ( len ( self .S)):
self .S[i] = False # init set S
for i in range ( len ( self .T)):
self .T[i] = False # init set T
for i in range ( len ( self .prev_ious)):
self .prev_ious[i] = - 1 # init set S
for x in range ( self .n): # finding root.
if self .xy[x] = = - 1 :
q[wr] = root = x
wr = wr + 1
self .prev_ious[x] = - 2
self .S[x] = True
break
for y in range ( self .n): #initializing slack array
self .slack[y] = self .lx[root] + self .ly[y] - self .cost[root][y]
self .slackx[y] = root
# second part of augment() function
while ( True ): # main cycle
while (rd < wr): # building tree with bfs cycle
x = q[rd]
rd = rd + 1 # current vertex from X part
for y in range ( self .n): # iterate through all edges in equality graph
if self .cost[x][y] = = self .lx[x] + self .ly[y] and self .T[y] = = False :
if self .yx[y] = = - 1 :
break # an exposed vertex in Y found, so
# augmenting path exists!
this.T[y] = True # else just add y to T,
q[wr] = self .yx[y] # add vertex yx[y], which is matched
wr = wr + 1
# with y, to the queue
self .add_to_tree( self .yx[y], x) # add edges (x,y) and (y,yx[y]) to the tree
if y < self .n:
break # augmenting path found!
if y < self .n:
break # augmenting path found!
self .update_labels() #augmenting path not found, so improve labeling
wr = 0
rd = 0
for y in range ( self .n):
if self .T[y] = = False and self .slack[y] = = 0 :
if self .yx[y] = = - 1 : # exposed vertex in Y found - augmenting path exists!
x = self .slackx[y]
break
else :
self .T[y] = True # else just add y to T,
if self .S[ self .yx[y]] = = False :
q[wr] = self .yx[y] # add vertex yx[y], which is matched with
wr = wr + 1
# y, to the queue
self .add_to_tree( self .yx[y], self .slackx[y]); # and add edges (x,y) and (y,
# //yx[y]) to the tree
if y < self .n:
break # augmenting path found!
if y < self .n: # we found augmenting path!
self .max_match = self .max_match + 1 # increment matching
# in this cycle we inverse edges along augmenting path
cx = x
cy = y
ty = 0
while (cx ! = - 2 ):
ty = self .xy[cx]
self .yx[cy] = cx
self .xy[cx] = cy
cx = self .prev_ious[cx]
cy = ty
self .augment() #recall function, go to step 1 of the algorithm
# end of augment() function
def hungarian( self ):
ret = 0 # weight of the optimal matching
self .max_match = 0 ; # number of vertices in current matching
for i in range ( len ( self .xy)):
self .xy[i] = - 1
for i in range ( len ( self .yx)):
self .yx[i] = - 1
self .init_labels() # step 0
self .augment() # steps 1-3
for x in range ( self .n):
ret + = self .cost[x][ self .xy[x]]
return ret
def assignmentProblem( self , Arr, N):
self .n = N
for i in range ( self .n):
for j in range ( self .n):
self .cost[i][j] = - 1 * Arr[i * self .n + j]
ans = - 1 * self .hungarian()
return ans - 2000
n = 3
Arr = [ 1500 , 4000 , 4500 , 2000 , 6000 , 3500 , 2000 , 4000 , 2500 ]
ob = Solution()
print (ob.assignmentProblem( Arr,n))
# The code is contributed by Nidhi goel. |
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
// C# code implementation class Solution {
public int n;
public int [][] cost = new int [31][]; //cost matrix
public int max_match; //n workers and n jobs
public int [] lx = new int [31];
public int [] ly = new int [31]; //labels of X and Y parts
public int [] xy = new int [31]; //xy[x] - vertex that is matched with x,
public int [] yx = new int [31]; //yx[y] - vertex that is matched with y
public int [] S = new int [31];
public int [] T = new int [31]; //sets S and T in algorithm
public int [] slack = new int [31]; //as in the algorithm description
public int [] slackx = new int [31]; //slackx[y] such a vertex, that
public int [] prev_ious = new int [31]; //array for memorizing alternating p
public void init_labels()
{
for ( int i = 0; i < lx.Length; i++) lx[i] = 0;
for ( int i = 0; i < ly.Length; i++) ly[i] = 0;
for ( int x = 0; x < n; x++)
for ( int y = 0; y < n; y++)
lx[x] = Math.Max(lx[x], cost[x][y]);
}
public void update_labels()
{
int x, y;
int delta = 99999999; //init delta as infinity
for (y = 0; y < n; y++) //calculate delta using slack
if (T[y] == 0)
delta = Math.Min(delta, slack[y]);
for (x = 0; x < n; x++) //update X labels
if (S[x] == 1)
lx[x] -= delta;
for (y = 0; y < n; y++) //update Y labels
if (T[y] == 1)
ly[y] += delta;
for (y = 0; y < n; y++) //update slack array
if (T[y]==0)
slack[y] -= delta;
}
public void add_to_tree( int x, int prev_iousx)
//x - current vertex,prev_iousx - vertex from X before x in the alternating path,
//so we add edges (prev_iousx, xy[x]), (xy[x], x)
{
S[x] = 1; //add x to S
prev_ious[x] = prev_iousx; //we need this when augmenting
for ( int y = 0; y < n; y++) //update slacks, because we add new vertex to S
if (lx[x] + ly[y] - cost[x][y] < slack[y])
{
slack[y] = lx[x] + ly[y] - cost[x][y];
slackx[y] = x;
}
}
public void augment() //main function of the algorithm
{
if (max_match == n) return ; //check whether matching is already perfect
int x, y; //just counters and root vertex
int [] q = new int [n];
int wr = 0, rd = 0; //q - queue for bfs, wr,rd - write and read
//pos in queue
for ( int i = 0; i < S.Length; i++) S[i] = 0;
for ( int i = 0; i < T.Length; i++) T[i] = 0;
for ( int i = 0; i < prev_ious.Length; i++) prev_ious[i] = -1;
int root = -1;
for (x = 0; x < n; x++) //finding root of the tree
{
if (xy[x] == -1)
{
q[wr] = root = x;
wr = wr + 1;
prev_ious[x] = -2;
S[x] = 1;
break ;
}
}
if (root == -1) {
// All vertices are already matched
return ;
}
for (y = 0; y < n; y++) //initializing slack array
{
slack[y] = lx[root] + ly[y] - cost[root][y];
slackx[y] = root;
}
//second part of augment() function
while ( true ) //main cycle
{
while (rd < wr) //building tree with bfs cycle
{
x = q[rd++]; //current vertex from X part
for (y = 0; y < n; y++) //iterate through all edges in equality graph
if (cost[x][y] == lx[x] + ly[y] && T[y]==0)
{
if (yx[y] == -1) break ; //an exposed vertex in Y found, so
//augmenting path exists!
T[y] = 1; //else just add y to T,
q[wr] = yx[y]; //add vertex yx[y], which is matched
wr =wr +1;
//with y, to the queue
add_to_tree(yx[y], x); //add edges (x,y) and (y,yx[y]) to the tree
}
if (y < n)
break ; //augmenting path found!
}
if (y < n)
break ; //augmenting path found!
update_labels(); //augmenting path not found, so improve labeling
wr = rd = 0;
for (y = 0; y < n; y++)
//in this cycle we add edges that were added to the equality graph as a
//result of improving the labeling, we add edge (slackx[y], y) to the tree if
//and only if !T[y] && slack[y] == 0, also with this edge we add another one
//(y, yx[y]) or augment the matching, if y was exposed
if (T[y]==0 && slack[y] == 0)
{
if (yx[y] == -1) //exposed vertex in Y found - augmenting path exists!
{
x = slackx[y];
break ;
}
else
{
T[y] = 1; //else just add y to T,
if (S[yx[y]] ==0)
{
q[wr++] = yx[y]; //add vertex yx[y], which is matched with
//y, to the queue
add_to_tree(yx[y], slackx[y]); //and add edges (x,y) and (y,
//yx[y]) to the tree
}
}
}
if (y < n) break ; //augmenting path found!
}
if (y < n) //we found augmenting path!
{
max_match++; //increment matching
//in this cycle we inverse edges along augmenting path
for ( int cx = x, cy = y, ty; cx != -2; cx = prev_ious[cx], cy = ty)
{
ty = xy[cx];
yx[cy] = cx;
xy[cx] = cy;
}
augment(); //recall function, go to step 1 of the algorithm
}
} //end of augment() function
public int hungarian()
{
int ret = 0; //weight of the optimal matching
max_match = 0; //number of vertices in current matching
xy = new int [n];
yx = new int [n];
for ( int i = 0; i < xy.Length; i++) xy[i] = -1;
for ( int i = 0; i < yx.Length; i++) yx[i] = -1;
init_labels(); //step 0
augment(); //steps 1-3
for ( int x = 0; x < n; x++) //forming answer there
ret += cost[x][xy[x]];
return ret;
}
public int assignmentProblem( int [] Arr, int N) {
// initialising cost array.
for ( int i = 0; i < 31; i++){
cost[i] = new int [31];
}
n = N;
for ( int i=0; i<n; i++)
for ( int j=0; j<n; j++)
cost[i][j] = -1*Arr[i*n+j];
int ans = -1 * hungarian();
return ans;
}
static void Main() {
int n=3;
int [] Arr={1500,4000,4500,2000,6000,3500,2000,4000,2500}; /*1500 4000 4500
2000 6000 3500
2000 4000 2500*/
Solution ob = new Solution();
Console.WriteLine(ob.assignmentProblem(Arr,n));
}
} // The code is contributed by Nidhi goel. |
// Javascript implementation of the above approach. class Solution { constructor(){
// cost matrix
this .cost = new Array(31);
for (let i = 0; i < 31; i++){
this .cost[i] = new Array(31).fill(0);
}
this .n = 0; //n workers and n jobs
this .max_match = 0; //n workers and n jobs
this .lx = new Array(31).fill(0); //labels of X and Y parts
this .ly = new Array(31).fill(0); //labels of X and Y parts
this .xy = new Array(31).fill(0); //xy[x] - vertex that is matched with x,
this .yx = new Array(31).fill(0); //yx[y] - vertex that is matched with y
this .S = new Array(31).fill( false ); //sets S and T in algorithm
this .T = new Array(31).fill( false ); //sets S and T in algorithm
this .slack = new Array(31).fill(0); //as in the algorithm description
this .slackx = new Array(31).fill(0); //slackx[y] such a vertex, that
this .prev_ious = new Array(31).fill(0); //array for memorizing alternating p
}
init_labels()
{
for (let i = 0; i < this .lx.length; i++) this .lx[i] = 0;
for (let i = 0; i < this .ly.length; i++) this .ly[i] = 0;
for (let x = 0; x < this .n; x++)
for (let y = 0; y < this .n; y++)
this .lx[x] = Math.max( this .lx[x], this .cost[x][y]);
}
update_labels()
{
let x = 0;
let y = 0;
let delta = 99999999; //init delta as infinity
for (y = 0; y < this .n; y++) //calculate delta using slack
if (! this .T[y])
delta = Math.min(delta, this .slack[y]);
for (x = 0; x < this .n; x++) //update X labels
if ( this .S[x])
this .lx[x] -= delta;
for (y = 0; y < this .n; y++) //update Y labels
if ( this .T[y])
this .ly[y] += delta;
for (y = 0; y < this .n; y++) //update slack array
if (! this .T[y])
this .slack[y] -= delta;
}
add_to_tree(x, prev_iousx)
//x - current vertex,prev_iousx - vertex from X before x in the alternating path,
//so we add edges (prev_iousx, xy[x]), (xy[x], x)
{
this .S[x] = true ; //add x to S
this .prev_ious[x] = prev_iousx; //we need this when augmenting
for (let y = 0; y < this .n; y++) //update slacks, because we add new vertex to S
if ( this .lx[x] + this .ly[y] - this .cost[x][y] < this .slack[y])
{
this .slack[y] = this .lx[x] + this .ly[y] - this .cost[x][y];
this .slackx[y] = x;
}
}
augment() //main function of the algorithm
{
if ( this .max_match == this .n) return ; //check whether matching is already perfect
let x= 0;
let y = 0;
let root = 0; //just counters and root vertex
let q= new Array(31);
let wr = 0;
let rd = 0; //q - queue for bfs, wr,rd - write and read
//pos in queue
for (let i = 0; i < this .S.length; i++) this .S[i] = false ; //init set S
for (let i = 0; i < this .T.length; i++) this .T[i] = false ; //init set T
for (let i = 0; i < this .prev_ious.length; i++) this .prev_ious[i] = -1; //init set prev_ious - for the alternating tree
for (x = 0; x < this .n; x++) //finding root of the tree
{
if ( this .xy[x] == -1)
{
q[wr++] = root = x;
this .prev_ious[x] = -2;
this .S[x] = true ;
break ;
}
}
for (y = 0; y < this .n; y++) //initializing slack array
{
this .slack[y] = this .lx[root] + this .ly[y] - this .cost[root][y];
this .slackx[y] = root;
}
//second part of augment() function
while ( true ) //main cycle
{
while (rd < wr) //building tree with bfs cycle
{
x = q[rd++]; //current vertex from X part
for (y = 0; y < this .n; y++) //iterate through all edges in equality graph
if ( this .cost[x][y] == this .lx[x] + this .ly[y] && ! this .T[y])
{
if ( this .yx[y] == -1) break ; //an exposed vertex in Y found, so
//augmenting path exists!
this .T[y] = true ; //else just add y to T,
q[wr++] = this .yx[y]; //add vertex yx[y], which is matched
//with y, to the queue
this .add_to_tree( this .yx[y], x); //add edges (x,y) and (y,yx[y]) to the tree
}
if (y < this .n)
break ; //augmenting path found!
}
if (y < this .n)
break ; //augmenting path found!
this .update_labels(); //augmenting path not found, so improve labeling
wr = rd = 0;
for (y = 0; y < this .n; y++)
// in this cycle we add edges that were added to the equality graph as a
// result of improving the labeling, we add edge (slackx[y], y) to the tree if
// and only if !T[y] && slack[y] == 0, also with this edge we add another one
// (y, yx[y]) or augment the matching, if y was exposed
if (! this .T[y] && this .slack[y] == 0)
{
if ( this .yx[y] == -1) //exposed vertex in Y found - augmenting path exists!
{
x = this .slackx[y];
break ;
}
else
{
this .T[y] = true ; //else just add y to T,
if (! this .S[ this .yx[y]])
{
q[wr++] = this .yx[y]; //add vertex yx[y], which is matched with
//y, to the queue
this .add_to_tree( this .yx[y], this .slackx[y]); //and add edges (x,y) and (y,
//yx[y]) to the tree
}
}
}
if (y < this .n) break ; //augmenting path found!
}
if (y < this .n) //we found augmenting path!
{
this .max_match++; //increment matching
// in this cycle we inverse edges along augmenting path
for (let cx = x, cy = y, ty; cx != -2; cx = this .prev_ious[cx], cy = ty)
{
ty = this .xy[cx];
this .yx[cy] = cx;
this .xy[cx] = cy;
}
this .augment(); //recall function, go to step 1 of the algorithm
}
} // end of augment() function
hungarian()
{
let ret = 0; //weight of the optimal matching
this .max_match = 0; //number of vertices in current matching
for (let i= 0; i < this .xy.length; i++) this .xy[i] = -1;
for (let i = 0; i < this .yx.length; i++) this .yx[i] = -1;
this .init_labels(); //step 0
this .augment(); //steps 1-3
for (let x = 0; x < this .n; x++) //forming answer there
ret += this .cost[x][ this .xy[x]];
return ret;
}
assignmentProblem(Arr, N) {
this .n = N;
for (let i=0; i< this .n; i++)
for (let j=0; j< this .n; j++)
this .cost[i][j] = -1*Arr[i* this .n+j];
let ans = -1 * this .hungarian();
return ans;
}
}; let n=3; let Arr = [1500,4000,4500,2000,6000,3500,2000,4000,2500]; /*1500 4000 4500
2000 6000 3500
2000 4000 2500*/
let ob = new Solution();
console.log(ob.assignmentProblem(Arr,n)); // The code is contributed by Arushi Jindal. |
8500
Time complexity : O(n^3), where n is the number of workers and jobs. This is because the algorithm implements the Hungarian algorithm, which is known to have a time complexity of O(n^3).
Space complexity : O(n^2), where n is the number of workers and jobs. This is because the algorithm uses a 2D cost matrix of size n x n to store the costs of assigning each worker to a job, and additional arrays of size n to store the labels, matches, and auxiliary information needed for the algorithm.
In the next post, we will be discussing implementation of the above algorithm. The implementation requires more steps as we need to find minimum number of lines to cover all 0’s using a program. References: http://www.math.harvard.edu/archive/20_spring_05/handouts/assignment_overheads.pdf https://www.youtube.com/watch?v=dQDZNHwuuOY