65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 实现 Eratosthenes/Sundaram/Atkins 筛法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (17投票s)

2012年11月6日

CPOL

4分钟阅读

viewsIcon

49785

downloadIcon

809

C# 实现 Eratosthenes/Sundarm/Atkins 筛法以生成素数,并比较三种实现之间的性能。

介绍 

本文详细介绍了使用四种著名算法生成素数。本文不讨论算法本身,而是讨论算法的优化实现。

  • 穷举法
  • 埃拉托斯特尼筛法
  • 苏达兰筛法
  • 阿特金筛法

背景  

素数是“公钥密码学 (PKC)”的基础。 让我们深入了解 PKC,我将解释素数是如何使用的。

我们取两个素数 7 和 13,如果我想计算它们的乘积,我们可以轻松计算出 91。现在,假设我有一个数字 91,我想找到一对素数,它们的乘积等于 91;找到它们会比较困难,但最终我们可以找到。如果给定的数字是一个 128 位数字,那么快速找到它的因子将非常困难。

这种“因式分解困难”的素数问题被用作 PKC 的单向函数基础。

使用代码

穷举法 或试除法

穷举法是寻找素数的最简单方法。 为了确定一个数字是否为素数,我们将该数字与从 3 开始的奇数相除,并检查余数,直到奇数的平方小于我们要检查的数字。

如果没有余数,则该数字不是素数,否则就是。 下面的代码片段对此进行了检查。 `totalCount` 初始化为 1,因为 2 是素数,在我们的代码中被忽略。

int totalCount = 1;
bool isPrime = true;
for (long i = 3; i < topCandidate; i += 2)
{
    for (int j = 3; j * j <= i; j += 2)
    {
        if ((i % j) == 0) { isPrime = false; break; }
    }
    if (isPrime) { totalCount++; } else isPrime = true;
}
return totalCount; 

埃拉托斯特尼筛法 

埃拉托斯特尼筛法是寻找素数的古老算法,也是第一个被编写出来的有效算法。

算法本身很简单。 假设我们要找到小于 10 的素数,创建一个长度为 10 的布尔数组,所有值都初始化为 true。 从 2 开始,将 2 的倍数在数组中设置为 false。 这个过程一直持续到数字的倍数小于 10。

最后,布尔数组中值为 true 的所有索引都被视为素数,除了索引 1。

 初始数组                 : 1  1  1  1  1  1  1  1  1 1

第一次遍历 (2 的倍数) :  1  1  1  0  1  0  1  0  1 0

第二次遍历 (3 的倍数) :  1  1  1  0  1  0  1  0  0 0

第三次遍历 (4 的倍数) :  1  1  1  0  1  0  1  0  0 0  

第四次遍历 (5 的倍数) :  1  1  1  0  1  0  1  0  0 0 

第五次遍历不再可能,因为 6 的倍数大于 10。素数是 2、3、5 和 7。

int totalCount = 0;
BitArray myBA1 = new BitArray(topCandidate + 1);

/* Set all but 0 and 1 to prime status */
myBA1.SetAll(true);
myBA1[0] = myBA1[1] = false;

/* Mark all the non-primes */
int thisFactor = 2;
int lastSquare = 0;
int thisSquare = 0;

while (thisFactor * thisFactor <= topCandidate)
{
    /* Mark the multiples of this factor */
    int mark = thisFactor + thisFactor;
    while (mark <= topCandidate)
    {
        myBA1[mark] = false;
        mark += thisFactor;

    }

    /* Print the proven primes so far */
    thisSquare = thisFactor * thisFactor;
    for (; lastSquare < thisSquare; lastSquare++)
    {
        if (myBA1[lastSquare]) totalCount++;

    }

    /* Set thisfactor to next prime */
    thisFactor++;
    while (!myBA1[thisFactor]) { thisFactor++; }

}
/* Print the remaining primes */
for (; lastSquare <= topCandidate; lastSquare++)
{
    if (myBA1[lastSquare]) { totalCount++; }

} 

此算法比穷举法有效,但像任何其他素数计算算法一样,受到内存限制。

苏达兰筛法

苏达兰筛法比埃拉托斯特尼筛法更有效。 它使用与埃拉托斯特尼筛法类似的筛法原理,但不考虑偶数。

它会划掉筛法 (布尔数组) 中任何形式为 i+j+2ij 且 i+j+2ij <=n 的数字

其中 n 是我们要找出其下所有素数的数字。

           i >= 1 且小于 < n/2

           j >= i 且小于 n

最后,筛法中值为 true 的索引乘以 2 再加 1 即可得到素数。 下面是 SOS 的优化实现。

/* SEIVE */
int maxVal = 0;
int denominator = 0;
for (int i = 1; i < k; i++)
{
    denominator = (i << 1) + 1;
    maxVal = (k - i) / denominator;
    for (int j = i; j <= maxVal; j++)
    {
        myBA1[i + j * denominator] = false;
    }
}
int prime= 0;
for (int i = 1; i < k; i++)
{
    if (myBA1[i])
    {
        totalCount++;
        prime= (i << 1) + 1; 
    }
}

性能优于埃拉托斯特尼筛法,但在内存限制方面,其问题是埃拉托斯特尼筛法的一半。

阿特金筛法

阿特金筛法是对埃拉托斯特尼筛法的改进。 它不通过所有数字来判断其是否为素数,而是依赖于二次方程和模 60 来找出素数/非素数的模式,并在布尔数组中标记它们。

下面的代码实现了阿特金筛法。

int totalCount = 0;
BitArray isPrime = new BitArray(topCandidate + 1);
int squareRoot = (int)Math.Sqrt(topCandidate);
int xSquare = 1, xStepsize = 3;
int ySquare = 1, yStepsize = 3;
int computedVal = 0;

for (int x = 1; x <= squareRoot; x++)
{
    ySquare = 1;
    yStepsize = 3;
    for (int y = 1; y <= squareRoot; y++)
    {
        computedVal = (xSquare << 2) + ySquare;

        if ((computedVal <= topCandidate) && (computedVal % 12 == 1 || computedVal % 12 == 5))
            isPrime[computedVal] = !isPrime[computedVal];

        computedVal -= xSquare;
        if ((computedVal <= topCandidate) && (computedVal % 12 == 7))
            isPrime[computedVal] = !isPrime[computedVal];

        if (x > y)
        {
            computedVal -= ySquare << 1;
            if ((computedVal <= topCandidate) && (computedVal % 12 == 11))
                isPrime[computedVal] = !isPrime[computedVal];
        }
        ySquare += yStepsize;
        yStepsize += 2;
    }
    xSquare += xStepsize;
    xStepsize += 2;
}

for (int n = 5; n <= squareRoot; n++)
{
    if (isPrime[n] == true)
    {
        int k = n * n;
        for (int z = k; z <= topCandidate; z += k)
            isPrime[z] = false;
    }
}

for (int i = 1; i < topCandidate; i++)
{
    if (isPrime[i]) totalCount++;
}
return (totalCount + 2); // 2 and 3 will be missed in Sieve Of Atkins 

上述代码比埃拉托斯特尼筛法更有效,在某些情况下与苏达兰筛法相同或效率稍差。 这是因为对不相关的数字进行了不必要的计算。

假设我们需要找出 10 以下的所有素数。 4x^2+y^2 需要对小于 10 的平方根(即 3)的所有数字进行计算。 Operation 4 * (3^2) + 1 ^ 2 , 4 * (3^2) + 2 ^ 2 ,4 * (3^2) + 3 ^ 2  是不必要的,因为结果大于 10。在正常实现中无法删除这一点。

在提供的代码中,提供了一个优化实现,它将忽略不必要的计算,但同时也更难理解。

兴趣点 

  • 所有算法的更有效实现。
  • 通过使用分页、机器集群来克服内存限制,并能快速找到符合密码学标准的素数。
© . All rights reserved.