Sum(i ... j) - 有更高效/更优雅的解决方案吗?

时间:2012-03-18 05:14:23

标签: c algorithm

我们给出了一个简单的任务来提出最有效的方法来分别使用递归和迭代来计算起点和终点('从'和'到')之间的所有数字,而不使用明显的公式这将是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;
}

5 个答案:

答案 0 :(得分:3)

我尝试改进迭代版本:

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

我不确定这是否可以视为您的目的的公式的变体。无论如何,对我来说这是一个有趣的练习。

答案 1 :(得分:1)

在下半部分和上半部分分割范围(从零到上限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)

答案 2 :(得分:0)

我不会为它编写代码,但是这种情况会直接与您处理任务的核心数量相关。

将范围划分为任务并开始一个线程来对范围的每个子部分求和,将根据核心数量(给予或接受)划分所选实施所需的时间。

您还可以使用SIMD扩展来通过事先在内存中写出数据来促进添加(向量添加)。把它带到另一个极端,你实际上可以使用GPU来计算子范围的增加(但是你必须有足够大的范围来使它值得开销),使它变得愚蠢;因为这个问题很简单,你可以在指令之间没有任何依赖关系。

答案 3 :(得分:0)

您可以使用segment tree来获取从i到j的段的总和。此结构具有O(log n)查找时间。

答案 4 :(得分:0)

功能:

long sum(long lower, long upper) {
    long s = 0;
    s = ((upper * (upper + 1)) - (lower - 1) * (lower))/2;
    return s;
}

使用参数调用:(1,9999999)返回49999995000000,它与求和公式n(n + 1)/ 2一致,并在核心组合上使用以下配置文件运行:

real    0m0.005s
user    0m0.002s
sys     0m0.003s

可能值得检查你的功能,我看不到它们返回这个结果 - 数学选项是一个非常优秀的解决方案;)