简体   繁体   English

你是一个素数

[英]Are You A Prime Number

I've been interested in the problem of finding a better prime number recognizer for years. 多年来,我一直对寻找更好的素数识别器的问题感兴趣。 I realize this is a huge area of academic research and study - my interest in this is really just for fun. 我意识到这是一个巨大的学术研究和学习领域 - 我对此感兴趣只是为了好玩。 Here was my first attempt at a possible solution, in C (below). 这是我在C(下面)中首次尝试可能的解决方案。

My question is, can you suggest an improvement (without citing some other reference on the net, I'm looking for actual C code)? 我的问题是,你能否提出一个改进(没有引用网上的其他参考,我正在寻找实际的C代码)? What I'm trying to get from this is a better understanding of determining performance complexity of a solution like this. 我想从中获得的是更好地理解确定这样的解决方案的性能复杂性。

Am I right in concluding that the complexity of this solution is O(n^2)? 我是否正确地得出结论,这个解决方案的复杂性是O(n ^ 2)?

#include <stdio.h>
#include <math.h>

/* isprime                                                           */
/* Test if each number in the list from stdin is prime.              */
/* Output will only print the prime numbers in the list.             */

int main(int argc, char* argv[]) {

    int returnValue = 0;
    int i;
    int ceiling;
    int input = 0;
    int factorFound = 0;

    while (scanf("%d", &input) != EOF) {

        ceiling = (int)sqrt(input);
        if (input == 1) {
            factorFound = 1;
        }

        for (i = 2; i <= ceiling; i++) {
            if (input % i == 0) {
                factorFound = 1;
            } 
        }

        if (factorFound == 0) {
            printf("%d\n", input);
        }

        factorFound = 0;    
    } 

    return returnValue;
}
   for (i = 2; i <= ceiling; i++) {
        if (input % i == 0) {
            factorFound = 1;
            break;
        } 
    }

This is the first improvement to make and still stay within the bounds of "same" algorithm. 这是第一个改进,仍然保持在“相同”算法的范围内。 It doesn't require any math at all to see this one. 它根本不需要任何数学就可以看到这个。

Beyond that, once you see that input is not divisible by 2, there is no need to check for 4, 6, 8, etc. If any even number divided into input , then surely 2 would have because it divides all even numbers. 除此之外,一旦你看到input不能被2整除,就没有必要检查4,6,8等。如果任何偶数被分成input ,那么肯定会有2,因为它除了所有偶数。

If you want to step outside of the algorithm a little bit, you could use a loop like the one that Sheldon L. Cooper provides in his answer. 如果你想稍微超出算法的范围,可以使用Sheldon L. Cooper在答案中提供的循环。 (This is just easier than having him correct my code from the comments though his efforts are much appreciated) (这比让他从评论中纠正我的代码更容易,尽管他的努力非常受欢迎)

this takes advantage of the fact that every prime other than 2 and 3 is of the form n*6 + 1 or n*6 - 1 for some positive integer n . 这利用了以下事实:对于某些正整数n除了2和3之外的每个素数都是n*6 + 1n*6 - 1 6-1的形式。 To see this, just note that if m = n*6 + 2 or m = n*6 + 4 , then n is divisible by 2. if m = n*6 + 3 then it is divisible by 3. 要看到这一点,请注意,如果m = n*6 + 2m = n*6 + 4 ,那么n可以被2整除。如果m = n*6 + 3则可以被3整除。

In fact, we can take this further. 事实上,我们可以更进一步。 If p1, p2, .., pk are the first k primes, then all of the integers that are coprime to their product mark out 'slots' that all remaining primes must fit into. 如果p1, p2, .., pk是第一个k素数,则所有与其产品互质的整数都标出“槽”,所有剩余的素数必须适合。

to see this, just let k# be the product of all primes up to pk . 看到这一点,让k#成为所有素数的产物,直到pk then if gcd(k#, m) = g , g divides n*k# + m and so this sum is trivially composite if g != 1 . 那么如果gcd(k#, m) = g ,则gn*k# + m ,因此如果g != 1则该和是非常复合的。 so if you wanted to iterate in terms of 5# = 30 , then your coprime integers are 1, 7, 11, 13, 17, 19, 23 and 29. 所以如果你想用5# = 30进行迭代,那么你的互质整数分别是1,7,11,13,17,19,23和29。


technically, I didn't prove my last claim. 从技术上讲,我没有证明我的最后一个主张。 It's not much more difficult 这并不困难

if g = gcd(k#, m) , then for any integer, n , g divides n*k# + m because it divides k# so it must also divide n*k# . 如果g = gcd(k#, m) ,那么对于任何整数, ngn*k# + m因为它除了k#所以它也必须除n*k# But it divides m as well so it must divide the sum. 但它也将m分开,所以它必须除以总和。 Above I only proved it for n = 1 . 上面我只证明了n = 1 my bad. 我的错。


Also, I should note that none of this is changing the fundamental complexity of the algoritm, it will still be O(n^1/2). 另外,我应该注意到,这一切都没有改变算法的基本复杂性,它仍然是O(n ^ 1/2)。 All it is doing is drastically reducing the coefficient that gets used to calculate actual expected run times. 它所做的只是大幅减少用于计算实际预期运行时间的系数。

The time complexity for each primality test in your algorithm is O(sqrt(n)) . 算法中每个素性测试的时间复杂度为O(sqrt(n))

You can always use the fact that all primes except 2 and 3 are of the form: 6*k+1 or 6*k-1 . 您总是可以使用以下事实:除2和3之外的所有素数都是以下形式: 6*k+16*k-1 For example: 例如:

int is_prime(int n) {
  if (n <= 1) return 0;
  if (n == 2 || n == 3) return 1;
  if (n % 2 == 0 || n % 3 == 0) return 0;
  int k;
  for (k = 6; (k-1)*(k-1) <= n; k += 6) {
    if (n % (k-1) == 0 || n % (k+1) == 0) return 0;
  }
  return 1;
}

This optimization does not improve the asymptotic complexity. 此优化不会改善渐近复杂度。

EDIT 编辑

Given that in your code you are testing numbers repeatedly, you might want to pre-calculate a list of primes. 鉴于您在代码中反复测试数字,您可能需要预先计算素数列表。 There are only 4792 primes less than or equal to the square root of INT_MAX (assuming 32 bit ints). 只有4792个素数小于或等于INT_MAX的平方根(假设为32位整数)。

Furthermore, if the input numbers are relatively small you can try calculating a sieve . 此外,如果输入数字相对较小,您可以尝试计算筛子

Here's a combination of both ideas: 以下是两种想法的组合:

#define UPPER_BOUND 46340  /* floor(sqrt(INT_MAX)) */
#define PRIME_COUNT 4792  /* number of primes <= UPPER_BOUND */

int prime[PRIME_COUNT];
int is_prime_aux[UPPER_BOUND];

void fill_primes() {
  int p, m, c = 0;
  for (p = 2; p < UPPER_BOUND; p++)
    is_prime_aux[p] = 1;
  for (p = 2; p < UPPER_BOUND; p++) {
    if (is_prime_aux[p]) {
      prime[c++] = p;
      for (m = p*p; m < UPPER_BOUND; m += p)
        is_prime_aux[m] = 0;
    }
  }
}

int is_prime(int n) {
  if (n <= 1) return 0;
  if (n < UPPER_BOUND) return is_prime_aux[n];
  int i;
  for (i = 0; i < PRIME_COUNT && prime[i]*prime[i] <= n; i++)
    if (n % prime[i] == 0)
      return 0;
  return 1;
}

Call fill_primes at the beginning of your program, before starting to process queries. 在开始处理查询之前,在程序开头调用fill_primes It runs pretty fast. 它运行得非常快。

Your code there only has complexity O(sqrt(n)lg(n)). 那里的代码只有复杂度O(sqrt(n)lg(n))。 If you assume basic mathematical operations are O(1) (true until you start using bignums), then it's just O(sqrt(n)). 如果你假设基本的数学运算是O(1)(直到你开始使用bignums为真),那么它只是O(sqrt(n))。

Note that primality testing can be performed in faster-than-O(sqrt(n)lg(n)) time. 注意,素数测试可以在比O(sqrt(n)lg(n))更快的时间内执行。 This site has a number of implementations of the AKS primality test , which has been proven to operate in O((log n)^12) time. 该站点具有许多AKS素性测试的实现,已经证明它在O((log n)^ 12)时间内运行。

There are also some very, very fast probalistic tests - while fast, they sometimes give an incorrect result. 还有一些非常非常快速的probalistic测试 - 虽然速度很快,但它们有时会给出错误的结果。 For example, the Fermat primality test : 例如, 费马素性测试

Given a number p we want to test for primality, pick a random number a , and test whether a^(p-1) mod p = 1 . 给定一个数p我们想测试素数,选择一个随机数a ,并测试a^(p-1) mod p = 1 If false, p is definitely not prime. 如果为假,则p绝对不是素数。 If true, p is probably prime. 如果为真, p 可能是素数。 By repeating the test with different random values of a , the probability of a false positive can be reduced. 通过重复用的不同的随机值测试a ,假阳性的概率可被减少。

Note that this specific test has some flaws to it - see the Wikipedia page for details, and other probabilistic primality tests you can use. 请注意,此特定测试有一些缺陷 - 有关详细信息,请参阅Wikipedia页面,以及您可以使用的其他概率素性测试。

If you want to stick with the current approach, there are a number of minor improvements which can still be made - as others have pointed out, after 2 , all further primes are odd, so you can skip two potential factors at a time in the loop. 如果你想坚持使用当前的方法,仍然可以进行一些小的改进 - 正如其他人指出的那样,在2之后,所有其他的素数都是奇数,所以你可以一次跳过两个潜在的因素。环。 You can also break out immediately when you find a factor. 当您找到一个因素时,您也可以立即突破。 However, this doesn't change the asymptotic worst-case behavior of your algorithm, which remains at O(sqrt(n)lg(n)) - it just changes the best case (to O(lg(n))), and reduces the constant factor by roughly one-half. 但是,这并不会改变算法的渐近最坏情况行为,它保持在O(sqrt(n)lg(n)) - 它只是改变最佳情况(到O(lg(n))),并且将常数因子减少大约一半。

A simple improvement would be to change the for loop to break out when it finds a factor: 一个简单的改进是在找到一个因子时将for循环更改为break:

   for (i = 2; i <= ceiling && !factorFound; i++) {
        if (input % i == 0) {
            factorFound = 1;

Another possibility would be to increment the counter by 2 (after checking 2 itself). 另一种可能性是将计数器增加2(在检查2本身之后)。

can you suggest an improvement 你能建议改进吗?

Here you go ... not for the algorithm, but for the program itself :) 在这里你去...不是算法,而是程序本身:)

  • If you aren't going to use argc and argv , get rid of them 如果你不打算使用argcargv ,那就去掉它们吧
  • What if I input "fortytwo"? 如果输入“fortytwo”怎么办? Compare scanf() == 1 , not != EOF 比较scanf() == 1 ,而不是!= EOF
  • No need to cast the value of sqrt() 无需sqrt()的值
  • returnValue is not needed, you can return a constant: return 0; 不需要returnValue ,可以返回一个常量: return 0;
  • Instead of having all of the functionality inside the main() function, separate your program in as many functions as you can think of. 而不是将所有功能都放在main()函数中,而是将程序与您能想到的功能分开。
#include <stdio.h>
#include <math.h>

int IsPrime (int n) {
  int i, sqrtN;
  if (n < 2) { return 0; } /* 1, 0, and negatives are nonprime */
  if (n == 2) { return 2; }
  if ((n % 2) == 0) { return 0; } /* Check for even numbers */
  sqrtN = sqrt((double)n)+1; /* We don't need to search all the way up to n */
  for (i = 3; i < sqrtN; i += 2) {
    if (n % i == 0) { return 0; } /* Stop, because we found a factor! */
  }
  return n;
}

int main()
{
  int n;
  printf("Enter a positive integer: ");
  scanf("%d",&n);
  if(IsPrime(n))
     printf("%d is a prime number.",n);
  else
     printf("%d is not a prime number.",n);
  return 0;
}

Even numbers(except 2) cannot be prime numbers. 偶数(2除外)不能是素数。 So, once we know that the number is not even, we can just check if odd numbers are it's factors. 所以,一旦我们知道数字不均匀,我们就可以检查奇数是否是它的因素。

for (i = 3; i <= ceiling; i += 2) {
        if (input % i == 0) {
            factorFound = 1;
            break;
        } 
    }

You can make small cuts to your algorithm without adding too much code complexity. 您可以对算法进行小幅削减,而不会增加太多的代码复杂性。 For example, you can skip the even numbers on your verification, and stop the search as soon as you find a factor. 例如,您可以跳过验证中的偶数,并在找到因素后立即停止搜索。

if (input < 2 || (input != 2 && input % 2 == 0))
  factorFound = 1;

if (input > 3)
  for (i = 3; i <= ceiling && !factorFound; i += 2)
    if (input % i == 0)
      factorFound = 1;

Regarding the complexity, if n is your input number, wouldn't the complexity be O(sqrt(n)), as you are doing roughly at most sqrt(n) divisions and comparisons? 关于复杂性,如果n是您的输入数字,那么复杂性不是O(sqrt(n)),因为您大致在大多数sqrt(n)分区和比较中进行?

The time complexity of your program is O(n*m^0.5) . 程序的时间复杂度为O(n*m^0.5) With n the number of primes in the input. n表示输入中的素数。 And m the size of the biggest prime in the input, or MAX_INT if you prefer. 并且m是输入中最大素数的大小,如果您愿意,则为MAX_INT So complexity could also be written as O(n) with n the number of primes to check. 所以复杂性也可以写成O(n)其中n要检查的素数。

With Big-O, n is (usually) the size of the input, in your case that would be the number of primes to check. 对于Big-O, n (通常)是输入的大小,在您的情况下,将是要检查的素数。 If I make this list twice as big (for example duplicating it), it would take (+-) exactly twice as long, thus O(n) . 如果我将这个列表的两倍大(例如重复它),它将花费(+ - )两倍的长度,因此O(n)

Here's my algorithm, Complexity remains O(n^0.5) but i managed to remove some expensive operations in the code... 这是我的算法,Complexity仍为O(n^0.5)但我设法删除代码中的一些昂贵的操作......

The algorithm's slowest part is the modulus operation, i've managed to eliminate sqrt or doing i * i <= n 算法最慢的部分是modulus运算,我设法消除sqrt或做i * i <= n

This way i save precious cycles...its based on the fact that sum of odd numbers is always a perfect square. 这样我就节省了宝贵的周期......它基于sum of odd numbers is always a perfect square.

Since we are iterating over odd numbers anyway, why not exploit it? 既然我们正在迭代odd numbers ,为什么不利用它呢? :) :)

int isPrime(int n)
{
    int squares = 1;
    int odd = 3;

    if( ((n & 1) == 0) || (n < 9)) return (n == 2) || ((n > 1) && (n & 1));
    else
    {
        for( ;squares <= n; odd += 2)
        {
            if( n % odd == 0) 
                return 0;
            squares+=odd;
        }
        return 1;
    }
}

There is no way to improve the algorithm. 没有办法改进算法。 There may be tiny ways to improve your code, but not the base speed (and complexity) of the algorithm. 可能有很小的方法来改进您的代码,但不是算法的基本速度(和复杂性)。

EDIT: Of course, since he doesn't need to know all the factors, just whether or not it is a prime number. 编辑:当然,因为他不需要知道所有因素,只要它是否是素数。 Great spot. 好点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM