需要帮助优化算法 - 所有素数的总和低于200万

时间:2010-10-30 06:23:24

标签: c

我正在尝试Project Euler问题。

我正在寻找2,000,000以下所有素数的总和。

这就是我的......

int main(int argc, char *argv[]) {


    unsigned long int sum = 0;

    for (unsigned long int i = 0; i < 2000000; i++) {


        if (isPrime(i)) {
            sum += i;
        }
    }

    printf("sum => %lu\n", sum);


}

int isPrime(int num) {

    if (num < 2) {
        return 0;
    }

    for (int i = 2; i < num; ++i) {
        if (num % i == 0) {
            return 0;
        }
    }
    return 1;
}

运行需要很长时间,因此它不满足Euler问题的运行时间的一分钟规则

当我以10的限制运行时,它会得到正确的答案,17就像在问题中一样。

我的猜测是有一些算法可以减少正在进行的工作。问题是,我不知道它是什么。

有人可以指出我正确的方向吗?

感谢。

8 个答案:

答案 0 :(得分:8)

i22000000(或任何上限),一旦确定i为素数,您就会知道{2i, 3i, 4i, ..., ni}是不是素数,ni <= 2000000

您不需要测试这些数字 - 您知道它们不是素数。

当您浏览testNumbers数组并从此列表中删除数字时,您现在知道的数字不是素数,您可以显着减少实际需要测试的数字。

是的,这是Eratosthenes的筛子。

答案 1 :(得分:2)

你可以缩短寻找素数的时间。有百万种方法可以做到这一点。 我认为你不需要测试直到num,但只需要测试sqrt(num),但这只会对你有所帮助(在实际素数上)

有一些统计方法可以检查素数是否实际上是素数更快,而且只能在2 ^中错误一次....

找到素数的最快方法是来自印度的研究人员,但我找不到链接。

你也可以看一下:

Wiki

答案 2 :(得分:2)

您可以通过传递到目前为止发现的素数数组来加速isPrime(int num)函数,并检查num是否是这些素数的倍数。此外,您无需循环到num,只需sqrt(num)

答案 3 :(得分:0)

使用Atkin筛,它是Eratosthenes link筛的优化版本。

答案 4 :(得分:0)

<强>提示: 使用BitArray和seive方法。

如果你无法弄清楚,请参阅code。代码是Java的,但不难转换为C.

答案 5 :(得分:0)

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

struct prime_list_t {
    long                n;
    struct prime_list_t *next;
};

void add_prime_list_t(struct prime_list_t** list, long n);
void free_prime_list_t(struct prime_list_t** list);
long count_prime_list_t(struct prime_list_t* list);
long last_prime_list_t(struct prime_list_t* list);

/* if one of the primes in list divides n, is not a prime */
int is_prime(struct prime_list_t* pl, long n) {
    struct prime_list_t* p;

    if(pl == NULL) {
        abort();
    }
    p = pl;
    while(p != NULL) {
        if(n % p->n == 0) {
            return 1;
        }
        p = p->next;
    }
    return 0;
}

int main() {
    struct prime_list_t* pl = NULL;
    struct prime_list_t* p;
    long n_up = 2000000;
    long long p_sum = 0;
    long ppr;
    int done;

    /* add first prime number */
    add_prime_list_t(&pl, 2);

    /* from now on add to this list up to the number of items */
    done = 0;
    do {
        /* get the last prime plus one */
        ppr = last_prime_list_t(pl) + 1;
        while(is_prime(pl, ppr) != 0) {
            ppr++;
            if(ppr >= n_up) {
                /* hit the upper limit */
                done = 1;
                break;
            }
        }
        if(done) {
            break;
        }

        /* ppr is prime */
        add_prime_list_t(&pl, ppr);

        /* display status */
        printf("%ld numbers largest prime %ld\r", count_prime_list_t(pl), last_prime_list_t(pl));
    } while(!done);

    p = pl;
    p_sum = 0;
    while(p) {
        // printf("%d ", p->n);
        p_sum += p->n;
        p = p->next;
    }
    printf("\nsum: %I64d\n", p_sum);


    free_prime_list_t(&pl);
    return 0;
}

void add_prime_list_t(struct prime_list_t** list, long n) {
    struct prime_list_t* node;

    if(list == NULL) {
        abort();
    }

    node = (struct prime_list_t*)malloc(sizeof(struct prime_list_t));
    if(node == NULL) {
        abort();
    }

    node->n = n;
    node->next = NULL;

    if(*list == NULL) {
        *list = node;
    }
    else {
        struct prime_list_t* p;

        p = *list;
        while(p->next != NULL) {
            p = p->next;
        }
        p->next = node;
    }
}

void free_prime_list_t(struct prime_list_t** list) {
    struct prime_list_t* node;

    if(list == NULL) {
        return;
    }
    node = *list;
    while(node != NULL) {
        struct prime_list_t* p;

        p = node;
        node = node->next;
        free(p);
    }
    *list = NULL;
}

long count_prime_list_t(struct prime_list_t* list) {
    long c;
    struct prime_list_t* p;

    for(p = list, c = 0; p != NULL; p = p->next, c++)
        ;
    return c;
}

long last_prime_list_t(struct prime_list_t* list) {
    long n;
    struct prime_list_t* p;

    n = 0;
    p = list;
    while(p->next != NULL) {
        p = p->next;
    }
    return p->n;
}

这将是输出:

$kpwr
148933 numbers largest prime 1999993
sum: 142913828922

$

答案 6 :(得分:0)

供参考(如果你在这里,那么你已经在尝试查找答案),我quote Lucy_Hedgehog(在PE开发团队):

  

这是一种比筛子更有效的解决方案   埃拉托色尼。它源自counting primes的类似算法。该   优点是没有必要找到所有要找到的素数   他们的总和。

     

主要思想如下:设S(v,m)为整数的和   范围2..v在筛选后保留所有质数小或相等   比米。也就是说,S(v,m)是直到v的整数之和   素数或素数大于m的乘积。

     

如果p不是素数或v小于p,则S(v,p)等于S(v,p-1)   * p。否则(p prime,p * p&lt; = v)S(v,p)可以通过找到在用p筛选时去除的整数之和从S(v,p-1)计算。   如果它是p的乘积,则在此步骤中删除整数   另一个没有小于p的除数的整数。这可以   表示为

     

$ S(v,p)= S(v,p-1) - p(S(v / p,p-1) - S(p-1,p-1))。$

     

动态编程可用于实现此目的。这足够了   计算可表示为的所有正整数v的S(v,p)   对于某些整数k和所有$ p \ leq \ sqrt v $。

的floor(n / k)
def P10(n):
    r = int(n**0.5)
    assert r*r <= n and (r+1)**2 > n
    V = [n//i for i in range(1,r+1)]
    V += list(range(V[-1]-1,0,-1))
    S = {i:i*(i+1)//2-1 for i in V}
    for p in range(2,r+1):
        if S[p] > S[p-1]:  # p is prime
            sp = S[p-1]  # sum of primes smaller than p
            p2 = p*p
            for v in V:
                if v < p2: break
                S[v] -= p*(S[v//p] - sp)
    return S[n]
     

该算法的复杂性约为$ O(n ^ {0.75})$,需要9 ms   找到解决方案。

这些提示与finding totient sum类似,是Dirichlet hyperbola method的应用。

答案 7 :(得分:-1)

如果你可以处理case的{​​{1}}测试,那么从三开始循环并将2而不是i+=2减少循环迭代一半