閱讀《C語言編程—一本全面的C語言入門教程》一書,看到了質數生成的小程序,特此記錄
1. 直接求解
這是最簡單和無腦的暴力算法了,直接雙重循環,復雜度為\(O(N^2)\):
void prime_generator_1(void)
{
int p;
int d;
bool isPrime;
for( p = 2; p <= MAX_NUM; ++p)
{
isPrime = true;
for( d = 2; d < p; ++ d)
{
if(p % d == 0)
{
isPrime = 0;
break;
}
}
if( isPrime != false)
{
printf("%i ", p);
}
}
printf("\n");
}
2. 一些改進
很明顯的一個改進是,任何大於2的偶數都不可能是質數,因此,在外循環中p
從3開始,每次遞增2;內循環與之類似。額外注意的是,2既是偶數,也是質數,需單獨處理。
void prime_generator_2(void)
{
int p;
int d;
bool isPrime;
printf("%i ", 2);
for( p = 3; p <= MAX_NUM; p += 2)
{
isPrime = true;
for( d = 3; d < p; d += 2)
{
if(p % d == 0)
{
isPrime = 0;
break;
}
}
if( isPrime != false)
{
printf("%i ", p);
}
}
printf("\n");
}
3. 繼續改進
以上的改進雖然有效,但算法的效率並沒有根本性的提高,特別是在產生大型的質數表(>1,000,000),算法的效率至關重要。
有兩個標准可以幫助提高效率
- 任何一個非質整數都可以分解為多個質數的乘積,也就是說,如果一個數不能被任何質數整除,那么這個數就是一個質數。
- 任何一個非質數的整數,肯定會有一個小於其平方根的質因數。
void prime_generator_3(void)
{
int primes[MAX_NUM];
int primeIndex = 2;
bool isPrime;
primes[0] = 2;
primes[1] = 3;
for( int p = 5; p <= MAX_NUM; p += 2)
{
isPrime = true;
for( int i = 1; isPrime && primes[i] <= sqrt(p); ++i)
{
if(p % primes[i] == 0)
{
isPrime = false;
break;
}
}
if( isPrime == true)
{
primes[primeIndex ++] = p;
}
}
for(int i = 0; i < primeIndex; ++ i)
{
printf("%i ", primes[i]);
}
printf("\n");
}
4. 埃拉托色尼篩網法
其步驟為:
- 定義整數數組
P
, 將所有的數組元素設置為0. - 設置變量
i
等於2。 - 如果
i > n
,算法結束。 - 如果
P[i]
等於0, 那么i
是一個質數。 - 對於所有的正整數
j
, 如果i * j <= n
,將數組元素P[i*j]
設置為1。 - 將
i
的值增加1,返回第3步。
代碼如下:
void prime_generator_4(void)
{
int primes[MAX_NUM + 1] ={0};
int i = 2;
while(i <= MAX_NUM)
{
if(primes[i] == 0)
{
printf("%i ", i);
}
for( int j = 2; i * j <= MAX_NUM; ++j)
{
primes[i * j] = 1;
}
++i;
}
printf("\n");
}
5. 對比
當MAX_NUM =500000
時,前三種算法的計算時長分別為:
可以看到,經過改進,算法的時間消耗得到大幅降低,表明改進算法的有效性。
6. 關於數組越界帶來的死循環問題
這個問題在第4部分代碼編寫過程中出現過,后來經過搜索找到了原因,數組越界導致循環變量被重新賦值。其匯編代碼如下:
在此驗證了一個簡單的C語言數組循環語句,對循環變量和數組越界元素的地址進行了輸出,可以看到,二者地址一致,證明了以上的論斷。