Efficient Algorithms for Finding Prime Numbers

Paul Ngugi - Jul 15 - - Dev Community

This section presents several algorithms in the search for an efficient algorithm for finding prime numbers. Can you design a fast algorithm for finding prime numbers?

An integer greater than 1 is prime if its only positive divisor is 1 or itself. For example, 2, 3, 5, and 7 are prime numbers, but 4, 6, 8, and 9 are not.

How do you determine whether a number n is prime? PrimeNumber.java presented a brute-force algorithm for finding prime numbers. The algorithm checks whether 2, 3, 4, 5, . . . , or n - 1 is divisible by n. If not, n is prime. This algorithm takes O(n) time to check whether n is prime. Note that you need to check only whether 2, 3, 4, 5, . . . , and n/2 is divisible by n. If not, n is prime. This algorithm is slightly improved, but it is still of O(n).

In fact, we can prove that if n is not a prime, n must have a factor that is greater than 1 and less than or equal to sqrt(n). Here is the proof. Since n is not a prime, there exist two numbers p and q such that n = pq with 1 < p <= q. Note that n = sqrt(n)sqrt(n). p must be less than or equal to sqrt(n). Hence, you need to check only whether 2, 3, 4, 5, . . . , or sqrt(n) is divisible by n. If not, n is prime. This significantly reduces the time complexity of the algorithm to O(sqrt(n)).

Now consider the algorithm for finding all the prime numbers up to n. A straightforward implementation is to check whether i is prime for i = 2, 3, 4, . . . , n. The program is given in code below.



package demo;
import java.util.Scanner;

public class PrimeNumbers {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("Find all prime numbers <= n, enter n: ");
        int n= input.nextInt();

        final int NUMBER_PER_LINE = 10; // Display 10 per line
        int count = 0; // Count the number of prime numbers
        int number = 2; // A number to be tested for primeness

        System.out.println("The prime numbers are:");

        // Repeatedly find prime numbers
        while(number <= n) {
            // Assume the number is prime
            boolean isPrime = true; // Is the current number prime?

            // Test if number is prime
            for(int divisor = 2; divisor <= (int)(Math.sqrt(number)); divisor++) {
                if(number % divisor == 0) { // If true, number is not prime
                    isPrime = false; // Set isPrime to false
                    break; // Exit the for loop
                }
            }

            // Print the prime number and increase the count
            if(isPrime) {
                count++; // Increase the count

                if(count % NUMBER_PER_LINE == 0) {
                    // Print the number and advance to the new line
                    System.out.printf("%7d\n", number);
                }
                else
                    System.out.printf("%7d", number);
            }

            // Check if the next number is prime
            number++;
        }

        System.out.println("\n" + count + " prime(s) less than or equal to " + n);
    }

}



Enter fullscreen mode Exit fullscreen mode

Find all prime numbers <= n, enter n: 1000
The prime numbers are:
2 3 5 7 11 13 17 19 23 29
31 37 41 43 47 53 59 61 67 71
...
...
168 prime(s) less than or equal to 1000

The program is not efficient if you have to compute Math.sqrt(number) for every iteration of the for loop (line 23). A good compiler should evaluate Math.sqrt(number) only once for the entire for loop. To ensure this happens, you can explicitly replace line 23 with the following two lines:

int squareRoot = (int)(Math.sqrt(number));
for (int divisor = 2; divisor <= squareRoot; divisor++) {

In fact, there is no need to actually compute Math.sqrt(number) for every number. You need look only for the perfect squares such as 4, 9, 16, 25, 36, 49, and so on. Note that for all the numbers between 36 and 48, inclusively, their (int)(Math.sqrt(number)) is 6. With this insight, you can replace the code in lines 18–27 with the following:

...
int squareRoot = 1;
// Repeatedly find prime numbers
while (number <= n) {
// Assume the number is prime
boolean isPrime = true; // Is the current number prime?
if (squareRoot * squareRoot < number) squareRoot++;
// Test if number is prime
for (int divisor = 2; divisor <= squareRoot; divisor++) {
if (number % divisor == 0) { // If true, number is not prime
isPrime = false; // Set isPrime to false
break; // Exit the for loop
}
}
...

Now we turn our attention to analyzing the complexity of this program. Since it takes sqrt(i) steps in the for loop (lines 23–28) to check whether number i is prime, the algorithm takes sqrt(2) + sqrt(3) + sqrt(4) + ... + sqrt(n) steps to find all the prime numbers less than or equal to n. Observe that

sqrt(2) + sqrt(3) + sqrt(4) + ... + sqrt(n) <= (n)sqrt(n)

Therefore, the time complexity for this algorithm is O((n)sqrt(n)).

To determine whether i is prime, the algorithm checks whether 2, 3, 4, 5, . . . , and 2i are divisible by i. This algorithm can be further improved. In fact, you need to check only whether the prime numbers from 2 to sqrt(i) are possible divisors for i.

We can prove that if i is not prime, there must exist a prime number p such that i = pq and p <= q. Here is the proof. Assume that i is not prime; let p be the smallest factor of i. p must be prime, otherwise, p has a factor k with 2 <= k < p. k is also a factor of i, which contradicts that p be the smallest factor of i. Therefore, if i is not prime, you can find a prime number from 2 to sqrt(i) that is divisible by i. This leads to a more efficient algorithm for finding all prime numbers up to n, as shown in the code below.



package demo;
import java.util.Scanner;

public class EfficientPrimeNumbers {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("Find all prime numbers <= n, enter n: ");
        int n = input.nextInt();

        // A list to hold prime numbers
        java.util.List<Integer> list = new java.util.ArrayList<>();

        final int NUMBER_PER_LINE = 10; // Display 10 per line
        int count = 0; // Count the number of prime numbers
        int number = 2; // A number to be tested for primeness
        int squareRoot = 1; // Check whether number <= squareRoot

        System.out.println("The prime numbers are \n");

        // Repeatedly find prime numbers
        while(number <= n) {
            // Assume the number is prime
            boolean isPrime = true; // Is the current number prime?

            if(squareRoot * squareRoot < number) squareRoot++;

            // Test if number is prime
            for(int k = 0; k < list.size() && list.get(k) <= squareRoot; k++) {
                if(number % list.get(k) == 0) { // If true, not prime
                    isPrime = false; // Set isPrime to false
                    break; // Exit the for loop
                }
            }

            // Print the prime number and increase the count
            if(isPrime) {
                count++; // Increase the count
                list.add(number); // Add a new prime to the list
                if(count % NUMBER_PER_LINE == 0) {
                    // Print the number and advance to the new line
                    System.out.println(number);
                }
                else
                    System.out.print(number + " ");
            }

            // Check whether the next number is prime
            number++;
        }

        System.out.println("\n" + count + " prime(s) less than or equal to " + n);
    }

}



Enter fullscreen mode Exit fullscreen mode

Find all prime numbers <= n, enter n: 1000
The prime numbers are:
2 3 5 7 11 13 17 19 23 29
31 37 41 43 47 53 59 61 67 71
...
...
168 prime(s) less than or equal to 1000

Let PI(i) denote the number of prime numbers less than or equal to i. The primes under 20 are 2, 3, 5, 7, 11, 13, 17, and 19. Therefore, PI(2) is 1, PI(3) is 2, PI(6) is 3, and PI(20) is 8.

It has been proved that PI(i) is approximately i / log(i).

For each number i, the algorithm checks whether a prime number less than or equal to sqrt(i) is divisible by i. The number of the prime numbers less than or equal to sqrt(i) is

sqrt(i) / (log)sqrt(i) = (2)sqrt(i) / logi

Thus, the complexity for finding all prime numbers up to n is

Image description

This algorithm is another example of dynamic programming. The algorithm stores the results of the subproblems in the array list and uses them later to check whether a new number is prime.

Is there any algorithm better than

O((n)sqrt(n) / logn)?

Let us examine the well-known Eratosthenes

algorithm for finding prime numbers. Eratosthenes (276–194 b.c.) was a Greek mathematician who devised a clever algorithm, known as the Sieve of Eratosthenes, for finding all prime numbers <= n. His algorithm is to use an array named primes of n Boolean values. Initially, all elements in primes are set true. Since the multiples of 2 are not prime, set primes[2 * i] to false for all 2 <= i <= n/2, as shown in Figure below. Since we don’t care about primes[0] and primes[1], these values are marked X in the figure.

Image description

Since the multiples of 3 are not prime, set primes[3 * i] to false for all 3 … i … n/3. Because the multiples of 5 are not prime, set primes[5 * i] to false for all 5 … i … n/5. Note that you don’t need to consider the multiples of 4, because the multiples of 4 are also the multiples of 2, which have already been considered. Similarly, multiples of 6, 8, and 9 need not be considered. You only need to consider the multiples of a prime number k = 2, 3, 5, 7, 11, . . . , and set the corresponding element in primes to false. Afterward, if primes[i] is still true, then i is a prime number. As shown in Figure below, 2, 3, 5, 7, 11, 13, 17, 19, and 23 are prime numbers. The cde below gives the program for finding the prime numbers using the Sieve of Eratosthenes algorithm.

Image description

Find all prime numbers <= n, enter n: 1000
The prime numbers are:
2 3 5 7 11 13 17 19 23 29
31 37 41 43 47 53 59 61 67 71
...
...
168 prime(s) less than or equal to 1000

Note that k <= n / k (line 17). Otherwise, k * i would be greater than n (line 20). What is the time complexity of this algorithm?

For each prime number k (line 18), the algorithm sets primes[k * i] to false (line 20). This is performed n / k – k + 1 times in the for loop (line 19). Thus, the complexity for finding all prime numbers up to n is

Image description

Table below summarizes the complexity of these three algorithms for finding all prime numbers up to n.

Image description

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .