从矩阵中选择“不重叠”数字的总和最大化

6
只是想寻求一点方向,我意识到所给的示例可以使用暴力迭代来解决,但我正在寻找更优雅(即数学?)的解决方案,这可能可以解决更大的示例(例如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)。 - Li-aung Yip
我有一个动态规划算法,应该在O(n^3)的时间内运行。我必须将其写出并正式证明它的正确性。它应该比朴素递归解决方案具有显着更好的性能...只要我能证明它是有效的。 ;) - Li-aung Yip
4个回答

6
你的问题与分配问题几乎相同,例如可以通过匈牙利算法在多项式时间内解决。
请注意,分配问题通常是一个最小化问题,但将矩阵乘以-1并添加一些常数应该使方法适用。此外,在存在多个最优解的情况下,没有正式的决策条件。然而,该方法会给出一个具有最佳总和的解决方案。设m为最小项。通过将所有小于或等于m的条目设置为零并重新求解来修改您的矩阵。要么您会得到一个具有相同总和且比上一个更好的解决方案。如果不是,则先前的解决方案已经是最优的。

1
哎呀。这个算法是错的;没有证明,因为它是错误的,所以无法证明它是正确的。;) 我把它留在这里,因为我太喜欢它了,而且它很好地演示了为什么你应该正式地证明算法,而不是说“这看起来对!没有可能失败!”


目前我还没有证明,但是我会提供这个解决方案。对于这个问题,我有一个证明草稿,但是我在证明这个问题的最优子结构时遇到了一些困难。无论如何...

问题

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

算法

  • A为一个由n x n个数字组成的正方形数组。
  • Aij表示A中第i行和第j列的元素。
  • S(i1:i2, j1:j2)表示包含行i1i2和列j1j2的交集的A的正方形子数组的最佳非重叠数字的总和。

那么最佳非重叠数字的总和表示为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)个结果 - 所有其他请求都将从缓存中满足。(这被称为记忆化。)
  • 不要从顶部开始,使用大的n*n数组,并通过连续较小的子问题向下工作,而是从最小可能的子问题开始,从底部构建到n*n情况。这被称为动态规划。这也是O(n^3),但它是一个更快的O(n^3),因为你不必一直访问缓存。

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

  • 找到所有1x1子数组的最优解(微不足道)。
  • 找到所有2x2子数组的最优解。
  • 找到所有3x3子数组的最优解。
  • ...
  • 找到所有n-1 * n-1子数组的最优解。
  • 找到完整n*n子数组的最优解。

关于此解决方案的说明:

  • 尚未证明。我正在努力。
  • 您会注意到上面的算法只给出了S(),即最优的。它并没有告诉您哪些数字实际上构成了该和。您可以添加自己的回溯方法来找到解决方案的路径。
  • 上述算法不能保证像2,21,3这样的平局将被打破,以支持所有单个数字尽可能大(使2,2获胜)。我相信您可以定义max()以支持在最大可能数字的情况下打破平局,但我无法证明。

一般注释:

动态规划是一种强大的技术,可用于为展示以下两个特性的任何问题设计快速算法:

  1. 最优子结构:一个问题可以分解成稍微小一点的部分,每个部分都可以作为原问题的解的一部分来使用。
  2. 重叠子问题意味着要解决的实际子问题很少,并且子问题的解被多次重复使用。

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

如果您可以将问题拆分为明显更小的块-例如将大小为n的问题分成大小为n/2的两半,那么这就是分治法,而不是动态规划。分治法的解决方案通常非常快-例如,二分查找可以在O(log n)时间内找到排序数组中的元素。


0

正如Matthias所指出的,您应该使用回溯法。

  1. 找到一个合理的解决方案。从每一行选择最大值,并检查它们是否重叠。如果有重叠,则扰动部分解决方案,使其变为非重叠。
  2. 定义部分解决方案的适应度。假设您正在逐步选择每一行的值,并且已经从前k行选择了值。这个解决方案的适应度等于已选择的值的总和+剩余行和未选择列的最大值。
  3. 现在递归地开始搜索解决方案。从第一行选择值,计算它们的适应度并将其插入优先队列中。删除适应度低于当前最优解(在步骤1中初始化)的所有解决方案。选择队列头部的解决方案,计算下一级解决方案并将其重新插入优先队列。一旦您从所有列和行中选择了值,请计算总和,如果比当前最优解更高,则进行替换。

如果我理解正确的话,你基本上是在构建一个潜在解决方案的树形结构,并在进行时修剪掉死胡同? - Li-aung Yip
研究这种方法的平均情况和最坏情况运行时间会很有趣。现在是凌晨三点了,我非常困,所以我不会自己尝试做这个——但请注意,如果我的动态规划解决方案可以被证明是正确的,无论如何它都可以在O(n^3)的时间内找到至少一个有效的解决方案。 - Li-aung Yip
@Li-aungYip 这是一种启发式算法。因此,最坏情况下的复杂度为O(n!)(想象一个对角线为2,其他元素为1且最后一行中有一个非对角线元素非常大的矩阵)。不确定平均复杂度。 - ElKamina
我不确定这是归类为分支定界法还是爬山算法 - Li-aung Yip

-1

这与n皇后问题有关,但您不必考虑对角线,并且您有加权解决方案。与皇后问题一样,您可以通过(多次)回溯来解决它。

即,一旦找到解决方案,您会记住其权重,将该解决方案标记为无效,并重新开始。具有最高权重的(a)解决方案获胜。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接