如何使用6 * k + - 1规则生成Primes

时间:2015-08-05 16:16:31

标签: java optimization primes sieve

我们知道可以使用以下方法生成3以上的所有素数:

6 * k + 1
6 * k - 1

但是,我们从上述公式生成的所有数字都不是素数。

For Example:    
6 * 6 - 1 = 35 which is clearly divisible by 5.

为了消除这些条件,我使用筛选方法并删除了数字,这些数字是从上面公式生成的数字的因子。

使用事实:

  

如果一个数字没有素数因素,那么它就是素数。

  1. 我们可以使用上面的公式生成所有素数。
  2. 如果我们可以删除上述数字的所有倍数,我们只剩下素数。
  3. 生成低于1000的素数。

    ArrayList<Integer> primes = new ArrayList<>();
    primes.add(2);//explicitly add
    primes.add(3);//2 and 3
    int n = 1000;
    for (int i = 1; i <= (n / 6) ; i++) {
    //get all the numbers which can be generated by the formula
        int prod6k = 6 * i;
        primes.add(prod6k - 1);
        primes.add(prod6k + 1);
    }
    for (int i = 0; i < primes.size(); i++) {
        int k = primes.get(i);
        //remove all the factors of the numbers generated by the formula
        for(int j = k * k; j <= n; j += k)//changed to k * k from 2 * k, Thanks to DTing
        {           
            int index = primes.indexOf(j); 
            if(index != -1)
                primes.remove(index);
        }
    }
    System.out.println(primes);
    

    但是,此方法确实正确生成素数。这样运行速度要快得多,因为我们不需要检查我们在Sieve中检查的所有数字。

    我的问题是我错过了任何边缘案例吗?这会好很多但我从未见过有人使用过这个。我做错了吗?

    这种方法可以更加优化吗?

    采用boolean[]代替ArrayList要快得多。

    int n = 100000000;
    boolean[] primes = new boolean[n + 1];
    for (int i = 0; i <= n; i++)
        primes[i] = false;
    primes[2] = primes[3] = true;
    for (int i = 1; i <= n / 6; i++) {
        int prod6k = 6 * i;
        primes[prod6k + 1] = true;
        primes[prod6k - 1] = true;
    }
    for (int i = 0; i <= n; i++) {
        if (primes[i]) {
            int k = i;
            for (int j = k * k; j <= n && j > 0; j += k) {
                   primes[j] = false;
            }
          }
    }
    for (int i = 0; i <= n; i++)
        if (primes[i]) 
            System.out.print(i + " ");
    

9 个答案:

答案 0 :(得分:6)

5是您的条件生成的第一个数字。让我们来看看最多生成25的数字:

  

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

现在,让我们看看这些相同的数字,当我们使用Sieve of Eratosthenes算法时:

  

5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25

删除2:

  

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

删除3:

  

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

这与第一套相同!注意它们都包括25,这不是素数。如果我们考虑一下,这是一个明显的结果。考虑任意一组6个连续数字:

  

6k - 3,6k - 2,6k - 1,6k,6k + 1,6k + 2

如果我们考虑一点,我们得到:

  

3 *(2k - 1),2 *(3k - 1),6k - 1,6 *(k),6k + 1,2 *(3k + 1)

在任何一组6个连续数字中,其中3个可以被2整除,其中2个可以被3整除。这些正是我们到目前为止删除的数字!因此:

仅使用6k - 16k + 1的算法与Erathosthenes筛子的前两轮完全相同。

与Sieve相比,这是一个非常好的速度提升,因为我们不必添加所有这些额外的元素只是为了删除它们。这解释了为什么你的算法有效以及为什么它不会错过任何情况;因为它与Sieve完全相同。

无论如何,我同意,一旦你生成素数,你的boolean方式到目前为止最快。我已经使用ArrayList方式设置了基准,你的boolean[]方式,以及我自己使用LinkedListiterator.remove()的方式(因为LinkedList中的删除很快。这是我的测试工具的代码。请注意,我运行了测试12次以确保JVM预热,并打印列表大小并更改n的大小以尝试防止过多branch prediction优化。您也可以加快速度在初始种子中使用+= 6而不是prod6k

的三种方法
import java.util.*;

public class PrimeGenerator {
  public static List<Integer> generatePrimesArrayList(int n) {
    List<Integer> primes = new ArrayList<>(getApproximateSize(n));
    primes.add(2);// explicitly add
    primes.add(3);// 2 and 3

    for (int i = 6; i <= n; i+=6) {
      // get all the numbers which can be generated by the formula
      primes.add(i - 1);
      primes.add(i + 1);
    }

    for (int i = 0; i < primes.size(); i++) {
      int k = primes.get(i);
      // remove all the factors of the numbers generated by the formula
      for (int j = k * k; j <= n; j += k)// changed to k * k from 2 * k, Thanks
                                         // to DTing
      {
        int index = primes.indexOf(j);
        if (index != -1)
          primes.remove(index);
      }
    }
    return primes;
  }

  public static List<Integer> generatePrimesBoolean(int n) {
    boolean[] primes = new boolean[n + 5];
    for (int i = 0; i <= n; i++)
      primes[i] = false;
    primes[2] = primes[3] = true;

    for (int i = 6; i <= n; i+=6) {
      primes[i + 1] = true;
      primes[i - 1] = true;
    }

    for (int i = 0; i <= n; i++) {
      if (primes[i]) {
        int k = i;
        for (int j = k * k; j <= n && j > 0; j += k) {
          primes[j] = false;
        }
      }
    }

    int approximateSize = getApproximateSize(n);
    List<Integer> primesList = new ArrayList<>(approximateSize);
    for (int i = 0; i <= n; i++)
      if (primes[i])
        primesList.add(i);

    return primesList;
  }

  private static int getApproximateSize(int n) {
    // Prime Number Theorem. Round up
    int approximateSize = (int) Math.ceil(((double) n) / (Math.log(n)));
    return approximateSize;
  }

  public static List<Integer> generatePrimesLinkedList(int n) {
    List<Integer> primes = new LinkedList<>();
    primes.add(2);// explicitly add
    primes.add(3);// 2 and 3

    for (int i = 6; i <= n; i+=6) {
      // get all the numbers which can be generated by the formula
      primes.add(i - 1);
      primes.add(i + 1);
    }

    for (int i = 0; i < primes.size(); i++) {
      int k = primes.get(i);
      for (Iterator<Integer> iterator = primes.iterator(); iterator.hasNext();) {
        int primeCandidate = iterator.next();
        if (primeCandidate == k)
          continue; // Always skip yourself
        if (primeCandidate == (primeCandidate / k) * k)
          iterator.remove();
      }
    }
    return primes;
  }

  public static void main(String... args) {
    int initial = 4000;

    for (int i = 0; i < 12; i++) {
      int n = initial * i;
      long start = System.currentTimeMillis();
      List<Integer> result = generatePrimesArrayList(n);
      long seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tArrayList Seconds: " + seconds);

      start = System.currentTimeMillis();
      result = generatePrimesBoolean(n);
      seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tBoolean Seconds: " + seconds);

      start = System.currentTimeMillis();
      result = generatePrimesLinkedList(n);
      seconds = System.currentTimeMillis() - start;
      System.out.println(result.size() + "\tLinkedList Seconds: " + seconds);
    }
  }
}

最后几次试验的结果:

3432    ArrayList Seconds: 430
3432    Boolean Seconds: 0
3432    LinkedList Seconds: 90
3825    ArrayList Seconds: 538
3824    Boolean Seconds: 0
3824    LinkedList Seconds: 81
4203    ArrayList Seconds: 681
4203    Boolean Seconds: 0
4203    LinkedList Seconds: 100
4579    ArrayList Seconds: 840
4579    Boolean Seconds: 0
4579    LinkedList Seconds: 111

答案 1 :(得分:4)

您无需将所有可能的候选项添加到数组中。您可以创建一个Set来存储所有非素数。

您也可以k * k开始检查,而不是2 * k

  public void primesTo1000() {
    Set<Integer> notPrimes = new HashSet<>();
    ArrayList<Integer> primes = new ArrayList<>();
    primes.add(2);//explicitly add
    primes.add(3);//2 and 3

    for (int i = 1; i < (1000 / 6); i++) {
      handlePossiblePrime(6 * i - 1, primes, notPrimes);
      handlePossiblePrime(6 * i + 1, primes, notPrimes);
    }
    System.out.println(primes);
  }

  public void handlePossiblePrime(
      int k, List<Integer> primes, Set<Integer> notPrimes) {
    if (!notPrimes.contains(k)) {
      primes.add(k);
      for (int j = k * k; j <= 1000; j += k) {
        notPrimes.add(j);
      }
    }
  }

未经测试的代码,检查角落

根据the answer引用的@Will Ness中的建议,这是筛子的一些包装版本。而不是返回n th prime,此版本返回n的素数列表:

public List<Integer> primesTo(int n) {
  List<Integer> primes = new ArrayList<>();
  if (n > 1) {
    int limit = (n - 3) >> 1;
    int[] sieve = new int[(limit >> 5) + 1];
    for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
        int p = i + i + 3;
        for (int j = (p * p - 3) >> 1; j <= limit; j += p)
          sieve[j >> 5] |= 1 << (j & 31);
      }
    primes.add(2);
    for (int i = 0; i <= limit; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
        primes.add(i + i + 3);
  }
  return primes;
}

您的更新代码中似乎存在一个使用布尔数组的错误(它没有返回所有素数)。

public static List<Integer> booleanSieve(int n) {
  boolean[] primes = new boolean[n + 5];
  for (int i = 0; i <= n; i++)
    primes[i] = false;
  primes[2] = primes[3] = true;
  for (int i = 1; i <= n / 6; i++) {
    int prod6k = 6 * i;
    primes[prod6k + 1] = true;
    primes[prod6k - 1] = true;
  }
  for (int i = 0; i <= n; i++) {
    if (primes[i]) {
      int k = i;
      for (int j = k * k; j <= n && j > 0; j += k) {
        primes[j] = false;
      }
    }
  }

  List<Integer> primesList = new ArrayList<>();
  for (int i = 0; i <= n; i++)
    if (primes[i])
      primesList.add(i);

  return primesList;
}

public static List<Integer> bitPacking(int n) {
  List<Integer> primes = new ArrayList<>();
  if (n > 1) {
    int limit = (n - 3) >> 1;
    int[] sieve = new int[(limit >> 5) + 1];
    for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
        int p = i + i + 3;
        for (int j = (p * p - 3) >> 1; j <= limit; j += p)
          sieve[j >> 5] |= 1 << (j & 31);
      }
    primes.add(2);
    for (int i = 0; i <= limit; i++)
      if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
        primes.add(i + i + 3);
  }
  return primes;
}

public static void main(String... args) {
  Executor executor = Executors.newSingleThreadExecutor();
  executor.execute(() -> {
    for (int i = 0; i < 10; i++) {
      int n = (int) Math.pow(10, i);
      Stopwatch timer = Stopwatch.createUnstarted();
      timer.start();
      List<Integer> result = booleanSieve(n);
      timer.stop();
      System.out.println(result.size() + "\tBoolean: " + timer);
    }

    for (int i = 0; i < 10; i++) {
      int n = (int) Math.pow(10, i);
      Stopwatch timer = Stopwatch.createUnstarted();
      timer.start();
      List<Integer> result = bitPacking(n);
      timer.stop();
      System.out.println(result.size() + "\tBitPacking: " + timer);
    }
  });
}
0   Boolean: 38.51 μs
4   Boolean: 45.77 μs
25  Boolean: 31.56 μs
168 Boolean: 227.1 μs
1229    Boolean: 1.395 ms
9592    Boolean: 4.289 ms
78491   Boolean: 25.96 ms
664116  Boolean: 133.5 ms
5717622 Boolean: 3.216 s
46707218    Boolean: 32.18 s
0   BitPacking: 117.0 μs
4   BitPacking: 11.25 μs
25  BitPacking: 11.53 μs
168 BitPacking: 70.03 μs
1229    BitPacking: 471.8 μs
9592    BitPacking: 3.701 ms
78498   BitPacking: 9.651 ms
664579  BitPacking: 43.43 ms
5761455 BitPacking: 1.483 s
50847534    BitPacking: 17.71 s

答案 2 :(得分:2)

有几件事可以优化。

对于初学者来说,ArrayList上的“contains”和“removeAll”操作是相当昂贵的操作(前者是线性的,后者是最差的二次方),所以你可能不想为此使用ArrayList。 Hash或TreeSet具有更好的复杂性,几乎不变(Hashing复杂性很奇怪)和对数我认为

如果你想要一个更有效的筛子altogeter,你可以看看Eratosthenes的筛子筛,但这将是你的问题关于6k + -1技巧的问题。它比你的解决方案略微但并不显着地更昂贵,但速度更快。

答案 3 :(得分:1)

  

这种方法可以更加优化吗?

答案是肯定的。

我首先要说的是 是一个好主意,在一定范围内对数字子集使用筛子,你的建议就是这样做。

阅读generating Primes

  

...此外,基于筛子形式,一些整数序列   (A240673中的序列OEIS)被构造出来,它们也可以用于在特定区间内生成素数。

本段的含义是,你从简化的整数列表开始的方法确实被学院采用,但他们的技术更有效(但自然而然,更复杂)。

答案 4 :(得分:1)

您可以使用滚轮生成试用编号,交替添加2和4,从而消除6 * k +/- 1中的乘法。

public void primesTo1000() {
  Set<Integer> notPrimes = new HashSet<>();
  ArrayList<Integer> primes = new ArrayList<>();
  primes.add(2);  //explicitly add
  primes.add(3);  //2 and 3

  int step = 2;
  int num = 5  // 2 and 3 already handled.
  while (num < 1000) {     
    handlePossiblePrime(num, primes, notPrimes);
    num += step;      // Step to next number.
    step = 6 - step;  // Step by 2, 4 alternately.
  }
  System.out.println(primes);
}

答案 5 :(得分:1)

Eratosthenes筛选最合适的标准数据结构可能是the correct number。这是我的解决方案:

static BitSet genPrimes(int n) {
    BitSet primes = new BitSet(n);
    primes.set(2); // add 2 explicitly
    primes.set(3); // add 3 explicitly
    for (int i = 6; i <= n ; i += 6) { // step by 6 instead of multiplication
        primes.set(i - 1);
        primes.set(i + 1);
    }
    int max = (int) Math.sqrt(n); // don't need to filter multiples of primes bigger than max

    // this for loop enumerates all set bits starting from 5 till the max
    // sieving 2 and 3 is meaningless: n*6+1 and n*6-1 are never divisible by 2 or 3
    for (int i = primes.nextSetBit(5); i >= 0 && i <= max; i = primes.nextSetBit(i+1)) {
        // The actual sieve algorithm like in your code
        for(int j = i * i; j <= n; j += i)
            primes.clear(j);
    }
    return primes;
}

用法:

BitSet primes = genPrimes(1000); // generate primes up to 1000
System.out.println(primes.cardinality()); // print number of primes
// print all primes like {2, 3, 5, ...}
System.out.println(primes);
// print all primes one per line
for(int prime = primes.nextSetBit(0); prime >= 0; prime = primes.nextSetBit(prime+1))
    System.out.println(prime);
// print all primes one per line using java 8:
primes.stream().forEach(System.out::println);

基于布尔的版本可以更快地处理小n值,但如果您需要,例如,一百万个素数,BitSet将在几次内胜过它并且实际上正常工作。这是一个蹩脚的基准:

public static void main(String... args) {
    long start = System.nanoTime();
    BitSet res = genPrimes(10000000);
    long diff = System.nanoTime() - start;
    System.out.println(res.cardinality() + "\tBitSet Seconds: " + diff / 1e9);

    start = System.nanoTime();
    List<Integer> result = generatePrimesBoolean(10000000); // from durron597 answer
    diff = System.nanoTime() - start;
    System.out.println(result.size() + "\tBoolean Seconds: " + diff / 1e9);
}

输出:

664579  BitSet Seconds: 0.065987717
664116  Boolean Seconds: 0.167620323

664579是低于10000000的素数的{{3}}。

答案 6 :(得分:0)

下面的方法显示了如何使用6k +/- 1逻辑查找素数

这是用python 3.6编写的

def isPrime(n):
    if(n<=1):
        return 0
    elif(n<4):   #2 , 3 are prime
        return 1
    elif(n%2==0):  #already excluded no.2 ,so any no. div. by 2 cant be prime
        return 0
    elif(n<9):   #5, 7 are prime and 6,8 are excl. in the above step
        return 1
    elif(n%3==0):
        return 1

    f=5         #Till now we have checked the div. of n with 2,3 which means with 4,6,8 also now that is why f=5
    r=int(n**.5)    #rounding of root n, i.e: floor(sqrt(n))    r*r<=n
    while(f<=r):
        if(n%f==0): #checking if n has any primefactor lessthan sqrt(n), refer LINE 1
            return 0
        if(n%(f+2)==0): #remember her we are not incrementing f, see the 6k+1 rule to understand this while loop steps ,you will see that most values of f are prime
            return 0
        f=f+6

    return 1    

def prime_nos():
    counter=2  #we know 2,3 are prime
    print(2)
    print(3)   #we know 2,3 are prime
    i=1
    s=5  #sum  2+3
    t=0

    n=int(input("Enter the upper limit( should be > 3: "))

    n=(n-1)//6   #finding max. limit(n=6*i+1) upto which I(here n on left hand side) should run
    while(i<n):#2*(10**6)):
        if (isPrime(6*i-1)):   
            counter=counter+1
            print(6*i-1)  #prime no                                                

        if(isPrime(6*i+1)):    
           counter=counter+1
           print(6*i+1)  #prime no                                

        i+=1

prime_nos()  #fn. call

答案 7 :(得分:0)

您的素数公式在数学上不正确,例如。取 96 可整除 6 96/6=16 所以根据这个逻辑,如果平方根通过,97 和 95 必须是素数,但 95 的平方根是 9.7467...(通过)所以它的“素数”。但是 95 在 c# 中显然可以被 5 快速算法整除

       int n=100000000;    
       bool [] falseprimes = new bool[n + 2];
       int ed=n/6;
        ed = ed * 6;
        int md = (int)Math.Sqrt((double)ed);
        for (int i = ed; i > md; i-=6)
        {
            falseprimes[i + 1] = true;
            falseprimes[i - 1] = true;

        }

        md = md / 6;
        md = md * 5;

        for (int i = md; i > 5; i -= 6)
        {
            falseprimes[i + 1] = true;
            falseprimes[i - 1] = true;

            falseprimes[(i + 1)* (i + 1)] = false;
            falseprimes[(i-1) * (i-1)] = false;
        }

        falseprimes[2] = true;
        falseprimes[3] = true;

答案 8 :(得分:0)

要使用 6 * k + - 1 规则生成素数,请使用以下算法:

int n = 100000000;
int j,jmax=n/6;
boolean[] primes5m6 = new boolean[jmax+1];
boolean[] primes1m6 = new boolean[jmax+1];
for (int i = 1; i <= (int)((Math.sqrt(n)+1)/6)+1; i++){
    if (!primes5m6[i]){
        for (j = 6*i*i; j <= jmax; j+=6*i-1){       
            primes5m6[j]=true;
            primes1m6[j-2*i]=true;
        }
        for (; j <= jmax+2*i; j+=6*i-1)       
            primes1m6[j-2*i]=true;
    }
    if (!primes1m6[i]){
        for (j = 6*i*i; j <= jmax-2*i; j+=6*i+1){      
            primes5m6[j]=true; 
            primes1m6[j+2*i]=true; 
        }
        for (; j <= jmax; j+=6*i+1)      
            primes5m6[j]=true;   
   }
}

System.out.print(2 + " ");
System.out.print(3 + " ");
for (int i = 1; i <= jmax; i++){
    if (!primes5m6[i]) 
        System.out.print((6*i-1) + " ");
    if (!primes1m6[i]) 
        System.out.print((6*i+1) + " ");
}