Eratosthenes筛选的自由Pascal实现

时间:2016-06-02 11:55:08

标签: freepascal sieve-of-eratosthenes

我的老师给了我这样的作业: 使用给定的数字 n ,使用 p < = n 找到最大的素数 p ñ< = 10 ^ 9。 我尝试使用以下函数执行此操作:

Const amax=1000000000
Var i,j,n:longint;
    a:array [1..amax] of boolean;
Function lp(n:longint):longint;
 Var max:longint;
 Begin
  For i:=1 to n do a[i]:=true;
  For i:=2 to round(sqrt(n)) do
   If (a[i]=true) then
    For j:=1 to n div i do
     If (i*i+(j-1)*i<=n) then
      a[i*i+(j-1)*i]:=false;
  max:=0;
  i:=n;
  While max=0 do
   Begin
    If a[i]=true then max:=i;
    i:=i-1;
   End;
  lp:=max;
 End;

这段代码可以完美地用于100万等数字,但是当我尝试n = 10 ^ 9时,程序需要很长时间来打印输出。所以这就是我的问题:有没有办法改进我的代码以降低延迟?或者可能是不同的代码?

1 个答案:

答案 0 :(得分:2)

这里最重要的方面是,不大于n的最大素数必须非常接近n。快速查看The Gaps Between Primes(在The Prime Pages - 总是值得查看与素数有关的一切)表明,对于32位数,素数之间的差距不能大于335.这意味着最大的不大于n的素数必须在[n - 335,n]范围内。换句话说,最多需要检查336个候选人 - 例如通过试验部门 - 这肯定比筛选十亿个数字快得多。

对于此类任务,试验分区是合理的选择,因为要扫描的范围非常小。在我对Prime sieve implementation (using trial division) in C++的回答中,我分析了几种加速它的方法。

Eratosthenes的Sieve也是一个不错的选择,它只需要修改以仅筛选感兴趣的范围而不是从1到n的所有数字。这被称为“筛窗”筛子&#39;因为它只筛选一个窗口。由于窗口很可能包含直到n的平方根的所有素数(即所有素数可能是要扫描的范围内复合材料的潜在最不理想因素),最好是通过一个单独的,简单的Eratosthenes筛子来筛选因子。

首先,我展示了正常(非窗口)筛子的简单再现,作为比较窗口代码的基线。我使用C#来比使用Pascal更清楚地显示算法。

List<uint> small_primes_up_to (uint n)
{
    if (n == uint.MaxValue)
        throw new ArgumentOutOfRangeException("n", "n must be less than UINT32_MAX");

    var eliminated = new bool[n + 1];  // +1 because indexed by numbers

    eliminated[0] = true;
    eliminated[1] = true;

    for (uint i = 2, sqrt_n = (uint)Math.Sqrt(n); i <= sqrt_n; ++i)
        if (!eliminated[i])
            for (uint j = i * i; j <= n; j += i)
                eliminated[j] = true;

    return remaining_unmarked_numbers(eliminated, 0);
}

功能小&#39;在它的名字,因为它不适合筛选大范围;我使用类似的代码(带有一些铃声和口哨声)仅用于筛选更先进的筛子所需的小因子素数。

提取筛选质数的代码同样简单:

List<uint> remaining_unmarked_numbers (bool[] eliminated, uint sieve_base)
{
    var result = new List<uint>();

    for (uint i = 0, e = (uint)eliminated.Length; i < e; ++i)
        if (!eliminated[i])
            result.Add(sieve_base + i);

    return result;
}

现在,窗口版本。一个区别是潜在的最小因子素数需要单独筛选(通过刚才显示的函数),如前所述。另一个不同之处在于给定填料的交叉序列的起点可能位于要筛分的范围之外。如果起点位于窗口开始之前,则需要一些模数魔法才能找到第一个“跳跃”。落在窗户里。从那时起,一切都照常进行。

List<uint> primes_between (uint m, uint n)
{
    m = Math.Max(m, 2);

    if (m > n)  
        return new List<uint>();  // empty range -> no primes

    // index overflow in the inner loop unless `(sieve_bits - 1) + stride <= UINT32_MAX`
    if (n - m > uint.MaxValue - 65521)  // highest prime not greater than sqrt(UINT32_MAX)
        throw new ArgumentOutOfRangeException("n", "(n - m) must be <= UINT32_MAX - 65521");

    uint sieve_bits = n - m + 1;
    var eliminated = new bool[sieve_bits];

    foreach (uint prime in small_primes_up_to((uint)Math.Sqrt(n)))
    {
        uint start = prime * prime, stride = prime;

        if (start >= m)
            start -= m;
        else
            start = (stride - 1) - (m - start - 1) % stride;

        for (uint j = start; j < sieve_bits; j += stride)
            eliminated[j] = true;
    }

    return remaining_unmarked_numbers(eliminated, m);
}

两个&#39; -1&#39;模数计算中的项似乎很奇怪,但它们将逻辑偏差为1,以消除需要映射到0的不方便情况stride - foo % stride == stride

这样,不超过n的最大素数可以这样计算:

uint greatest_prime_not_exceeding (uint n)
{
    return primes_between(n - Math.Min(n, 335), n).Last();
}

这需要不到一毫秒的时间,包括筛选因子素数等等,即使代码不包含任何优化。在我对prime number summing still slow after using sieve的回答中可以找到适用优化的一个很好的概述;使用这里显示的技术,可以在大约半秒钟内筛选出高达10 ^ 9的整个范围。