问题规模
让我们先考虑最坏的情况:
您有 10 列和每列 5(完整)行。应该清楚的是,您将能够获得(每个地方有适当数量的人口)多达 5^10 ≅ 10^6 个不同的结果(解决方案空间)。
例如,以下矩阵将为您提供 3 列的最坏情况:
| 1 10 100 |
| 2 20 200 |
| 3 30 300 |
| 4 40 400 |
| 5 50 500 |
产生 5^3=125 个不同的结果。 每个结果的格式为 {a1 a2 a3} 和 a i ∈ {1,5}
很容易证明这样一个矩阵对于任意数量的 n 列总是存在的。
现在,要获得每个数值结果,您需要进行 n-1 次求和,总计问题大小为 O(n 5^n)。所以,这是最坏的情况,我认为对此无能为力,因为要知道有效执行求和所需的可能结果。
更多良性化身:
问题的复杂性可以通过两种方式截断:
- 更少的数字(即并非所有列都已满)
- 重复的结果(即多个部分总和给出相同的结果,您可以将它们合并到一个线程中)。稍后在这方面更多。
让我们看一个后两行的简化示例:
| 7 6 100 |
| 3 4 200 |
| 1 2 200 |
乍一看,你需要做 2 3^3 的总和。但事实并非如此。当您将第一列相加时,您不会得到预期的 9 个不同的结果,而只有 6 个 ({13,11,9,7,5,3})。
因此,您不必将 9 个结果带到第三列,而只需 6 个。
当然,这是以从列表中删除重复数字为代价的。 “删除重复的整数元素”was posted before in SO 我不会在这里重复讨论,而只是引用在列表大小 (m) 中执行合并排序 O(m log m) 将删除重复项。如果你想要更简单的东西,双循环 O(m^2) 就可以了。
无论如何,出于几个原因,我不会尝试以这种方式计算(均值)问题的大小。其中之一是排序合并中的“m”不是问题的大小,而是任何两列相加后结果向量的大小,并且该操作重复(n-1)次......我真的不想做数学:(。
另一个原因是,当我实现算法时,我们将能够使用一些实验结果并将我们从我肯定泄漏的理论考虑中解救出来。
算法
正如我们之前所说,很明显我们应该针对良性情况进行优化,因为最坏的情况是失败的情况。
为此,我们需要对列使用列表(或可变暗淡向量,或任何可以模拟这些的东西),并在每列添加后进行合并。
合并可以由其他几种算法(例如在 BTree 上的插入)替换,而无需修改结果。
所以算法(程序伪代码)类似于:
Set result_vector to Column 1
For column i in (2 to n-1)
Remove repeated integers in the result_vector
Add every element of result_vector to every element of column i+1
giving a new result vector
Next column
Remove repeated integers in the result_vector
或者按照您的要求,递归版本可以按如下方式工作:
function genResVector(a:list, b:list): returns list
local c:list
{
Set c = CartesianProduct (a x b)
Set c = Sum up each element {a[i],b[j]} of c </code>
Drop repeated elements of c
Return(c)
}
function ResursiveAdd(a:matrix, i integer): returns list
{
genResVector[Column i from a, RecursiveAdd[a, i-1]];
}
function ResursiveAdd(a:matrix, i==0 integer): returns list={0}
算法实现(递归)
我选择一种函数式语言,我想翻译成任何程序性语言都没什么大不了的。
我们的程序有两个功能:
- genResVector,将两个列表相加,给出所有可能的结果,并删除重复的元素,并且
- recursiveAdd,它在矩阵列上递归所有这些列。
recursiveAdd,在矩阵列上递归所有这些列。
代码是:
genResVector[x__, y__] := (* Header: A function that takes two lists as input *)
Union[ (* remove duplicates from resulting list *)
Apply (* distribute the following function on the lists *)
[Plus, (* "Add" is the function to be distributed *)
Tuples[{x, y}],2] (*generate all combinations of the two lists *)];
recursiveAdd[t_, i_] := genResVector[t[[i]], recursiveAdd[t, i - 1]];
(* Recursive add function *)
recursiveAdd[t_, 0] := {0}; (* With its stop pit *)
测试
如果我们采用您的示例列表
| 1 1 7 9 1 1 |
| 2 2 5 2 2 |
| 3 3 |
| 4 |
| 5 |
并运行程序结果是:
{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}
最大值和最小值很容易验证,因为它们对应于从每列中获取最小值或最大值。
一些有趣的结果
让我们考虑一下当矩阵每个位置上的数字有界时会发生什么。为此,我们将采用一个完整的 (10 x 5 ) 矩阵并用 Random Integers 填充它。
在整数只有零或一的极端情况下,我们可能会期待两件事:
- 非常小的结果集
- 快速执行,因为会有很多重复的中间结果
如果我们增加随机整数的 范围,我们可能会期望增加结果集和执行时间。
实验 1:用不同范围随机整数填充的 5x10 矩阵
很明显,对于接近最大结果集大小 (5^10 ≅ 10^6 ) 的结果集,计算时间和“!= 结果数”具有渐近线。我们看到函数递增的事实只是表明我们离那个点还很远。
士气:你的元素越小,你获得它的机会就越大。这是因为你可能有很多重复!
请注意,对于测试的最坏情况,我们的 MAX 计算时间接近 20 秒
实验 2:没有优化
有大量可用内存,我们可以通过蛮力计算,而不是删除重复的结果。
结果很有趣…… 10.6 秒! ...等等!发生了什么 ?我们的“删除重复整数”小技巧会占用大量时间,当没有太多要删除的结果时,没有任何收获,但在尝试摆脱重复时会失败。
但是当矩阵中的最大数远低于 5 10^5 时,我们可能会从优化中获得很多好处。请记住,我是在满载 5x10 矩阵的情况下进行这些测试的。
本次实验的精神是:重复整数去除算法很关键。
HTH!
PS:如果我有时间编辑它们,我还有一些实验要发布。