如何找到0-100之间的素数?

IT技术 javascript math primes
2021-02-05 12:15:57

在 Javascript 中,我如何找到 0 - 100 之间的素数?我已经考虑过了,我不知道如何找到它们。我想过做 x % x 但我发现了明显的问题。这就是我到目前为止所拥有的:但不幸的是,这是有史以来最糟糕的代码。

var prime = function (){
var num;
for (num = 0; num < 101; num++){
    if (num % 2 === 0){
        break;
    }
    else if (num % 3 === 0){
        break;
    }
    else if (num % 4=== 0){
        break;
    }
    else if (num % 5 === 0){
        break;
    }
    else if (num % 6 === 0){
        break;
    }
    else if (num % 7 === 0){
        break;
    }
    else if (num % 8 === 0){
        break;
    }
    else if (num % 9 === 0){
        break;
    }
    else if (num % 10 === 0){
        break;
    }
    else if (num % 11 === 0){
        break;
    }
    else if (num % 12 === 0){
        break;
    }
    else {
        return num;
    }
}
};
console.log(prime());
6个回答

下面是 JavaScript 中筛选实现的示例:

function getPrimes(max) {
    var sieve = [], i, j, primes = [];
    for (i = 2; i <= max; ++i) {
        if (!sieve[i]) {
            // i has not been marked -- it is prime
            primes.push(i);
            for (j = i << 1; j <= max; j += i) {
                sieve[j] = true;
            }
        }
    }
    return primes;
}

然后getPrimes(100)将返回一个包含 2 到 100(含)之间的所有素数的数组。当然,由于内存限制,您不能将其用于大参数。

Java 实现看起来非常相似。

@caub - 这是一个清晰的问题(在我看来,这会影响可维护性)。声明sieve为数组表示正在通过数字索引存储和检索值。维护人员(可能希望修改代码以使用 做其他事情sieve)会知道这一点,sieve.length并且可以使用数组方法。至于最优性,如果一个对象的性能明显优于这里的数组,我会感到惊讶。事实上,数组可能更快(例如,请参见此处)。
2021-03-12 12:15:57
@BubblewareTechnology -<<运算符将左操作数左移一位(必要时将其转换为整数值后)。这只是一个快速的方法来乘以2的内环只套sieve[j]true了的倍数i这样做的原因是没有倍数i可以是素数。
2021-03-16 12:15:57
@OmShankar 为什么n^2根据这个答案(和这个评论),它应该是通常的n*log log n(不是 O(n) btw)。
2021-03-19 12:15:57
您的算法的复杂性更多:O(n^2),其中Eratosthenes筛分O(n)
2021-03-25 12:15:57
很好 - 你能解释一下 j for 循环吗?我找不到有关“<<”部分的文档。
2021-04-03 12:15:57

这是我解决它的方法。把它从 Java 改写成 JavaScript,所以如果有语法错误,请见谅。

function isPrime (n)
{
    if (n < 2) return false;

    /**
     * An integer is prime if it is not divisible by any prime less than or equal to its square root
     **/

    var q = Math.floor(Math.sqrt(n));

    for (var i = 2; i <= q; i++)
    {
        if (n % i == 0)
        {
            return false;
        }
    }

    return true;
}

如果一个数 ,n不能被除 1 和它本身以外的任何其他数整除,则它是素数。此外,检查数字 [2, sqrt(n)] 就足够了。

即使 n = 10,000,000 似乎也能工作,不确定什么是“小”哈哈
2021-03-11 12:15:57
@devonJS 当 n=10,000,000 时,它会在第一次迭代时停止,因为它可以被 2 整除,很快就会发现 10,000,000 不是质数。尽管如此,它可以非常快地找到 2,147,483,647 以及 67,280,421,310,721 并没有太多麻烦,尽管它似乎无法在 Chrome 中处理 170,141,183,460,469,231,731,687,303,7215 的数字只是因为 % 。
2021-03-11 12:15:57
仅供参考,此解决方案是伪多项式。除非您知道 n 会很小,否则不要使用它。
2021-03-20 12:15:57
仅供参考,这是该线程中迭代次数最少的算法。但是,是的,我同意越大n--> 找到一个更好的(并为发现新的质数赢得一笔钱:))
2021-03-26 12:15:57
而不是(int) Math.sqrt (n)use parseInt(Math.sqrt(n)),通过编辑更正。[abs()](http://www.w3schools.com/jsref/jsref_abs.asp)也可以测试使用负数。此外,根据逻辑, theif (n < 2)应该返回 true,因为它是一个质数。
2021-04-03 12:15:57

这是这个脚本的现场演示:http : //jsfiddle.net/K2QJp/

首先,创建一个函数来测试单个数字是否为素数。如果你想扩展 Number 对象,你可以,但我决定让代码尽可能简单。

function isPrime(num) {
    if(num < 2) return false;
    for (var i = 2; i < num; i++) {
        if(num%i==0)
            return false;
    }
    return true;
}

该脚本遍历小于该数字的 2 到 1 之间的每个数字,并测试是否有任何数字没有余数(如果将数字除以增量)。如果有任何没有余数,它不是质数。如果数字小于 2,则它不是质数。否则,它是主要的。

然后使用 for 循环遍历数字 0 到 100 并使用该函数测试每个数字。如果是质数,则将数字输出到日志中。

for(var i = 0; i < 100; i++){
    if(isPrime(i)) console.log(i);
}
您提出的代码未优化,i必须停在sqrt(num)
2021-03-14 12:15:57
@Mike - 我不知道你为什么这么说。我验证了输出并正确记录。对于不需要使用控制台窗口的版本,请查看此处@Gray / @argshook - 该行用于检查是否num可以被i我们正在检查的当前数字整除如果它可以被任何小于当前数的数整除,我们就返回false它,这意味着它不是素数。
2021-03-23 12:15:57
@EvanKennedy:抱歉,您必须为此责怪控制台。你的回答片段 // for(var i = 0; i < 100; i++){ if(isPrime(i)) console.log(i); },不记录正确的结果。
2021-03-28 12:15:57
为什么我们检查数字直到最后,如果我们检查到中间我们降低时间复杂度.... for (var i = 2; i <= num/2; i++) { ....
2021-04-04 12:15:57
@argshook 想发表此评论,但他的代表太低,所以我代表他们添加。“不应该 isPrime() 循环检查 ifnum % i !== 0而不是num % i == 0?”
2021-04-10 12:15:57

无论使用什么语言,在一个范围内寻找素数的最好和最容易获得的方法之一就是使用筛法

不会给你代码,但这是一个很好的起点。

对于像您这样的小范围,最有效的方法是预先计算数字。

我稍微修改了Sieve of Sundaram算法以减少不必要的迭代,它似乎非常快。

这个算法实际上比这个主题下最被接受的@Ted Hopp 的解决方案两倍解决 0 - 1M 之间的 78498 个素数在 Chrome 55 中需要 20~25 毫秒,在 FF 50.1 中需要 < 90 毫秒。另外@vitaly-t 的 get next prime 算法看起来很有趣,但结果也慢得多。

这是核心算法。人们可以应用分段和线程来获得极好的结果。

"use strict";
function primeSieve(n){
  var a = Array(n = n/2),
      t = (Math.sqrt(4+8*n)-2)/4,
      u = 0,
      r = [];
  for(var i = 1; i <= t; i++){
    u = (n-i)/(1+2*i);
    for(var j = i; j <= u; j++) a[i + j + 2*i*j] = true;
  }
  for(var i = 0; i<= n; i++) !a[i] && r.push(i*2+1);
  return r;
}

var primes = [];
console.time("primes");
primes = primeSieve(1000000);
console.timeEnd("primes");
console.log(primes.length);

循环限制解释:

就像 Erasthotenes 的筛选一样,Sundaram 的筛选算法也会从列表中划掉一些选定的整数。要选择要划掉哪些整数,规则是 i + j + 2ij ≤ n,其中 i 和 j 是两个索引,n 是总元素的数量。一旦我们划掉每一个 i + j + 2ij,剩下的数字就会被加倍和奇数化 (2n+1) 以显示素数列表。最后阶段实际上是偶数的自动折扣。它的证明在这里得到了很好的解释

Sundaram 的筛选只有在正确选择循环索引开始和结束限制的情况下才会很快,这样就不会(或最少)消除非素数的冗余(多重)。由于我们需要 i 和 j 值来计算要划掉的数字,因此 i + j + 2ij 到 n 让我们看看如何处理。

i)所以我们必须找到 i 和 j 相等时可以取的最大值。即 2i + 2i^2 = n。我们可以使用二次公式轻松求解 i 的正值,即t = (Math.sqrt(4+8*n)-2)/4,

j)内循环索引 j 应该从 i 开始并运行到它可以与当前 i 值一致的点。仅此而已。由于我们知道 i + j + 2ij = n,这可以很容易地计算为u = (n-i)/(1+2*i);

虽然这不会完全消除冗余交叉,但它会“极大地”消除冗余。例如,对于 n = 50(检查最多 100 的素数)而不是 50 x 50 = 2500,我们总共将只进行 30 次迭代。很明显,这个算法不应该被视为 O(n^2) 时间复杂度的算法。

i  j  v
1  1  4
1  2  7
1  3 10
1  4 13
1  5 16
1  6 19
1  7 22  <<
1  8 25
1  9 28
1 10 31  <<
1 11 34
1 12 37  <<
1 13 40  <<
1 14 43
1 15 46
1 16 49  <<
2  2 12
2  3 17
2  4 22  << dupe #1
2  5 27
2  6 32
2  7 37  << dupe #2
2  8 42
2  9 47
3  3 24
3  4 31  << dupe #3
3  5 38
3  6 45
4  4 40  << dupe #4
4  5 49  << dupe #5

其中只有5个重复。22、31、37、40、49。对于 n = 100,冗余度约为 20%,但对于 n = 10M,它增加到 ~300%。这意味着 SoS 的进一步优化有可能随着 n 的增长更快地获得结果。因此,一个想法可能是分段并始终保持 n 小。

所以好吧..我决定更进一步地进行这个任务。

经过对重复交叉的一些仔细检查后,我意识到一个事实,除了i === 1情况,如果ij指数值中的一个或两个在 4,7,10,13,16,19.. . 系列,生成重复的交叉。然后仅在 时才允许内循环转动i%3-1 !== 0,从而进一步减少了循环总数的 35-40%。因此,例如对于 1M 个整数,嵌套循环的总轮数从 1.4M 下降到 1M。哇..!我们在这里谈论的几乎是 O(n)。

我刚刚做了一个测试。在 JS 中,仅一个计数到 1B 的空循环就需要 4000 毫秒。在以下修改后的算法中,找到最多 100M 的素数需要相同的时间。

我还实现了这个算法的分割部分来推送给工作人员。这样我们也可以使用多个线程。但该代码稍后将遵循。

因此,让我向您介绍经过修改的 Sundaram 筛网可能在不分段时最好。它将使用 Chrome V8 和 Edge ChakraCore 在大约 15-20 毫秒内计算 0-1M 之间的素数。

"use strict";
function primeSieve(n){
  var a = Array(n = n/2),
      t = (Math.sqrt(4+8*n)-2)/4,
      u = 0,
      r = [];
  for(var i = 1; i < (n-1)/3; i++) a[1+3*i] = true;
  for(var i = 2; i <= t; i++){
    u = (n-i)/(1+2*i);
    if (i%3-1) for(var j = i; j < u; j++) a[i + j + 2*i*j] = true;
  }
  for(var i = 0; i< n; i++) !a[i] && r.push(i*2+1);
  return r;
}

var primes = [];
console.time("primes");
primes = primeSieve(1000000);
console.timeEnd("primes");
console.log(primes.length);

嗯...最后我想我已经实现了一个筛子(它起源于 Sundaram 的巧妙 Sieve),这样它是我可以在互联网上找到的最快的 JavaScript 筛子,包括“Eratosthenes 的 Odds only Sieve”或“阿特金斯筛”。这也是为 web 工作者准备的,多线程。

这样想。在这台不起眼的单线程 AMD PC 中,JS 需要 3,300 毫秒才能计算到 10^9,而以下优化的分段 SoS 将仅在 14,000 毫秒内为我提供 50847534 个素数,达到 10^9。这意味着只是计数操作的 4.25 倍。我认为它令人印象深刻。

你可以自己测试一下;

console.time("tare");
for (var i = 0; i < 1000000000; i++);
console.timeEnd("tare");

在这里,我向您介绍最好的 Sundaram 分段 Seieve。

"use strict";
function findPrimes(n){
  
  function primeSieve(g,o,r){
    var t = (Math.sqrt(4+8*(g+o))-2)/4,
        e = 0,
        s = 0;
    
    ar.fill(true);
    if (o) {
      for(var i = Math.ceil((o-1)/3); i < (g+o-1)/3; i++) ar[1+3*i-o] = false;
      for(var i = 2; i < t; i++){
        s = Math.ceil((o-i)/(1+2*i));
        e = (g+o-i)/(1+2*i);
        if (i%3-1) for(var j = s; j < e; j++) ar[i + j + 2*i*j-o] = false;
      }
    } else {
        for(var i = 1; i < (g-1)/3; i++) ar[1+3*i] = false;
        for(var i = 2; i < t; i++){
          e = (g-i)/(1+2*i);
          if (i%3-1) for(var j = i; j < e; j++) ar[i + j + 2*i*j] = false;
        }
      }
    for(var i = 0; i < g; i++) ar[i] && r.push((i+o)*2+1);
    return r;
  }
  
  var cs = n <= 1e6 ? 7500
                    : n <= 1e7 ? 60000
                               : 100000, // chunk size
      cc = ~~(n/cs),                     // chunk count
      xs = n % cs,                       // excess after last chunk
      ar = Array(cs/2),                  // array used as map
  result = [];
  
  for(var i = 0; i < cc; i++) result = primeSieve(cs/2,i*cs/2,result);
  result = xs ? primeSieve(xs/2,cc*cs/2,result) : result;
  result[0] *=2;
  return result;
}


var primes = [];
console.time("primes");
primes = findPrimes(1000000000);
console.timeEnd("primes");
console.log(primes.length);

我不确定它是否会比这更好。我很想听听你的意见。

@Redu,我没有“讨厌筛分的东西”;在我看来,这一切都只是调整算法以获得最佳性能,包括划分工作,以便它可以有效地进行多处理,如果这是一个选项。我挖掘出这些“埋藏已久”的话题,因为虽然有很多优秀的程序员,但很多人并不完全了解是什么让 SoE 真正快速运转,以及为了大众的教育;) 为此,分段已经除了将工作分成几部分之外的其他好处;它还有助于缓存关联性,因为理想情况下,每个段应该 <= 一个 CPU L1/L2 缓存。
2021-03-22 12:15:57
@Redu,考虑到您提议对 SoS 代码进行进一步优化的建议,我认为您将越来越接近 SoE 算法并没有多大意义,正如我在我对 ComputerScience 算法问题的回答中所述,除了如果你想更深入地了解它。事实上,您为消除某些冗余剔除而进行的优化本质上只是一种相对低效的预剔除方式,以消除必须剔除 3 的因素,为此有更有效的方法。
2021-03-25 12:15:57
@Redu,您建议将我的代码放在沙箱中,并附上解释其工作原理和速度的注释,这是一个很好的建议。这个问题的答案已经达到极限,我们“远远超出了我们的任务”,而当问题是数百个时,我们已经“远远超出了我们的任务”。我已经按照您的建议将实时代码插入到另一个链接的答案中,按照您的评论。然而,这个答案已经变得太大了,我不想添加太多。我可以在那里添加另一个答案,进一步扩展该答案。我不相信 So 允许制作问题教程吗?建议?
2021-03-31 12:15:57
@Redu,(续)但是,即使您进行更高级别的预剔除和更高效,由于通过所有奇数进行筛选,而不仅仅是通过奇素数,你还没有考虑过“最大轮分解”。换句话说,参考其他“算法”答案,完全优化的 SoS 成为 SoE,您也可以使用页面分段 SoE 并完成它......
2021-03-31 12:15:57
@Redu(续)...我怀疑我可以优化 SoS 以接近“仅赔率”SoE,但没有多大意义,因为它会比 SoE 更复杂,如果我们要添加更多,则更糟可以对 SoE 进行轮分解。
2021-04-01 12:15:57