import edu.princeton.cs.introcs.In; import edu.princeton.cs.introcs.StdOut; /************************************************************************* * Compilation: javac AssignmentProblem.java * Execution: java AssignmentProblem N * Dependencies: DijkstraSP.java DirectedEdge.java * * Solve an N-by-N assignment problem in N^3 log N time using the * successive shortest path algorithm. * * Remark: could use dense version of Dijsktra's algorithm for * improved theoretical efficiency of N^3, but it doesn't seem to * help in practice. * * Assumes N-by-N cost matrix is nonnegative. * * *********************************************************************/ public class AssignmentProblem { private static final int UNMATCHED = -1; private int N; // number of rows and columns private double[][] weight; // the N-by-N cost matrix private double[] px; // px[i] = dual variable for row i private double[] py; // py[j] = dual variable for col j private int[] xy; // xy[i] = j means i-j is a match private int[] yx; // yx[j] = i means i-j is a match public AssignmentProblem(double[][] weight) { N = weight.length; this.weight = new double[N][N]; for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) this.weight[i][j] = weight[i][j]; // dual variables px = new double[N]; py = new double[N]; // initial matching is empty xy = new int[N]; yx = new int[N]; for (int i = 0; i < N; i++) xy[i] = UNMATCHED; for (int j = 0; j < N; j++) yx[j] = UNMATCHED; // add N edges to matching for (int k = 0; k < N; k++) { assert isDualFeasible(); assert isComplementarySlack(); augment(); } assert check(); } // find shortest augmenting path and upate private void augment() { // build residual graph EdgeWeightedDigraph G = new EdgeWeightedDigraph(2*N+2); int s = 2*N, t = 2*N+1; for (int i = 0; i < N; i++) { if (xy[i] == UNMATCHED) G.addEdge(new DirectedEdge(s, i, 0.0)); } for (int j = 0; j < N; j++) { if (yx[j] == UNMATCHED) G.addEdge(new DirectedEdge(N+j, t, py[j])); } for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (xy[i] == j) G.addEdge(new DirectedEdge(N+j, i, 0.0)); else G.addEdge(new DirectedEdge(i, N+j, reduced(i, j))); } } // compute shortest path from s to every other vertex DijkstraSP spt = new DijkstraSP(G, s); // augment along alternating path for (DirectedEdge e : spt.pathTo(t)) { int i = e.from(), j = e.to() - N; if (i < N) { xy[i] = j; yx[j] = i; } } // update dual variables for (int i = 0; i < N; i++) px[i] += spt.distTo(i); for (int j = 0; j < N; j++) py[j] += spt.distTo(N+j); } // reduced cost of i-j private double reduced(int i, int j) { return weight[i][j] + px[i] - py[j]; } // dual variable for row i public double dualRow(int i) { return px[i]; } // dual variable for column j public double dualCol(int j) { return py[j]; } // total weight of min weight perfect matching public double weight() { double total = 0.0; for (int i = 0; i < N; i++) { if (xy[i] != UNMATCHED) total += weight[i][xy[i]]; } return total; } public int sol(int i) { return xy[i]; } // check that dual variables are feasible private boolean isDualFeasible() { // check that all edges have >= 0 reduced cost for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (reduced(i, j) < 0) { StdOut.println("Dual variables are not feasible"); return false; } } } return true; } // check that primal and dual variables are complementary slack private boolean isComplementarySlack() { // check that all matched edges have 0-reduced cost for (int i = 0; i < N; i++) { if ((xy[i] != UNMATCHED) && (reduced(i, xy[i]) != 0)) { StdOut.println("Primal and dual variables are not complementary slack"); return false; } } return true; } // check that primal variables are a perfect matching private boolean isPerfectMatching() { // check that xy[] is a perfect matching boolean[] perm = new boolean[N]; for (int i = 0; i < N; i++) { if (perm[xy[i]]) { StdOut.println("Not a perfect matching"); return false; } perm[xy[i]] = true; } // check that xy[] and yx[] are inverses for (int j = 0; j < N; j++) { if (xy[yx[j]] != j) { StdOut.println("xy[] and yx[] are not inverses"); return false; } } for (int i = 0; i < N; i++) { if (yx[xy[i]] != i) { StdOut.println("xy[] and yx[] are not inverses"); return false; } } return true; } // check optimality conditions private boolean check() { return isPerfectMatching() && isDualFeasible() && isComplementarySlack(); } public static void main(String[] args) { In in = new In(args[0]); int N = in.readInt(); double[][] weight = new double[N][N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { weight[i][j] = in.readDouble(); } } AssignmentProblem assignment = new AssignmentProblem(weight); StdOut.println("weight = " + assignment.weight()); for (int i = 0; i < N; i++) StdOut.println(i + "-" + assignment.sol(i) + "' " + weight[i][assignment.sol(i)]); for (int i = 0; i < N; i++) StdOut.println("px[" + i + "] = " + assignment.dualRow(i)); for (int j = 0; j < N; j++) StdOut.println("py[" + j + "] = " + assignment.dualCol(j)); for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) StdOut.println("reduced[" + i + "-" + j + "] = " + assignment.reduced(i, j)); } }