private static int RunStep4(float[,] costs, bool[] rowsCovered, bool[] colsCovered, int w, int h)
        {
            if (costs == null)
            {
                throw new ArgumentNullException("Costs are null");
            }

            if (rowsCovered == null)
            {
                throw new ArgumentNullException("Rows covered are null");
            }

            if (colsCovered == null)
            {
                throw new ArgumentNullException("Cols covered are null");
            }

            var minValue = HungarianAlgorithm.FindMinimum(costs, rowsCovered, colsCovered, w, h);

            for (var i = 0; i < h; i++)
            {
                for (var j = 0; j < w; j++)
                {
                    if (rowsCovered[i])
                    {
                        costs[i, j] += minValue;
                    }
                    if (!colsCovered[j])
                    {
                        costs[i, j] -= minValue;
                    }
                }
            }
            return(2);
        }
        private static int RunStep2(float[,] costs, byte[,] masks, bool[] rowsCovered, bool[] colsCovered, int w, int h, ref Location pathStart)
        {
            if (costs == null)
            {
                throw new ArgumentNullException("Costs is null");
            }

            if (masks == null)
            {
                throw new ArgumentNullException("Masks are null");
            }

            if (rowsCovered == null)
            {
                throw new ArgumentNullException("Rows covered are null");
            }

            if (colsCovered == null)
            {
                throw new ArgumentNullException("Cols covered are null");
            }

            while (true)
            {
                var loc = HungarianAlgorithm.FindZero(costs, rowsCovered, colsCovered, w, h);
                if (loc.row == -1)
                {
                    return(4);
                }

                masks[loc.row, loc.column] = 2;

                var starCol = HungarianAlgorithm.FindStarInRow(masks, w, loc.row);
                if (starCol != -1)
                {
                    rowsCovered[loc.row] = true;
                    colsCovered[starCol] = false;
                }
                else
                {
                    pathStart = loc;
                    return(3);
                }
            }
        }
        private static int RunStep3(byte[,] masks, bool[] rowsCovered, bool[] colsCovered, int w, int h, Location[] path, Location pathStart)
        {
            if (masks == null)
            {
                throw new ArgumentNullException("Masks are null");
            }

            if (rowsCovered == null)
            {
                throw new ArgumentNullException("Rows covered are null");
            }

            if (colsCovered == null)
            {
                throw new ArgumentNullException("Cols covered are null");
            }

            var pathIndex = 0;

            path[0] = pathStart;

            while (true)
            {
                var row = HungarianAlgorithm.FindStarInColumn(masks, h, path[pathIndex].column);
                if (row == -1)
                {
                    break;
                }

                pathIndex++;
                path[pathIndex] = new Location(row, path[pathIndex - 1].column);

                var col = HungarianAlgorithm.FindPrimeInRow(masks, w, path[pathIndex].row);

                pathIndex++;
                path[pathIndex] = new Location(path[pathIndex - 1].row, col);
            }

            HungarianAlgorithm.ConvertPath(masks, path, pathIndex + 1);
            HungarianAlgorithm.ClearCovers(rowsCovered, colsCovered, w, h);
            HungarianAlgorithm.ClearPrimes(masks, w, h);

            return(1);
        }
        /// <summary>
        /// Finds the optimal assignments for a given matrix of agents and costed tasks such that the total cost is minimized.
        /// </summary>
        /// <param name="costs">A cost matrix; the element at row <em>i</em> and column <em>j</em> represents the cost of agent <em>i</em> performing task <em>j</em>.</param>
        /// <returns>A matrix of assignments; the value of element <em>i</em> is the column of the task assigned to agent <em>i</em>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="costs"/> is null.</exception>
        public static int[] FindAssignments(this float[,] costs)
        {
            if (costs == null)
            {
                throw new ArgumentNullException("Costs are null");
            }

            var h = costs.GetLength(0);
            var w = costs.GetLength(1);

            for (var i = 0; i < h; i++)
            {
                var min = float.MaxValue;

                for (var j = 0; j < w; j++)
                {
                    min = Math.Min(min, costs[i, j]);
                }

                for (var j = 0; j < w; j++)
                {
                    costs[i, j] -= min;
                }
            }

            var masks       = new byte[h, w];
            var rowsCovered = new bool[h];
            var colsCovered = new bool[w];

            for (var i = 0; i < h; i++)
            {
                for (var j = 0; j < w; j++)
                {
                    if (costs[i, j] == 0 && !rowsCovered[i] && !colsCovered[j])
                    {
                        masks[i, j]    = 1;
                        rowsCovered[i] = true;
                        colsCovered[j] = true;
                    }
                }
            }

            HungarianAlgorithm.ClearCovers(rowsCovered, colsCovered, w, h);

            var path      = new Location[w * h];
            var pathStart = default(Location);
            var step      = 1;

            while (step != -1)
            {
                switch (step)
                {
                case 1:
                    step = HungarianAlgorithm.RunStep1(masks, colsCovered, w, h);
                    break;

                case 2:
                    step = HungarianAlgorithm.RunStep2(costs, masks, rowsCovered, colsCovered, w, h, ref pathStart);
                    break;

                case 3:
                    step = HungarianAlgorithm.RunStep3(masks, rowsCovered, colsCovered, w, h, path, pathStart);
                    break;

                case 4:
                    step = HungarianAlgorithm.RunStep4(costs, rowsCovered, colsCovered, w, h);
                    break;
                }
            }

            var agentsTasks = new int[h];

            for (var i = 0; i < h; i++)
            {
                for (var j = 0; j < w; j++)
                {
                    if (masks[i, j] == 1)
                    {
                        agentsTasks[i] = j;
                        break;
                    }
                }
            }

            return(agentsTasks);
        }