簡體   English   中英

你是一個素數

[英]Are You A Prime Number

多年來,我一直對尋找更好的素數識別器的問題感興趣。 我意識到這是一個巨大的學術研究和學習領域 - 我對此感興趣只是為了好玩。 這是我在C(下面)中首次嘗試可能的解決方案。

我的問題是,你能否提出一個改進(沒有引用網上的其他參考,我正在尋找實際的C代碼)? 我想從中獲得的是更好地理解確定這樣的解決方案的性能復雜性。

我是否正確地得出結論,這個解決方案的復雜性是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;
        } 
    }

這是第一個改進,仍然保持在“相同”算法的范圍內。 它根本不需要任何數學就可以看到這個。

除此之外,一旦你看到input不能被2整除,就沒有必要檢查4,6,8等。如果任何偶數被分成input ,那么肯定會有2,因為它除了所有偶數。

如果你想稍微超出算法的范圍,可以使用Sheldon L. Cooper在答案中提供的循環。 (這比讓他從評論中糾正我的代碼更容易,盡管他的努力非常受歡迎)

這利用了以下事實:對於某些正整數n除了2和3之外的每個素數都是n*6 + 1n*6 - 1 6-1的形式。 要看到這一點,請注意,如果m = n*6 + 2m = n*6 + 4 ,那么n可以被2整除。如果m = n*6 + 3則可以被3整除。

事實上,我們可以更進一步。 如果p1, p2, .., pk是第一個k素數,則所有與其產品互質的整數都標出“槽”,所有剩余的素數必須適合。

看到這一點,讓k#成為所有素數的產物,直到pk 那么如果gcd(k#, m) = g ,則gn*k# + m ,因此如果g != 1則該和是非常復合的。 所以如果你想用5# = 30進行迭代,那么你的互質整數分別是1,7,11,13,17,19,23和29。


從技術上講,我沒有證明我的最后一個主張。 這並不困難

如果g = gcd(k#, m) ,那么對於任何整數, ngn*k# + m因為它除了k#所以它也必須除n*k# 但它也將m分開,所以它必須除以總和。 上面我只證明了n = 1 我的錯。


另外,我應該注意到,這一切都沒有改變算法的基本復雜性,它仍然是O(n ^ 1/2)。 它所做的只是大幅減少用於計算實際預期運行時間的系數。

算法中每個素性測試的時間復雜度為O(sqrt(n))

您總是可以使用以下事實:除2和3之外的所有素數都是以下形式: 6*k+16*k-1 例如:

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;
}

此優化不會改善漸近復雜度。

編輯

鑒於您在代碼中反復測試數字,您可能需要預先計算素數列表。 只有4792個素數小於或等於INT_MAX的平方根(假設為32位整數)。

此外,如果輸入數字相對較小,您可以嘗試計算篩子

以下是兩種想法的組合:

#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;
}

在開始處理查詢之前,在程序開頭調用fill_primes 它運行得非常快。

那里的代碼只有復雜度O(sqrt(n)lg(n))。 如果你假設基本的數學運算是O(1)(直到你開始使用bignums為真),那么它只是O(sqrt(n))。

注意,素數測試可以在比O(sqrt(n)lg(n))更快的時間內執行。 該站點具有許多AKS素性測試的實現,已經證明它在O((log n)^ 12)時間內運行。

還有一些非常非常快速的probalistic測試 - 雖然速度很快,但它們有時會給出錯誤的結果。 例如, 費馬素性測試

給定一個數p我們想測試素數,選擇一個隨機數a ,並測試a^(p-1) mod p = 1 如果為假,則p絕對不是素數。 如果為真, p 可能是素數。 通過重復用的不同的隨機值測試a ,假陽性的概率可被減少。

請注意,此特定測試有一些缺陷 - 有關詳細信息,請參閱Wikipedia頁面,以及您可以使用的其他概率素性測試。

如果你想堅持使用當前的方法,仍然可以進行一些小的改進 - 正如其他人指出的那樣,在2之后,所有其他的素數都是奇數,所以你可以一次跳過兩個潛在的因素。環。 當您找到一個因素時,您也可以立即突破。 但是,這並不會改變算法的漸近最壞情況行為,它保持在O(sqrt(n)lg(n)) - 它只是改變最佳情況(到O(lg(n))),並且將常數因子減少大約一半。

一個簡單的改進是在找到一個因子時將for循環更改為break:

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

另一種可能性是將計數器增加2(在檢查2本身之后)。

你能建議改進嗎?

在這里你去...不是算法,而是程序本身:)

  • 如果你不打算使用argcargv ,那就去掉它們吧
  • 如果輸入“fortytwo”怎么辦? 比較scanf() == 1 ,而不是!= EOF
  • 無需sqrt()的值
  • 不需要returnValue ,可以返回一個常量: return 0;
  • 而不是將所有功能都放在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;
}

偶數(2除外)不能是素數。 所以,一旦我們知道數字不均勻,我們就可以檢查奇數是否是它的因素。

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

您可以對算法進行小幅削減,而不會增加太多的代碼復雜性。 例如,您可以跳過驗證中的偶數,並在找到因素后立即停止搜索。

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;

關於復雜性,如果n是您的輸入數字,那么復雜性不是O(sqrt(n)),因為您大致在大多數sqrt(n)分區和比較中進行?

程序的時間復雜度為O(n*m^0.5) n表示輸入中的素數。 並且m是輸入中最大素數的大小,如果您願意,則為MAX_INT 所以復雜性也可以寫成O(n)其中n要檢查的素數。

對於Big-O, n (通常)是輸入的大小,在您的情況下,將是要檢查的素數。 如果我將這個列表的兩倍大(例如重復它),它將花費(+ - )兩倍的長度,因此O(n)

這是我的算法,Complexity仍為O(n^0.5)但我設法刪除代碼中的一些昂貴的操作......

算法最慢的部分是modulus運算,我設法消除sqrt或做i * i <= n

這樣我就節省了寶貴的周期......它基於sum of odd numbers is always a perfect square.

既然我們正在迭代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;
    }
}

沒有辦法改進算法。 可能有很小的方法來改進您的代碼,但不是算法的基本速度(和復雜性)。

編輯:當然,因為他不需要知道所有因素,只要它是否是素數。 好點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM