我们被赋予了一项简单的任务,即想出最有效的方法,分别使用递归和迭代对起点和终点("from"one_answers"to")之间的所有数字求和,而不使用显而易见的公式O(1)。
没有应用程序,我只是好奇和挑战,看看我的解决方案是否可以比现在更好地改进/完善:
/* recursion */
unsigned int sum1(unsigned int from, unsigned int to) {
if (to - from < 2)
return from + (from == to ? 0 : to);
else
return from + to + sum1(from + 1, to - 1);
}
/* iteration */
unsigned int sum2(unsigned int from, unsigned int to) {
int p = to - from;
if (p == 0) return from;
int i, s, n = p / 2;
if (p % 2 == 0) s = n + from;
else {
s = 0;
n++;
}
for (i = 0; i < n; i++) {
s += from++ + to--;
}
return s;
}
我尝试改进迭代版本:
unsigned int sum2_improved(unsigned int from, unsigned int to) {
int p = to - from;
if (p == 0) return from;
int x = to + from;
int s = 0;
int i;
for (i = p >> 1; i > 0; i--)
{
s += x;
}
s += (p % 2 == 0) ? x >> 1 : x;
return s;
}
我用测试了你的版本
for (i = 0; i < 9999999; i++) sum2(1,999);
这就是我所看到的:
$ time ./addm
real 0m18.315s
user 0m18.220s
sys 0m0.015s
我尝试使用相同数量的循环来实现。以下是改进后的功能的表现:
$ time ./addm
real 0m14.196s
user 0m14.070s
sys 0m0.015s
更新
在我的实现中,x = to + from
是序列中第一个数字和最后一个数字的总和。如果你考虑任何连续的整数序列,并对第一个和最后一个、第二个和倒数第二个求和,依此类推。。。所有这些加起来都是相同的值。例如,在(1 ... 6), 1 + 6 = 2 + 5 = 3 + 4 = 7
。然而,对于包含奇数元素的序列,只剩下中间数,然后必须将其添加到累积和中(这就是for
循环之后的赋值所做的。
另外,请注意,这仍然是O(n)
。在我最初发布答案后,我意识到我的方法实际上可以在恒定的时间内完成。这是更新后的代码:
unsigned int sum0(unsigned int from, unsigned int to) {
int p = to - from;
if (p == 0) return from;
int x = to + from;
int s = 0;
s += (x * (p >> 1));
s += (p % 2 == 0) ? x >> 1 : x;
return s;
}
我使用与早期测试相同数量的循环来运行此测试。我看到的是:
$ time ./addm
real 0m0.158s
user 0m0.093s
sys 0m0.047s
我不确定这是否可以被视为公式的一种变体。无论如何,这对我来说都是一个有趣的练习
将范围(从零到上限n)分为上下两部分。对于下半部分中的每个值,上半部分中都有一个大于n/2的值;它们有n/2,所以上半部分的和就是下半部分的总和+(n/2)^2。
在Python中,这将是:
def sum1(lower, upper):
if upper <= 0:
return 0
m, r = divmod(upper, 2)
return sum1(0, m) * 2 + m * m + r * upper - sum1(0, lower - 1)
我不会为它编写代码,但这是一种会直接随着任务中核心数量而扩展的东西。
将范围划分为多个任务,并启动一个线程来对范围的每个小节求和,这将把你选择的任何实现所需的时间除以核心的数量(给予或接受)。
您还可以使用SIMD扩展,通过提前在内存中写入数据来促进加法(矢量加法)。将其推向另一个极端,你实际上可以使用GPU来计算子范围的添加(但你必须有足够大的范围才能使其值得开销),使其变得愚蠢而快速;因为这个问题非常简单,指令之间没有任何依赖关系。
您可以使用分段树来获取从i到j的分段的和。此结构具有O(log n)查找时间。
函数:
long sum(long lower, long upper) {
long s = 0;
s = ((upper * (upper + 1)) - (lower - 1) * (lower))/2;
return s;
}
使用参数调用:(19999999)返回49999995000000,与求和公式n(n+1)/2一致,并在核心二人组上运行以下配置文件:
real 0m0.005s
user 0m0.002s
sys 0m0.003s
可能值得检查一下你的函数,我看不出它们会返回这个结果——数学选项是一个非常好的解决方案;)