这是来自奥林匹克运动会的老练习题:
想象一下,您有一个1000x1000网格,其中单元格(i,j)包含数字i * j。 (行和列从1开始编号。)
在每个步骤中,我们从旧网格构建一个新网格,其中每个单元格(i,j)包含"邻域平均值" (i,j)在最后一个网格中。 "邻里平均值"被定义为单元的平均值的底限及其最多8个邻居。因此,例如,如果网格角落中的4个数字是1,2,5,7,则在下一步中,角落将被计算为(1 + 2 + 5 + 7)/ 4 = 3.
最终,我们将达到所有数字相同且网格不再发生变化的程度。目标是确定达到这一点需要多少步骤。
我试过简单地模拟它但是没有用,因为似乎答案是O(n ^ 2)个步骤,每个模拟步骤需要O(n ^ 2)来处理,导致O(n ^ 4)对于n = 1000来说太慢了。
有更快的方法吗?
答案 0 :(得分:3)
稍微快一点的方法如下:
如果您注意到任何不在矩阵边界(x,y)上的单元格,则其原始值应为x * y。
此外,第一次迭代后的单元格值应为:
V1 = ( xy + x(y+1) + x(y-1)
+(x+1)y + (x+1)(y+1) + (x+1)(y-1)
+(x-1)y + (x-1)(y+1) + (x-1)(y-1)
) / 9
= xy
对于左侧垂直边缘(不是角落)的元素
v2 = ( xy + (x-1)y + (x+1)y + x(y+1) + (x-1)(y+1) + (x+1)(y+1) ) / 6
= xy + x/2.
对于右侧垂直边缘(不是角落)的元素
v3 = ( xy + (x-1)y + (x+1)y + x(y-1) + (x-1)(y-1) + (x+1)(y-1) ) / 6
= xy - x/2.
同样适用于顶部和底部水平边缘和角落。
因此,在第一次迭代后,只有边界元素应改变它们的值,非边界元素应保持不变。
对于后续迭代,此更改应从矩阵中的边界向内传播。
因此,您可以稍微减少计算的一个显而易见的方法是仅更改那些您希望在前N / 2次迭代中更改的元素。 注意:这样做的复杂性不会改变IMO,但常数因素会减少。
您可以考虑的另一种可能方式如下:
你知道最中心元素应该保持不变,直到N / 2次迭代。
因此,您可以考虑通过从最中心元素向外开始来跳过启动迭代的方法。
如果你能在N / 2次迭代后找到元素变化的增量数学公式,你可以将算法的复杂度降低N倍。
答案 1 :(得分:1)
" floor"步骤让我怀疑分析解决方案不太可能,而这实际上是一个微观优化练习。这是我的想法。
让我们暂时忽略角落和边缘。它们只有3996个,无论如何它们都需要特殊处理。
对于内部单元格,您需要添加9个元素才能获得下一个状态。但转过来说,并说:每个内部细胞必须是8个添加物的一部分。
还是吗?从连续三行A[i]
,B[i]
和C[i]
开始,计算三个新行:
A'[i] = A[i-1] + A[i] + A[i+1]
B'[i] = B[i-1] + B[i] + B[i+1]
C'[i] = C[i-1] + C[i] + C[i+1]
(请注意,您可以使用"滑动窗口",从A'[i+1] = A'[i] - A[i-1] + A[i+1]
开始,稍微更快地计算每一个。算术运算数相同但负载更少。)
现在,要在位置B[j]
获取新值,您只需计算A'[j] + B'[j] + C'[j]
。
到目前为止,我们还没有保存任何工作;我们刚刚重新订购了这些内容。
但是现在,计算更新的行B
后,您可以扔掉A'
并计算下一行:
D'[i] = D[i-1] + D[i] + D[i+1]
...您可以使用数组B'
和C'
来计算行C
的新值,而无需重新计算B'
或C'
。 (你可以通过将行B'
和C'
转换为A'
和B'
来实现这一点,当然......但这种方式更容易解释。也许。我认为。)
对于每一行,例如B
,我们扫描一次以生成B'
执行2n
算术运算,第二次计算更新后的B
2n
次操作,因此我们总共对每个元素进行四次加法/减法,而不是八次。
当然,在实践中,您需要在更新C'
的同时计算B
以获得相同数量的操作,但更好的位置。
这是我唯一的结构性想法。 SIMD优化专家可能会有其他建议......
答案 2 :(得分:0)
如果你看一下初始矩阵,你会发现它是symmetric,即m [i] [j] = m [j] [i]。因此m [i] [j]的邻居将具有与m [j] [i]的邻居相同的值,因此您只需要计算每个步骤的矩阵的一半以上的值。
此优化可将每个网格的计算次数从N^2
减少到((N^2)+N)/2
。