最大化矩阵中“非重叠”数字的总和

Posted

技术标签:

【中文标题】最大化矩阵中“非重叠”数字的总和【英文标题】:Maximise sum of "non-overlapping" numbers from matrix 【发布时间】:2012-04-30 05:05:55 【问题描述】:

只是寻找一点方向,我意识到给出的示例可以使用蛮力迭代来解决,但我正在寻找一个更优雅(即数学?)的解决方案,它可能解决更大的示例(比如20x20 或 30x30)。这完全有可能无法做到,而且我在想出一种不依赖蛮力的方法方面几乎没有成功......

我有一个矩阵(称为 A),它是 nxn。我希望从矩阵 A 中选择一个点的子集(称为 B)。该子集将包含 n 个元素,其中从 A 的每一行和每一列中取一个且只有一个元素。输出应提供一个解决方案( B) 使得构成 B 的元素的总和是最大可能值,给定这些约束(例如,在下面的示例中为 25)。如果找到 B 的多个实例(即给出相同最大和的不同解),则应选择具有最大最小元素的 B 的解。

B 也可以是一个 nxn 的选择矩阵,但只有 n 个所需的元素是非零的。

例如: 如果 A =

|5 4 3 2 1|
|4 3 2 1 5|
|3 2 1 5 4|
|2 1 5 4 3|
|1 5 4 3 2|

=> B 是

|5 5 5 5 5|

但是,如果 A =

|5 4 3|
|4 3 2|
|3 2 1|

B =

|3 3 3|

因为 B 的最小元素是 3,比 for 大

|5 3 1|

|4 4 1|

两者总和为 9

【问题讨论】:

听起来它可以在O(n^3) 时间左右使用动态编程解决方案。 我有一个动态编程算法,应该在O(n^3) 时间运行。我必须把它写下来并正式证明它是正确的。它应该比简单的递归解决方案具有更好的性能......只要我能证明它有效。 ;) 【参考方案1】:

您的问题与Assignment problem 几乎相同,例如由Hungarian algorithm 在多项式时间内求解。

请注意,分配问题通常是一个最小化问题,但是将矩阵乘以 -1 并添加一些常数应该可以使该方法适用。此外,对于多个最优解的情况,没有正式的平局条件。但是,该方法会为您提供具有最佳总和的解决方案。设 m 为最小和数。通过将所有小于或等于 m 的条目设置为零来修改矩阵并再次求解。要么你得到一个总和比上一个更好的解决方案。如果不是,之前的解决方案已经是最优的了。

【讨论】:

这太棒了。谢谢!【参考方案2】:

正如 Matthias 所说,您应该使用回溯。

    找到合理的解决方案。从每一行中选择最大值并查看它们是否不重叠。如果不是,则扰动部分解,使结果不重叠。 定义部分解的适应度。假设您正在迭代地为每一行获取值,并且您已经从前 k 行中获取了值。此解决方案的适应度等于已选择值的总和 + 剩余行和未选择列的最大值 现在递归地开始搜索解决方案。从第一行中选择值,计算它们的适应度并将它们插入优先级队列。删除所有适应度低于当前最优解(在步骤 1 中初始化)的解。选择队列头部的解决方案,计算下一级解决方案并将它们插入优先级队列。从所有列和行中选择值后,计算总和,如果高于当前最优值,则替换它。

【讨论】:

如果我理解正确,您基本上是在构建潜在解决方案树,并在进行过程中修剪死胡同? 推理这种方法的平均情况和最坏情况运行时间会很有趣。现在是凌晨 3 点,我很困,所以我自己不会尝试这样做 - 但请注意,如果我的动态编程解决方案可以被证明是正确的,那么无论如何它总是在O(n^3) 时间内找到至少一个有效的解决方案。 @Li-aungYip 这是一个启发式方法。所以最坏情况的复杂度是 O(n!) (想象一个矩阵,其中对角线为 2,其他元素为 1,最后一行中的一个非对角线元素非常大)。不确定平均复杂度。 我不确定是将其归类为branch and bound 方法还是hill-climbing 方法。【参考方案3】:

哎哟。 This algorithm is wrong; there is no proof because it's wrong and therefore it's impossible to prove that it's correct. ;) 我把它留在这里是因为我太执着于完全删除它,这很好地说明了为什么你应该正式证明算法而不是说“这看起来正确!这不可能失败工作!”


我暂时给出了这个没有证据的解决方案。我有一个证明草图,但我无法证明这个问题的最佳子结构。总之……

问题

给定一个数字的方阵,选择尽可能多的“不重叠”数字,以使所选数字的总和最大化。 “不重叠”意味着没有两个数字可以来自同一行或同一列。

算法

A 是一个由n by n 数字组成的方阵。 让Aij 表示Aith 行和jth 列中的元素。 让S( i1:i2, j1:j2 ) 表示A 的方形子数组的非重叠数字的最佳总和,其中包含行i1i2 和列j1j2 的交集。

那么不重叠数的最优和记为S( 1:n , 1:n ),给出如下:

S( 1:n , 1:n ) = max   [ S(   2:n , 2:n   ) + A11 ]
                        [ S(   2:n , 1:n-1 ) + A1n ]
                        [ S( 1:n-1 , 2:n   ) + An1 ]
                        [ S( 1:n-1 , 1:n-1 ) + Ann ] 
(recursively)

Note that S( i:i, j:j ) is simply Aij.

也就是说,大小为n的方阵的最优和可以通过分别计算大小为n-1的四个子阵中的每一个的最优和,然后最大化子阵之和来确定。数组和“遗漏”的元素。

S for |# # # #|
      |# # # #|
      |# # # #|
      |# # # #|

Is the best of the sums S for:

|#      |      |      #|      |# # #  |       |  # # #|
|  # # #|      |# # #  |      |# # #  |       |  # # #|
|  # # #|      |# # #  |      |# # #  |       |  # # #|
|  # # #|      |# # #  |      |      #|       |#      |

实施

上面的递归算法提出了一个递归解决方案:

def S(A,i1,i2,j1,j2):
    if (i1 == i2) and (j1==j2):
        return A[i1][j1]
    else:
        return max ( S( A, i1+1, i2, j1+1, j2) + A[i1][j1] ],
                     S( A, i1+1, i2, j1, j2-1) + A[i1][j2] ],
                     S( A, i1, i2-1, j1+1, j2) + A[i2][j1] ],
                     S( A, i1, i2-1, j1, j2-1) + A[i2][j2] ], )

请注意,这将使O(4^n) 调用S()!!这比“蛮力”解决方案的阶乘 O(n!) 时间复杂度要好得多,但性能仍然很差。

这里要注意的重要一点是,许多调用使用相同的参数重复。比如求解一个 3*3 的数组,每个 2*2 的数组都要求解很多次。

这提出了两种可能的加速解决方案:

使递归函数S()缓存结果,这样每个i1,i2,j1,j2只需要S(A,i1,i2,j1,j2)一次。这意味着S() 只需要计算O(n^3) 结果——所有其他请求都将从缓存中完成。 (这称为memoising。) 不要从顶部开始,使用大的 n*n 数组,然后逐步处理较小的子问题,而是从底部开始,从可能的最小子问题开始,然后向上构建到 n*n案子。这称为动态规划。这也是O(n^3),但它比O(n^3) 快得多,因为您不必一直访问缓存。

动态规划解决方案有点像:

找到所有 1x1 子阵列的最优解。 (琐碎。) 为所有 2x2 子阵列找到最佳解决方案。 为所有 3x3 子阵列找到最佳解决方案。 ... 找到所有 n-1 * n-1 个子阵列的最优解。 找到完整 n*n 子阵列的最优解。

关于此解决方案的说明:

尚无证据。我正在努力。 您会注意到上面的算法只给您S(),即最佳sum。它并没有告诉你哪些数字实际上构成了这个总和。您可以添加自己的方法来回溯解决方案。 上面的算法不能保证像2,21,3这样的关系会被打破,而是让所有的个体数字尽可能大(这样2,2就会获胜。)我相信您可以定义 max() 来打破平局,支持尽可能多的数字,这将满足您的需求,但我无法证明这一点。

一般说明:

Dynamic programming 是一种强大的技术,可以为任何具有两个属性的问题设计快速算法:

    最优子结构:一个问题可以分解成更小的部分,每个部分都可以作为原始问题解决方案的一部分。 重叠子问题意味着实际要解决的子问题很少,子问题的解决方案会被多次重复使用。

如果问题具有最优子结构,并且问题分解为 稍微 更小的问题 - 比如说一个大小为 n 的问题分解为大小为 n-1 的子问题 - 那么问题可以是通过动态规划解决。

如果您可以将问题拆分为 很多 更小的块 - 比如说将大小为 n 的问题分成两半,每个大小为 n/2 - 这就是分而治之,不是动态规划。分而治之的解决方案通常非常快 - 例如,二分搜索将在 O(log n) 时间内在排序数组中找到一个元素。

【讨论】:

【参考方案4】:

这与n Queens problem 有关,只是您不关心对角线并且您有加权解决方案。作为皇后问题,你可以通过(多次)回溯来解决。

即,一旦找到解决方案,您就会记住它的重要性,将解决方案标记为无效,然后重新开始。 (a) 权重最高的解决方案获胜。

【讨论】:

谢谢,这有点帮助。除了找到“解决方案”并不是真正的问题。我知道有n! “解决方案”,而且我知道只要每次放置一个元素时在不同的行和列中选择一个元素,我总能找到一个“解决方案”,因此回溯与我的情况根本不相关.棘手的部分是找到某种方法来排除或优先考虑特定的“解决方案”集,这样您就不必遍历所有“解决方案”来找到给您最大总和的那个,这可能是也可能不是可能....

以上是关于最大化矩阵中“非重叠”数字的总和的主要内容,如果未能解决你的问题,请参考以下文章

寻找总和最大的子矩阵[重复]

Java算法之边界为1的最大子矩阵

矩阵列的最小元素

区间树中的最大非重叠区间

采访:使用递归的二维矩阵中的最大路径和。路径的恢复

c_cpp 最大子阵列总和。在具有最大总和的数组(包含至少一个数字)中查找连续的子数组。