Advertisement
Guest User

Untitled

a guest
Oct 17th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 14.07 KB | None | 0 0
  1. import java.util.Arrays;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import scala.Tuple2;
  5.  
  6. /* Copyright (c) 2012 Kevin L. Stern
  7.  *
  8.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  9.  * of this software and associated documentation files (the "Software"), to deal
  10.  * in the Software without restriction, including without limitation the rights
  11.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12.  * copies of the Software, and to permit persons to whom the Software is
  13.  * furnished to do so, subject to the following conditions:
  14.  *
  15.  * The above copyright notice and this permission notice shall be included in
  16.  * all copies or substantial portions of the Software.
  17.  *
  18.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  24.  * SOFTWARE.
  25.  */
  26.  
  27. /**
  28.  * An implementation of the Hungarian algorithm for solving the assignment problem. An instance of
  29.  * the assignment problem consists of a number of workers along with a number of jobs and a cost
  30.  * matrix which gives the cost of assigning the i'th worker to the j'th job at position (i, j). The
  31.  * goal is to find an assignment of workers to jobs so that no job is assigned more than one worker
  32.  * and so that no worker is assigned to more than one job in such a manner so as to minimize the
  33.  * total cost of completing the jobs. <p>
  34.  *
  35.  * An assignment for a cost matrix that has more workers than jobs will necessarily include
  36.  * unassigned workers, indicated by an assignment value of -1; in no other circumstance will there
  37.  * be unassigned workers. Similarly, an assignment for a cost matrix that has more jobs than workers
  38.  * will necessarily include unassigned jobs; in no other circumstance will there be unassigned jobs.
  39.  * For completeness, an assignment for a square cost matrix will give exactly one unique worker to
  40.  * each job. <p>
  41.  *
  42.  * This version of the Hungarian algorithm runs in time O(n^3), where n is the maximum among the
  43.  * number of workers and the number of jobs.
  44.  *
  45.  * @author Kevin L. Stern
  46.  */
  47. public class HungarianAlgorithm {
  48.  
  49.   private final Map<Tuple2<Integer, Integer>, Double> costMatrix;
  50.   private final int rows, cols, dim;
  51.   private final double[] labelByWorker, labelByJob;
  52.   private final int[] minSlackWorkerByJob;
  53.   private final double[] minSlackValueByJob;
  54.   private final int[] matchJobByWorker, matchWorkerByJob;
  55.   private final int[] parentWorkerByCommittedJob;
  56.   private final boolean[] committedWorkers;
  57.  
  58.   /**
  59.    * Construct an instance of the algorithm.
  60.    *
  61.    * @param costMatrix the cost matrix, where matrix[i][j] holds the cost of assigning worker i to
  62.    * job j, for all i, j. The cost matrix must not be irregular in the sense that all rows must be
  63.    * the same length; in addition, all entries must be non-infinite numbers.
  64.    */
  65.   public HungarianAlgorithm(double[][] costMatrix) {
  66.     this.dim = Math.max(costMatrix.length, costMatrix[0].length);
  67.     this.rows = costMatrix.length;
  68.     this.cols = costMatrix[0].length;
  69.     this.costMatrix = new HashMap<>();
  70. //    this.costMatrix = new double[this.dim][this.dim];
  71.     for (int w = 0; w < this.dim; w++) {
  72.       if (w < costMatrix.length) {
  73.         if (costMatrix[w].length != this.cols) {
  74.           throw new IllegalArgumentException("Irregular cost matrix");
  75.         }
  76.         for (int j = 0; j < this.cols; j++) {
  77.           if (Double.isInfinite(costMatrix[w][j])) {
  78.             throw new IllegalArgumentException("Infinite cost");
  79.           }
  80.           if (Double.isNaN(costMatrix[w][j])) {
  81.             throw new IllegalArgumentException("NaN cost");
  82.           }
  83.           if(costMatrix[w][j] != 0.) {
  84.             this.costMatrix.put(new Tuple2<>(w, j), costMatrix[w][j]);
  85.           }
  86.         }
  87.       } else {
  88.         System.err.println("ERROR HERE");
  89.         System.exit(1);
  90.       }
  91.     }
  92.     labelByWorker = new double[this.dim];
  93.     labelByJob = new double[this.dim];
  94.     minSlackWorkerByJob = new int[this.dim];
  95.     minSlackValueByJob = new double[this.dim];
  96.     committedWorkers = new boolean[this.dim];
  97.     parentWorkerByCommittedJob = new int[this.dim];
  98.     matchJobByWorker = new int[this.dim];
  99.     Arrays.fill(matchJobByWorker, -1);
  100.     matchWorkerByJob = new int[this.dim];
  101.     Arrays.fill(matchWorkerByJob, -1);
  102.   }
  103.  
  104.   public HungarianAlgorithm(Map<Tuple2<Integer, Integer>, Double> sim, int rows, int cols) {
  105.     this.dim = Math.max(rows, cols);
  106.     this.rows = rows;
  107.     this.cols = cols;
  108.     this.costMatrix = new HashMap<>(sim);
  109.     labelByWorker = new double[this.dim];
  110.     labelByJob = new double[this.dim];
  111.     minSlackWorkerByJob = new int[this.dim];
  112.     minSlackValueByJob = new double[this.dim];
  113.     committedWorkers = new boolean[this.dim];
  114.     parentWorkerByCommittedJob = new int[this.dim];
  115.     matchJobByWorker = new int[this.dim];
  116.     Arrays.fill(matchJobByWorker, -1);
  117.     matchWorkerByJob = new int[this.dim];
  118.     Arrays.fill(matchWorkerByJob, -1);
  119.   }
  120.  
  121.   /**
  122.    * Compute an initial feasible solution by assigning zero labels to the workers and by assigning
  123.    * to each job a label equal to the minimum cost among its incident edges.
  124.    */
  125.   protected void computeInitialFeasibleSolution() {
  126.     for (int j = 0; j < dim; j++) {
  127.       labelByJob[j] = Double.POSITIVE_INFINITY;
  128.     }
  129.     for (int w = 0; w < dim; w++) {
  130.       for (int j = 0; j < dim; j++) {
  131.         if (costMatrix.getOrDefault(new Tuple2<>(w, j), Double.MAX_VALUE) < labelByJob[j]) {
  132.           labelByJob[j] = costMatrix.getOrDefault(new Tuple2<>(w, j), Double.MAX_VALUE);
  133.         }
  134. //        if (costMatrix[w][j] < labelByJob[j]) {
  135. //          labelByJob[j] = costMatrix[w][j];
  136. //        }
  137.       }
  138.     }
  139.   }
  140.  
  141.   /**
  142.    * Execute the algorithm.
  143.    *
  144.    * @return the minimum cost matching of workers to jobs based upon the provided cost matrix. A
  145.    * matching value of -1 indicates that the corresponding worker is unassigned.
  146.    */
  147.   public int[] execute() {
  148.     /*
  149.      * Heuristics to improve performance: Reduce rows and columns by their
  150.      * smallest element, compute an initial non-zero dual feasible solution and
  151.      * create a greedy matching from workers to jobs of the cost matrix.
  152.      */
  153.     reduce();
  154.     computeInitialFeasibleSolution();
  155.     greedyMatch();
  156.  
  157.     int w = fetchUnmatchedWorker();
  158.     while (w < dim) {
  159.       initializePhase(w);
  160.       executePhase();
  161.       w = fetchUnmatchedWorker();
  162.     }
  163.     int[] result = Arrays.copyOf(matchJobByWorker, rows);
  164.     for (w = 0; w < result.length; w++) {
  165.       if (result[w] >= cols) {
  166.         result[w] = -1;
  167.       }
  168.     }
  169.     return result;
  170.   }
  171.  
  172.   /**
  173.    * Execute a single phase of the algorithm. A phase of the Hungarian algorithm consists of
  174.    * building a set of committed workers and a set of committed jobs from a root unmatched worker by
  175.    * following alternating unmatched/matched zero-slack edges. If an unmatched job is encountered,
  176.    * then an augmenting path has been found and the matching is grown. If the connected zero-slack
  177.    * edges have been exhausted, the labels of committed workers are increased by the minimum slack
  178.    * among committed workers and non-committed jobs to create more zero-slack edges (the labels of
  179.    * committed jobs are simultaneously decreased by the same amount in order to maintain a feasible
  180.    * labeling). <p>
  181.    *
  182.    * The runtime of a single phase of the algorithm is O(n^2), where n is the dimension of the
  183.    * internal square cost matrix, since each edge is visited at most once and since increasing the
  184.    * labeling is accomplished in time O(n) by maintaining the minimum slack values among
  185.    * non-committed jobs. When a phase completes, the matching will have increased in size.
  186.    */
  187.   protected void executePhase() {
  188.     while (true) {
  189.       int minSlackWorker = -1, minSlackJob = -1;
  190.       double minSlackValue = Double.POSITIVE_INFINITY;
  191.       for (int j = 0; j < dim; j++) {
  192.         if (parentWorkerByCommittedJob[j] == -1) {
  193.           if (minSlackValueByJob[j] < minSlackValue) {
  194.             minSlackValue = minSlackValueByJob[j];
  195.             minSlackWorker = minSlackWorkerByJob[j];
  196.             minSlackJob = j;
  197.           }
  198.         }
  199.       }
  200.       if (minSlackValue > 0) {
  201.         updateLabeling(minSlackValue);
  202.       }
  203.       parentWorkerByCommittedJob[minSlackJob] = minSlackWorker;
  204.       if (matchWorkerByJob[minSlackJob] == -1) {
  205.         /*
  206.          * An augmenting path has been found.
  207.          */
  208.         int committedJob = minSlackJob;
  209.         int parentWorker = parentWorkerByCommittedJob[committedJob];
  210.         while (true) {
  211.           int temp = matchJobByWorker[parentWorker];
  212.           match(parentWorker, committedJob);
  213.           committedJob = temp;
  214.           if (committedJob == -1) {
  215.             break;
  216.           }
  217.           parentWorker = parentWorkerByCommittedJob[committedJob];
  218.         }
  219.         return;
  220.       } else {
  221.         /*
  222.          * Update slack values since we increased the size of the committed
  223.          * workers set.
  224.          */
  225.         int worker = matchWorkerByJob[minSlackJob];
  226.         committedWorkers[worker] = true;
  227.         for (int j = 0; j < dim; j++) {
  228.           if (parentWorkerByCommittedJob[j] == -1) {
  229.             double slack = costMatrix.getOrDefault(new Tuple2<>(worker, j), Double.MAX_VALUE)
  230.                 - labelByWorker[worker]
  231.                 - labelByJob[j];
  232. //            double slack = costMatrix[worker][j] - labelByWorker[worker]
  233. //                - labelByJob[j];
  234.             if (minSlackValueByJob[j] > slack) {
  235.               minSlackValueByJob[j] = slack;
  236.               minSlackWorkerByJob[j] = worker;
  237.             }
  238.           }
  239.         }
  240.       }
  241.     }
  242.   }
  243.  
  244.   /**
  245.    * @return the first unmatched worker or {@link #dim} if none.
  246.    */
  247.   protected int fetchUnmatchedWorker() {
  248.     int w;
  249.     for (w = 0; w < dim; w++) {
  250.       if (matchJobByWorker[w] == -1) {
  251.         break;
  252.       }
  253.     }
  254.     return w;
  255.   }
  256.  
  257.   /**
  258.    * Find a valid matching by greedily selecting among zero-cost matchings. This is a heuristic to
  259.    * jump-start the augmentation algorithm.
  260.    */
  261.   protected void greedyMatch() {
  262.     for (int w = 0; w < dim; w++) {
  263.       for (int j = 0; j < dim; j++) {
  264.         if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1
  265.             && costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE) - labelByWorker[w] - labelByJob[j] == 0) {
  266. //        if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1
  267. //            && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) {
  268.           match(w, j);
  269.         }
  270.       }
  271.     }
  272.   }
  273.  
  274.   /**
  275.    * Initialize the next phase of the algorithm by clearing the committed workers and jobs sets and
  276.    * by initializing the slack arrays to the values corresponding to the specified root worker.
  277.    *
  278.    * @param w the worker at which to root the next phase.
  279.    */
  280.   protected void initializePhase(int w) {
  281.     Arrays.fill(committedWorkers, false);
  282.     Arrays.fill(parentWorkerByCommittedJob, -1);
  283.     committedWorkers[w] = true;
  284.     for (int j = 0; j < dim; j++) {
  285.       minSlackValueByJob[j] = costMatrix.getOrDefault(new Tuple2<>(w, j), Double.MAX_VALUE) - labelByWorker[w]
  286.           - labelByJob[j];
  287. //      minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w]
  288. //          - labelByJob[j];
  289.       minSlackWorkerByJob[j] = w;
  290.     }
  291.   }
  292.  
  293.   /**
  294.    * Helper method to record a matching between worker w and job j.
  295.    */
  296.   protected void match(int w, int j) {
  297.     matchJobByWorker[w] = j;
  298.     matchWorkerByJob[j] = w;
  299.   }
  300.  
  301.   /**
  302.    * Reduce the cost matrix by subtracting the smallest element of each row from all elements of the
  303.    * row as well as the smallest element of each column from all elements of the column. Note that
  304.    * an optimal assignment for a reduced cost matrix is optimal for the original cost matrix.
  305.    */
  306.   protected void reduce() {
  307.     for (int w = 0; w < dim; w++) {
  308.       double min = Double.POSITIVE_INFINITY;
  309.       for (int j = 0; j < dim; j++) {
  310.         if (costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE) < min) {
  311.           min = costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE);
  312.         }
  313. //        if (costMatrix[w][j] < min) {
  314. //          min = costMatrix[w][j];
  315. //        }
  316.       }
  317.       for (int j = 0; j < dim; j++) {
  318.         costMatrix.put(new Tuple2<>(w, j), costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE) - min);
  319. //        costMatrix[w][j] -= min;
  320.       }
  321.     }
  322.     double[] min = new double[dim];
  323.     for (int j = 0; j < dim; j++) {
  324.       min[j] = Double.POSITIVE_INFINITY;
  325.     }
  326.     for (int w = 0; w < dim; w++) {
  327.       for (int j = 0; j < dim; j++) {
  328.         if (costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE) < min[j]) {
  329.           min[j] = costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE);
  330.         }
  331. //        if (costMatrix[w][j] < min[j]) {
  332. //          min[j] = costMatrix[w][j];
  333. //        }
  334.       }
  335.     }
  336.     for (int w = 0; w < dim; w++) {
  337.       for (int j = 0; j < dim; j++) {
  338.         costMatrix.put(new Tuple2<>(w, j), costMatrix.getOrDefault(new Tuple2<>(w,j), Double.MAX_VALUE) - min[j]);
  339. //        costMatrix[w][j] -= min[j];
  340.       }
  341.     }
  342.   }
  343.  
  344.   /**
  345.    * Update labels with the specified slack by adding the slack value for committed workers and by
  346.    * subtracting the slack value for committed jobs. In addition, update the minimum slack values
  347.    * appropriately.
  348.    */
  349.   protected void updateLabeling(double slack) {
  350.     for (int w = 0; w < dim; w++) {
  351.       if (committedWorkers[w]) {
  352.         labelByWorker[w] += slack;
  353.       }
  354.     }
  355.     for (int j = 0; j < dim; j++) {
  356.       if (parentWorkerByCommittedJob[j] != -1) {
  357.         labelByJob[j] -= slack;
  358.       } else {
  359.         minSlackValueByJob[j] -= slack;
  360.       }
  361.     }
  362.   }
  363. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement