【问题标题】:Why are these sieve optimizations breaking my code?为什么这些筛子优化会破坏我的代码?
【发布时间】:2020-06-23 12:57:00
【问题描述】:

我该如何纠正它们以使其正常工作? 我正在尝试根据之前的建议优化我的筛子,但在这两种情况下,代码都会中断:

递增j = j + ( i * 2) 会破坏代码。

显然我和其他人一样缺少一些关于优化的概念。 但总的来说,您只需将素数的所有倍数标记为非素数即可。 下一步是优化。

// prime-3
// sieve implementation
function prime3(n) {
  const sieve = (new Array(n)).fill(true);
  // Only iterate up to the square root n for marking
  for (let i = 2; i * i <= n; i += 1) {
    if (sieve[i]) {
      // recommended optimization breaks the code
      // j = j + ( i * 2 )
      for (let j = i * i; j <= n; j = j + i) {
        sieve[j] = false;
      }
    }
  }
  return makePrimes(sieve, n);
};

function makePrimes(sieve, n) {
  let primes = [];
  for (let i = 2; i < n; i++) {
    if (sieve[i]) {
      primes.push(i);
    }
  }
  return primes;
}
console.log(prime3(100));

【问题讨论】:

  • recommendation was" 到特殊情况 2,从 3 开始 将候选者加 2 并使用 @ 的内部循环增量987654326@ 而不仅仅是 i" -- 提供 你从 3 开始并将候选者增加 2,也就是说。 :) 即优化是按特定顺序进行的,您不能跳过一个并使用另一个确实取决于第一个!
  • 谢谢,注意到了。我还需要单步执行代码以查看它在做什么,而且比它更明显。创建一个带有标记的筛子是 O(n) 时间复杂度你能解释一下所有的大 O 吗?
  • 是大 O 时间复杂度,请参阅my answer 中的最后一句话来回答您的其他问题。它提供链接。 π(n)* π(√n) 表示:n 下的每个素数都经过sqrt(n) 下所有素数的整除性测试。 π(n) =~ n/log n(详见链接)。至于复合材料,请参见其他链接。整体复杂度是两者中的最大值。如果您在处理完这些链接后会有一些具体问题,请在那里问他们。 :)
  • 或者您的意思是埃拉托色尼筛网的复杂性?为此,请参见例如stackoverflow.com/a/2582776/849891。 (以上评论指的是最优试划分筛)。

标签: javascript primes sieve-of-eratosthenes sieve


【解决方案1】:

您犯了一个简单的错误,即没有准备筛子。您应该消除所有 2 的倍数:

function makeSieve(n){
  const sieve = new Array(n).fill(true);
  for(let i = 2; i < sieve.length; i += 2){
    sieve[i] = false;
  }
}

现在,当您标记非素数时,您可以增加 i * 2

例如

3, 6, 9, 12, 15, 18, 21

会变成

3, 9, 15, 21

【讨论】:

    【解决方案2】:

    你的优化需要先去掉偶数后应用(除了2)。 因为当i==2 时,您通过增加i*2 有效地跳过了所有偶数。

    这是一个工作代码:

    // prime-3
    // sieve implementation
    function prime3(n) {
      let sieve = (new Array(n)).fill(true);
      for (let i = 4; i < n; i+=2) {
        sieve[i] = false;
      }
      
      // Only iterate up to the square root n for marking
      for (let i = 2; i * i <= n; i += 1) {
        if (sieve[i]) {
          // now it works
          // j = j + ( i * 2 )
          for (let j = i * i; j <= n; j = j + i*2) {
            sieve[j] = false;
          }
        }
      }
      return makePrimes(sieve, n);
    };
    
    function makePrimes(sieve, n) {
      let primes = [];
      for (let i = 2; i < n; i++) {
        if (sieve[i]) {
          primes.push(i);
        }
      }
      return primes;
    }
    console.log(prime3(100));
    
    

    编辑

    刮掉这个。进一步的测试确实表明 prime3 比简单的筛子快 3 倍。

    代码虽然有效,但似乎有太多技巧要做,并且引入了额外的计算和混乱。在我的比较中,一个简单的筛选代码(如下所示)执行答案中的代码。同样,KISS 是原则。

    原始答案中的脚本用了 317 毫秒来筛选 1M 个数字,而简单的筛选只用了 241 毫秒。

    function simpleSieve(n) {
      let a = new Array(n)
      let answer = []
      let p
      for (let i = 2; i < n; i ++) {
        a[i] = i
      }
      for (let i = 2; i < n; i++) {
        if (a[i]) {
          answer.push(a[i])
          p = a[i]
          for(let j = p; j < n;  j += p) {
            a[j] = 0
          }
        }
      }
      return answer
    }
    

    编辑 2

    用 cpp 和 prime3 重新测试确实比简单的筛子快了大约 3 倍:

    p3:
    n = 100000000, t = 866717 microseconds.
    n = 200000000, t = 2354425 microseconds.
    n = 300000000, t = 3689165 microseconds.
    n = 400000000, t = 4950224 microseconds.
    n = 500000000, t = 6119779 microseconds.
    n = 600000000, t = 7375925 microseconds.
    n = 700000000, t = 8647293 microseconds.
    n = 800000000, t = 10477116 microseconds.
    n = 900000000, t = 11589894 microseconds.
    n = 1000000000, t = 12806997 microseconds.
    simple:
    n = 100000000, t = 2316019 microseconds.
    n = 200000000, t = 6063749 microseconds.
    n = 300000000, t = 9783295 microseconds.
    n = 400000000, t = 13315450 microseconds.
    n = 500000000, t = 16640474 microseconds.
    n = 600000000, t = 20282461 microseconds.
    n = 700000000, t = 24219469 microseconds.
    n = 800000000, t = 29203786 microseconds.
    n = 900000000, t = 32965856 microseconds.
    n = 1000000000, t = 37694084 microseconds.
    

    为了完整起见,此处代码:

    void simpleSieve(int n) {
      bool *a = (bool *)calloc(n, sizeof(bool));
      int p;
      memset(a, true, sizeof(bool) * n);
      for (int i = 2; i < n; i++) {
        if (a[i]) {
          p = i;
          for (int j = p; j < n; j += p) {
            a[j] = 0;
          }
        }
      }
      free(a);
    }
    
    void prime3(int n) {
      bool *sieve = (bool*)calloc(n, sizeof(bool));
      sieve[2] = true;
      for (int i = 3; i < n; i+=2) {
        sieve[i] = true;
      }
      int step;
      for (int i = 2; i * i <= n; i += 1) {
        if (sieve[i]) {
          step = i*2;
          for (int j = i * i; j <= n; j = j + step) {
            sieve[j] = false;
          }
        }
      }
      free(sieve);
    }
    

    【讨论】:

    • 用所有真值创建一个数组然后标记所有偶数似乎效率低下。希望有一种方法可以初始化数组并标出所有事件。
    • 循环只是 O(n)。量级小于整体复杂度,即 O(nlog(log(N)))
    • 为什么将 4,6,8,10,... 的倍数标记为复合?它们不是已经标记为 2 的复合倍数了吗? ---你为什么要检查2以上的偶数是否是素数?你不知道他们不是吗? ---你为什么将2标记为复合,然后标记为素数,然后检查它是否是素数?你不知道 2 是素数吗?
    • 它们在问题中没有被标记为复合,因为增加了i*2,最小的i2。最后一个为什么是有效的。我更新了我的答案。
    • 还编辑了我的答案以添加标准的简单筛子
    【解决方案3】:

    实际上优化对我有用:

     // prime-3
        // sieve implementation
        function prime3 (n) {
          const sieve = (new Array(n)).fill(true);
        
          // Only iterate up to the square root n for marking
          for (let i = 2; i * i <= n; i += 1) {
            if (sieve[i]) {
        
              // recomended optimizations break the code
              // j = i * i 
              // j = j + ( i * 2 )
              for (let j = i * i; j <= n; j = j+i) {
                sieve[j] = false;
              }
            }
          }
          return makePrimes(sieve, n);
        };
        
        function makePrimes(sieve, n){
          let primes = [];
          for (let i = 2; i < n; i++) {
            if(sieve[i]) {
              primes.push(i);
            }
          }
          return primes;
        }
        
        console.log(prime3(100));

    但是您的j = j + ( i * 2 )“优化”实际上破坏了算法。结果将包含非素数。这里有一个 sn-p 来检查:

    function prime3 (n) {
          const sieve = (new Array(n)).fill(true);
        
          // Only iterate up to the square root n for marking
          for (let i = 2; i * i <= n; i += 1) {
            if (sieve[i]) {
        
              // recomended optimizations break the code
              // j = i * i 
              // j = j + ( i * 2 )
              for (let j = i * i; j <= n; j = j+(i*2)) {
                sieve[j] = false;
              }
            }
          }
          return makePrimes(sieve, n);
        };
        
        function makePrimes(sieve, n){
          let primes = [];
          for (let i = 2; i < n; i++) {
            if(sieve[i]) {
              primes.push(i);
            }
          }
          return primes;
        }
        
        console.log(prime3(100));

    【讨论】:

      猜你喜欢
      • 2012-10-08
      • 2021-02-01
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      • 2017-12-04
      • 1970-01-01
      • 2022-07-05
      • 2018-02-08
      相关资源
      最近更新 更多