通常,您可以通过使用大 O 表示法分析算法的增长率来量化算法的扩展能力。当您说您的算法通过“蛮力”运行时,尚不清楚它将扩展到何种程度。如果您的“蛮力”解决方案通过列出一组数据的所有可能子集或组合来工作,那么它几乎肯定不会扩展(它将具有渐近复杂度 O(2n) 或 O(n !), 分别)。如果您的蛮力解决方案通过查找所有元素对并检查每个元素来工作,它可能会很好地扩展 (O(n2))。但是,如果没有关于您的算法如何工作的更多信息,就很难说。
您可能希望以this excellent post about big-O 为起点,了解如何推断您的程序的长期可扩展性。通常来说,任何增长率为 O(n log n)、O(n)、O(log n) 或 O(1) 的东西都可以很好地扩展,任何增长率为 O(n2 ) 或 O(n3) 将扩大到一个点,任何增长率为 O(2n) 或更高的东西都不会扩大。
另一种选择是查找您要解决的问题,以了解它的研究程度。众所周知,有些问题有很好的解决方案,如果您的问题是其中之一,那么可能值得看看其他人的想法。也许有一个非常干净、非暴力的解决方案可以很好地扩展!其他一些问题被推测为根本没有可扩展的算法(所谓的NP-hard problems)。如果是这种情况,那么您应该非常确信没有办法获得可扩展的方法。
最后,您可以随时在 Stack Overflow 上提出一个新问题,描述您正在尝试做什么并征求意见。也许社区可以比您最初预期的更有效地帮助您解决问题!
编辑: 鉴于您要解决的问题的描述,现在您正在为每个变量执行一个 for 循环,从 0 到您尝试定位的数字。该算法的复杂度为 O(Uk),其中 k 是变量的数量,U 是总和。这种方法根本不会很好地扩展。在上述情况下引入每个新变量会使算法运行速度慢 100 倍,如果你想要 100 个变量,它肯定不会很好地扩展!
但是,我认为有一个相当好的算法,它的运行时间为 O(U2k),它使用 O(Uk) 内存来解决问题。直觉如下:假设我们想将1、2、4相加得到10。有很多方法可以做到:
2 * 4 + 1 * 2 + 0 * 1
2 * 4 + 0 * 2 + 2 * 1
1 * 4 + 3 * 2 + 0 * 1
1 * 4 + 2 * 2 + 2 * 1
1 * 4 + 1 * 2 + 4 * 1
1 * 4 + 0 * 2 + 6 * 1
0 * 4 + 5 * 2 + 0 * 1
0 * 4 + 4 * 2 + 2 * 1
0 * 4 + 3 * 2 + 4 * 1
0 * 4 + 2 * 2 + 6 * 1
0 * 4 + 1 * 2 + 8 * 1
0 * 4 + 0 * 2 + 10 * 1
关键的观察是,我们可以将所有这些写成总和,但更重要的是,总和中的每一项都不大于前一项:
2 * 4 + 1 * 2 + 0 * 1 = 4 + 4 + 2
2 * 4 + 0 * 2 + 2 * 1 = 4 + 4 + 1 + 1
1 * 4 + 3 * 2 + 0 * 1 = 4 + 2 + 2 + 2
1 * 4 + 2 * 2 + 2 * 1 = 4 + 2 + 2 + 1 + 1
1 * 4 + 1 * 2 + 4 * 1 = 4 + 2 + 1 + 1 + 1 + 1
1 * 4 + 0 * 2 + 6 * 1 = 4 + 1 + 1 + 1 + 1 + 1 + 1
0 * 4 + 5 * 2 + 0 * 1 = 2 + 2 + 2 + 2 + 2
0 * 4 + 4 * 2 + 2 * 1 = 2 + 2 + 2 + 2 + 1 + 1
0 * 4 + 3 * 2 + 4 * 1 = 2 + 2 + 2 + 1 + 1 + 1 + 1
0 * 4 + 2 * 2 + 6 * 1 = 2 + 2 + 1 + 1 + 1 + 1 + 1 + 1
0 * 4 + 1 * 2 + 8 * 1 = 2 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
0 * 4 + 0 * 2 + 10 * 1 = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
所以这给出了一个有趣的想法,即如何生成所有可能的方法来总结目标。这个想法是固定第一个系数,然后生成所有可能的方法来使其余的总和计算出来。换句话说,我们可以递归地思考这个问题。如果我们按 x1, x2, ..., xn 的顺序列出变量,那么我们可以尝试固定一些特定的系数对于 x1,然后仅使用 x2, ..., xn 解决对 sum - c_1 x_1 求和的问题。
到目前为止,这似乎并不那么花哨 - 事实上,这正是您在上面所做的 - 但我们可以使用一个技巧。只要我们要递归地思考这个问题,让我们以相反的方式思考这个问题。与其从 sum 开始并尝试将其分解,不如从 0 开始并尝试构建我们所能做的一切呢?
这就是想法。假设我们已经预先知道我们可以仅使用 x1 的总和得出的所有数字。然后对于介于 0 和 sum(含)之间的每个数字 k,我们可以将 x2 和 x1 中的 k 取自 k - c 的任意组合2 x2 可以由 x1 的组合组成。但是由于我们已经预先计算了这个,我们可以迭代所有可能的 c2 合法值,计算 k - c2 x2 ,看看我们是否知道如何制作它。假设我们存储了一个巨大的 U x (k + 1) 布尔值表,使得表条目 [x, y] 存储“我们能否以精确总结为 U 的方式总结第一个 y 值,包括在内?”我们可以有效地填写表格。这称为动态规划,是一种强大的算法工具。
更具体地说,这可能是如何工作的。给定 k 个变量,创建一个 U x (k + 1) 值表 T。然后,为所有 x > 0 设置 T[0][0] = true 和 T[x][0] = false。这里的基本原理是 T[0][0] 的意思是“我们可以使用第一个零变量的线性组合?”答案肯定是肯定的(空总和为零!),但对于任何其他由没有变量的线性组合组成的总和,我们肯定做不到。
现在,对于 i = 1 .. k,我们将尝试填充 T[x][i] 的值。请记住,T[x][i] 的意思是“我们可以将 x 作为前 i 个变量的线性组合吗?”好吧,我们知道如果有某个系数 c,我们可以做到这一点,使得 k - cxi 可以使用 x1, x 的线性组合2, ..., xi - 1。但是对于任何 c,这只是 T[x - c xi][i - 1] 是否为真。因此我们可以说
for i = 1 to k
for z = 0 to sum:
for c = 1 to z / x_i:
if T[z - c * x_i][i - 1] is true:
set T[z][i] to true
检查循环,我们看到外部循环运行 k 次,内部循环每次迭代运行 sum 次,最里面的循环每次迭代最多运行 sum 次。他们的产品是(使用我们上面的符号)O(U2 k),这比您最初使用的 O(Uk) 算法要好得多。
但是您如何使用这些信息列出所有可能的方法来总结目标?这里的诀窍是要意识到您可以使用该表来避免浪费大量精力来搜索所有可能的组合,而其中许多组合都不起作用。
让我们看一个例子。假设我们已经完全计算了这个表并且想要列出所有的解决方案。一个想法是考虑列出最后一个变量的系数为零的所有解决方案,然后当最后一个变量为 1 时,等等。您之前使用的方法的问题是,对于某些系数,可能根本没有任何解决方案.但是通过我们上面构建的表格,我们可以修剪掉那些分支。例如,假设我们想看看是否有任何以 xk 开头且系数为 0 的解。这意味着我们要问是否有任何方法可以总结前 k - 1 个变量,因此这些值的总和为 sum。当且仅当 T[sum][k - 1] 为真时,这是可能的。如果是真的,那么我们可以递归地尝试将系数分配给其余的值,总和为sum。如果没有,那么我们跳过这个系数并继续下一个。
递归地,这看起来像这样:
function RecursivelyListAllThatWork(k, sum) // Using last k variables, make sum
/* Base case: If we've assigned all the variables correctly, list this
* solution.
*/
if k == 0:
print what we have so far
return
/* Recursive step: Try all coefficients, but only if they work. */
for c = 0 to sum / x_k:
if T[sum - c * x_k][k - 1] is true:
mark the coefficient of x_k to be c
call RecursivelyListAllThatWork(k - 1, sum - c * x_k)
unmark the coefficient of x_k
这将递归地列出所有有效的解决方案,使用我们刚刚构建的表中的值来跳过大量浪费的工作。建立此表后,您可以将任务分配给多台计算机,让它们各自列出全部解决方案的一个子集,然后并行处理它们。
希望这会有所帮助!