当前位置: 首页 > 知识库问答 >
问题:

改进后埃拉托斯特尼筛的运行速度变慢

锺玺
2023-03-14

我试图通过避免划掉重复的素数倍数来改进埃拉托斯特尼的基本筛分算法,但结果比我预期的要糟糕

我已经实现了两种返回范围内质数的方法[2… max)

public static List<int> Sieve22Max_Basic(int n) {
    var primes = new List<int>();
    var sieve = new BitArray(n, true); // default all number are prime
    //int crossTotal = 0;

    int sqrt_n = (int)Math.Sqrt(n) + 1;
    for (int p = 2; p < sqrt_n; ++p) {
        if (sieve[p]) {
            primes.Add(p);
            //var cross = new List<int>();
            int inc = p == 2 ? p : 2 * p;
            for (int mul = p * p; mul < n; mul += inc) {
                // cross out multiple of prime p
                // cross.Add(mul);
                //++crossTotal;
                sieve[mul] = false;
            }

            //if (cross.Count > 0)
            //    Console.WriteLine($"Prime {p}, cross out: {string.Join(' ', cross)}");
        }
    }
    //Console.WriteLine($"crossTotal: {crossTotal:n0}");

    for (int p = sqrt_n; p < n; ++p)
        if (sieve[p])
            primes.Add(p);

    return primes;
}

运行< code > sieve 22 max _ Basic(100),查看一些倍数是否大于1(例如:< code>45,75,63)

Prime 2, cross out: 4 6 8 ... 96 98
Prime 3, cross out: 9 15 21 27 33 39 45 51 57 63 69 75 81 87 93 99
Prime 5, cross out: 25 35 45 55 65 75 85 95
Prime 7, cross out: 49 63 77 91

然后,我尝试通过使用存储每个数字的最小素数spd)的数组来改进。

45 = 3 x 5      // spd[45] = 3
75 = 3 x 5 x 5  // spd[75] = 3
63 = 3 x 3 x 7  // spd[63] = 3

当遍历素数p的倍数时,我不会划掉具有< code>spd[mul]的数< code>mul

public static List<int> Sieve22Max_Enh(int n) {
    var sieve = new BitArray(n, true);
    var spd = new int[n];
    for (int i = 0; i < n; ++i) spd[i] = i;
    
    var primes = new List<int>();
    //int crossTotal = 0;

    int sqrt_n = (int)Math.Sqrt(n) + 1;
    for (int p = 2; p < sqrt_n; ++p) {
        if (sieve[p]) {
            primes.Add(p);

            //var cross = new List<int>();
            int inc = p == 2 ? 1 : 2;
            for (long mul = p; mul * p < n; mul += inc) {
                if (spd[mul] >= p) {
                    sieve[(int)(mul * p)] = false;
                    spd[mul * p] = p;
                    //++crossTotal;
                    //cross.Add((int)(mul * p));
                }
            }
            //if (cross.Count > 0)
            //    Console.WriteLine($"Prime {p}, cross out: {string.Join(' ', cross)}");
        }
    }
    //Console.WriteLine($"crossTotal: {crossTotal:n0}");

    for (int p = sqrt_n; p < n; ++p)
        if (sieve[p])
            primes.Add(p);

    return primes;
}

我在我的笔记本电脑上测试(核心i7 - 2.6 Ghz),n = 10亿

< code>Sieve22Max_Basic只需6s,而< code>Sieve22Max_Enh需要10s以上才能完成

var timer = new Stopwatch();
int n = 1_000_000_000;

timer.Restart();
Console.WriteLine("==== Sieve22Max_Basic ===");
var list = Sieve22Max_Basic(n);
Console.WriteLine($"Count: {list.Count:n0}, Last: {list[list.Count - 1]:n0}, elapsed: {timer.Elapsed}");

Console.WriteLine();

timer.Restart();
Console.WriteLine("==== Sieve22Max_Enh ===");
list = Sieve22Max_Enh(n);
Console.WriteLine($"Count: {list.Count:n0}, Last: {list[list.Count - 1]:n0}, elapsed: {timer.Elapsed}");

你可以试试 https://onlinegdb.com/tWfMuDDK0

它使什么变慢?

共有1个答案

赵骏奇
2023-03-14

比较原始版本和改进版本中的两个循环。

原件:

int inc = p == 2 ? p : 2 * p;
for (int mul = p * p; mul < n; mul += inc) {
  sieve[mul] = false;
}

改进:

int inc = p == 2 ? 1 : 2;
for (long mul = p; mul * p < n; mul += inc) {
  if (spd[mul] >= p) {
    sieve[(int)(mul * p)] = false;
    spd[mul * p] = p;
  }
}

一些意见:

  • 两个循环运行相同数量的迭代。
  • 对于每次迭代,原始循环都会执行三个非常快速的操作:1)更改BitArray中的一个值,mul=inc并检查mul

总而言之,你的第二个改进循环的每次迭代在计算上都“更重”。这就是你的改进版速度慢的原因。

 类似资料:
  • 我正在尝试编写一个程序来实现对埃拉托西的筛选。我可以从2到任何给定的结束编号,但我们正在处理的赋值要求我们输入起始值。我完全被卡住了。我试过很多不同的代码,但它总是给我奇怪的答案。 我的起点是起始值,终点是结束值。我基本上想找到这个范围的素数。谢谢!!!

  • 做一个简单的筛子很容易: 但是当N非常大并且我无法在内存中持有这种数组时,该怎么办?我已经查找了分段筛方法,它们似乎涉及查找素数,直到sqrt(N),但我不明白它是如何工作的。如果 N 非常大(比如 10^18)怎么办?

  • 在一个类作业中,我被要求用Java编写Eratostenes筛选代码,我的代码效率非常低。运行时间不长,但我相当肯定,除了像我一样列出所有内容外,还有其他循环的空间。。 这是我的代码: 基本上,我所做的是将所有不是质数的元素设置为true… 所以我的主要2个问题是1。有没有办法实现一个循环,使代码更短2。如何打印此数组中所有为 true(质数)的元素

  • 我在我的一个班级里做的一个作业,我们必须实现一个厄拉多塞的筛子。我已经尝试了七次来得到一个有效的代码,并且尝试了整合我研究过的许多解决方案。我终于有一个可以输出数字的了。不幸的是,它同时打印合数和质数,但不打印2。 我的代码如下: 我怀疑我的循环有问题。我修复了前两个循环的和变量,以便它从2开始打印出来,问题似乎是在我将数组初始化为true后,它没有将合数标记为。 提前感谢你的帮助。

  • 我正在尝试解决spoj上的Prime Path问题,我正在尝试理解在github上找到的解决方案。解决这个问题的广义逻辑是生成所有四位数素数,并添加一个边,如果我们可以通过更改一个数字从一个素数转到下一个素值。我找到的这个解决方案使用筛子来生成所有素数。与此解决方案中的筛分功能相比,维基上的稀土筛分功能有所不同。只需要帮助了解以下代码中筛分函数的变化: 这里的筛选函数计算是什么?我无法理解为什么作

  • 我正在解决Project Euler问题10,我可以使用Eratosthenes Sieve来完成,但现在我想进一步优化代码。 考虑到所有大于3的质数都是< code>6k 1或< code>6k-1的形式,我只将数组中的那些值设置为真,但并不是所有这种形式的数都是质数,所以我必须筛选这些值并删除非质数,我的代码如下: 那么,我怎样才能优化我筛选出的较少数字的代码呢?例如,如果我的数字是5,那么像