多维数组行/列和匹配调整失效求助(附C#实现代码)
矩阵行和列和调整问题
我有三个数组:
- 第一个是元素为杂乱正整数的多维数组(示例为5行6列);
- 第二个是包含6个元素的数组,对应多维数组各列的目标和;
- 第三个是包含5个元素的数组,对应多维数组各行的目标和。
需要编写一个函数,通过偏移多维数组的元素值,使其行和与列和分别匹配第三个数组和第二个数组,且尽量不使用外部库。
我尝试编写了两个分别用于调整列和行的函数,但无法正常工作,恳请协助解决问题。
以下是我编写的C#代码:
private void AdjustColumnOfSqueakyForFutureOnMatrix(decimal[,] matrixOfQtysByDPAndInstance, List<InstanceQtyHolderItem> instanceQtyHolderItems, decimal[] totalByDP, int decimals) { string matrixBeforeAdjust = GetStringViewOfMatrix(matrixOfQtysByDPAndInstance); int mul = 1; for (int i = 0; i < decimals; ++i) { mul *= 10; } decimal unit = 1 / mul; Logger.Info("Start Adjust Column for FutureOnMatrix"); for (int j = 0; j <= matrixOfQtysByDPAndInstance.GetUpperBound(1); j++) { Logger.Info($"Try to Fix column {j}, rapresent element of InstanceQtyHolder idInstance: {instanceQtyHolderItems[j].IdInstance}, QtyHolder {instanceQtyHolderItems[j].QtyHolder}"); do { decimal totalByColumn = SumByColumnOnFutureOnMatrix(matrixOfQtysByDPAndInstance, j); Logger.Info($"Currently we have {totalByColumn} element on column {j}, expected {totalByDP[j]}"); if (totalByColumn == totalByDP[j]) { Logger.Info($"column {j}, correct"); break; } bool isToAdd = totalByDP[j] - totalByColumn > 0; decimal maxValueOfColumn = 0; int rowOfMaxValueOfColumn = -1; for (int ii = 0; ii <= matrixOfQtysByDPAndInstance.GetUpperBound(0); ii++) { if (matrixOfQtysByDPAndInstance[ii, j] > maxValueOfColumn) { maxValueOfColumn = matrixOfQtysByDPAndInstance[ii, j]; rowOfMaxValueOfColumn = ii; } } if (rowOfMaxValueOfColumn == -1) { if (isToAdd) rowOfMaxValueOfColumn = 0; else { Logger.Warn($"In column {j}, there isn't a value that i can swap"); Logger.Warn($"Matrix, at the begin of the function {matrixBeforeAdjust}Matrix, at this moment {GetStringViewOfMatrix(matrixOfQtysByDPAndInstance)}"); break; } } decimal maxValueOfRow = 0; int colOfMaxValueOfRow = -1; for (int jj = j + 1; jj <= matrixOfQtysByDPAndInstance.GetUpperBound(1); jj++) { if(SumByColumnOnFutureOnMatrix(matrixOfQtysByDPAndInstance, jj) != totalByDP[jj]) { if (matrixOfQtysByDPAndInstance[rowOfMaxValueOfColumn, jj] > maxValueOfRow) { maxValueOfRow = matrixOfQtysByDPAndInstance[rowOfMaxValueOfColumn, jj]; colOfMaxValueOfRow = jj; } } } if (colOfMaxValueOfRow == -1) { if (!isToAdd && j < matrixOfQtysByDPAndInstance.GetUpperBound(1)) colOfMaxValueOfRow = j + 1; else { Logger.Warn($"In column {j} there is value on row ({rowOfMaxValueOfColumn}) that i need to swap, but there isn't on another column with same row a value aviable to swap"); Logger.Warn($"Matrix, at the begin of the function {matrixBeforeAdjust}Matrix, at this moment {GetStringViewOfMatrix(matrixOfQtysByDPAndInstance)}"); break; } } matrixOfQtysByDPAndInstance[rowOfMaxValueOfColumn, j] += isToAdd ? unit : -unit; matrixOfQtysByDPAndInstance[rowOfMaxValueOfColumn, colOfMaxValueOfRow] += isToAdd ? -unit : unit; } while(true); } } private void AdjustRowOfSqueakyForFutureOnMatrix(decimal[,] matrixOfQtysByDPAndInstance, List<DeliveryPlanSaveEntity> sortedFutureDeliveryPlans, decimal[] totalByInstance, int decimals) { string matrixBeforeAdjust = GetStringViewOfMatrix(matrixOfQtysByDPAndInstance); int mul = 1; for (int i = 0; i < decimals; ++i) { mul *= 10; } decimal unit = 1 / mul; Logger.Info("Start Adjust Row for FutureOnMatrix"); for (int i = 0; i <= matrixOfQtysByDPAndInstance.GetUpperBound(0); i++) { Logger.Info($"Try to Fix row {i}, rapresent element of DeliveryPlans id: {sortedFutureDeliveryPlans[i].IdDeliveryPlan}, idArticle {sortedFutureDeliveryPlans[i].IdArticle}, week: {sortedFutureDeliveryPlans[i].Week}"); do { decimal totalByRow = SumByRowOnFutureOnMatrix(matrixOfQtysByDPAndInstance, i); Logger.Info($"Currently we have {totalByRow} element on row {i}, expected {totalByInstance[i]}"); if (totalByRow == totalByInstance[i]) { Logger.Info($"row {i}, correct"); break; } bool isToAdd = totalByInstance[i] - totalByRow > 0; decimal maxValueOfRow = 0; int columnOfMaxRowOfColumn = -1; for (int j = 0; j <= matrixOfQtysByDPAndInstance.GetUpperBound(1); j++) { if (matrixOfQtysByDPAndInstance[i, j] > maxValueOfRow) { maxValueOfRow = matrixOfQtysByDPAndInstance[i, j]; columnOfMaxRowOfColumn = j; } } if (columnOfMaxRowOfColumn == -1) { if (isToAdd) columnOfMaxRowOfColumn = 0; else { Logger.Warn($"In row {i}, there isn't a value that i can swap"); Logger.Warn($"Matrix, at the begin of the function {matrixBeforeAdjust}Matrix, at this moment {GetStringViewOfMatrix(matrixOfQtysByDPAndInstance)}"); break; } } decimal maxValueOfColumn = 0; int rowOfMaxValueOfColumn = -1; for (int ii = i + 1; ii <= matrixOfQtysByDPAndInstance.GetUpperBound(0); ii++) { if (SumByRowOnFutureOnMatrix(matrixOfQtysByDPAndInstance, ii) != totalByInstance[ii]) { if (matrixOfQtysByDPAndInstance[ii, columnOfMaxRowOfColumn] > maxValueOfColumn) { maxValueOfColumn = matrixOfQtysByDPAndInstance[ii, columnOfMaxRowOfColumn]; rowOfMaxValueOfColumn = ii; } } } if (rowOfMaxValueOfColumn == -1) { if (!isToAdd && i < matrixOfQtysByDPAndInstance.GetUpperBound(0)) rowOfMaxValueOfColumn = i + 1; else { Logger.Warn($"In row {i} there is value on column ({columnOfMaxRowOfColumn}) that i need to swap, but there isn't on another row with same column a value aviable to swap"); Logger.Warn($"Matrix, at the begin of the function {matrixBeforeAdjust}Matrix, at this moment {GetStringViewOfMatrix(matrixOfQtysByDPAndInstance)}"); break; } } matrixOfQtysByDPAndInstance[i, columnOfMaxRowOfColumn] += isToAdd ? unit : -unit; matrixOfQtysByDPAndInstance[rowOfMaxValueOfColumn, columnOfMaxRowOfColumn] += isToAdd ? -unit : unit; } while (true); } }
当前代码的问题
- 调整顺序冲突:单独调整列或行会破坏之前已调好的行和/列和。例如先调列到目标和,再调行时会改变列和,导致前序工作失效。
- 交换逻辑缺陷:调整列时仅修改同一行的两个元素,会改变该行总和,破坏行和正确性;同理调整行时会破坏列和。
- 无限循环风险:
do-while(true)循环仅在列/行和完全匹配时退出,若逻辑无法收敛(如找不到合适交换项)会陷入死循环。 - 无意义的最大值依赖:选择最大值位置调整没有逻辑依据,反而限制调整灵活性,甚至导致无法找到可调整元素。
解决方案思路
正确做法是先调整所有行到目标行和,再在不改变行和的前提下调整列到目标列和(反之亦然)。调整列时,通过修改同一列的两个不同行元素,保持每行总和不变:
- 给列j加单位:行i的列j元素加1单位,行k的列j元素减1单位,行i和行k总和不变,仅列j总和变化。
- 减列j总和则反向操作。
前提条件:所有行目标和的总和必须等于所有列目标和的总和,否则问题无解。
修正后的代码
// 辅助函数:计算指定行的和 private decimal SumRow(decimal[,] matrix, int row) { decimal sum = 0; for (int col = 0; col <= matrix.GetUpperBound(1); col++) { sum += matrix[row, col]; } return sum; } // 辅助函数:计算指定列的和 private decimal SumColumn(decimal[,] matrix, int col) { decimal sum = 0; for (int row = 0; row <= matrix.GetUpperBound(0); row++) { sum += matrix[row, col]; } return sum; } // 第一步:调整所有行到目标行和 private void AdjustRowsToTarget(decimal[,] matrix, decimal[] targetRowSums, int decimals) { decimal unit = 1 / (decimal)Math.Pow(10, decimals); int rowCount = matrix.GetUpperBound(0) + 1; int colCount = matrix.GetUpperBound(1) + 1; for (int row = 0; row < rowCount; row++) { decimal currentSum = SumRow(matrix, row); decimal difference = targetRowSums[row] - currentSum; // 处理精度误差,跳过已符合要求的行 if (Math.Abs(difference) < unit / 2) continue; // 计算需要调整的单位数 int steps = (int)Math.Round(difference / unit); // 遍历列调整元素,优先选择非零元素避免负数 int colIndex = 0; while (steps != 0 && colIndex < colCount) { // 减少时确保元素不会变负 if (steps < 0 && matrix[row, colIndex] + steps * unit < 0) { colIndex++; continue; } int adjustAmount = Math.Min(Math.Abs(steps), (int)(matrix[row, colIndex] / unit)); if (adjustAmount == 0) { colIndex++; continue; } matrix[row, colIndex] += steps > 0 ? adjustAmount * unit : -adjustAmount * unit; steps -= steps > 0 ? adjustAmount : -adjustAmount; } } } // 第二步:保持行和不变,调整列到目标列和 private void AdjustColumnsToTargetPreserveRows(decimal[,] matrix, decimal[] targetColSums, int decimals) { decimal unit = 1 / (decimal)Math.Pow(10, decimals); int rowCount = matrix.GetUpperBound(0) + 1; int colCount = matrix.GetUpperBound(1) + 1; for (int col = 0; col < colCount; col++) { decimal currentSum = SumColumn(matrix, col); decimal difference = targetColSums[col] - currentSum; // 处理精度误差,跳过已符合要求的列 if (Math.Abs(difference) < unit / 2) continue; int steps = (int)Math.Round(difference / unit); // 寻找可调整的行对:一个可加,一个可减 int row1 = 0; int row2 = 1; while (steps != 0 && row1 < rowCount && row2 < rowCount) { bool isAdding = steps > 0; // 找到可以执行加减操作的行 while (row1 < rowCount && (isAdding && matrix[row1, col] + unit < 0 || !isAdding && matrix[row1, col] - unit < 0)) { row1++; } while (row2 < rowCount && (row2 == row1 || (!isAdding && matrix[row2, col] + unit < 0 || isAdding && matrix[row2, col] - unit < 0))) { row2++; } if (row1 >= rowCount || row2 >= rowCount) break; // 每次调整1个单位,确保稳定性 int adjustAmount = 1; if (isAdding) { matrix[row1, col] += adjustAmount * unit; matrix[row2, col] -= adjustAmount * unit; } else { matrix[row1, col] -= adjustAmount * unit; matrix[row2, col] += adjustAmount * unit; } steps -= isAdding ? adjustAmount : -adjustAmount; } } } // 主函数:按顺序调用调整方法 public void AdjustMatrixToTargetSums(decimal[,] matrix, decimal[] targetRowSums, decimal[] targetColSums, int decimals) { // 验证总和是否相等,否则无解 decimal totalRowSum = targetRowSums.Sum(); decimal totalColSum = targetColSums.Sum(); if (Math.Abs(totalRowSum - totalColSum) > (1 / (decimal)Math.Pow(10, decimals)) / 2) { Logger.Warn("行目标总和与列目标总和不相等,无法调整"); return; } AdjustRowsToTarget(matrix, targetRowSums, decimals); AdjustColumnsToTargetPreserveRows(matrix, targetColSums, decimals); }
代码说明
- 总和验证:先检查行目标和总和是否等于列目标和总和,这是问题有解的必要条件。
- 行调整:遍历每一行,调整该行内元素值到目标行和,优先选择非零元素避免出现负数。
- 列调整:保持行和不变,通过修改同一列的两个不同行元素,将列和调整到目标值,每次调整1个单位确保稳定性。
- 精度处理:使用
Math.Abs(difference) < unit / 2处理浮点数精度误差,避免微小误差导致的无限循环。
内容的提问来源于stack exchange,提问作者Alberto Fagni
相关产品推荐
相关产品推荐

