c語言:實現對於給定的正整數N,依次打印出小於等於N的所有素數。兩種方法及其優化
請編寫一個程式,實現對於給定的正整數N,依次打印出小於等於N的所有素數。
方法一:試除法
由素數的定義得到如下程式:
#include<stdio.h>
int print_prime(int num)//prime表示素數
{
int i = 0;
for (i = 2; i <= num; i++)
{
int j = 0;
for (j = 2; j < i; j++)
{
if (i%j == 0)
{
break;
}
}
if (i==j)
{
printf("%d\t",i);
}
}
}
int main()
{
int num;
printf("請輸入一個正整數:");
scanf("%d", &num);
if (num > 1)
{
printf("打印出所有的素數:");
print_prime(num);
}
else
{
printf("不存在小於等於%d的素數", num);
}
printf("\n");
return 0;
}
上面的程式碼中,在判斷素數時一直從2試除到n-1。這裡從n/2之後的數到n-1的試除顯然是多餘的,比如正整數7不能被2整除,必然不能被4,6整除。
優化1:
試除的範圍優化到[2,n/2],則工作量減少一半,程式碼如下:
int print_prime(int num)//prime表示素數
{
int i = 0;
for (i = 2; i <= num; i++)
{
int j = 0;
for (j = 2; j <= i/2; j++)//修改部分
{
if (i%j == 0)
{
break;
}
}
if (j == (i/2+1))//修改部分
{
printf("%d\t", i);
}
}
}
既然能將試除範圍優化到[2,n/2],那麼這個範圍是不是還能繼續優化呢?答案是肯定的,在[2,n/2]這個範圍裡(√n,n/2]的試除也是多餘的。因為因數是成對出現的,如16可分解為:1和16 、2和8、4和4、8和2、16和1。這些因數裡必然有一個小於等於4。故只需試除小於等於√n的數就可以了。
優化2:
試除範圍優化為[2,√n],程式碼如下:
#include<stdio.h>
#include<math.h>//修改部分
int print_prime(int num)//prime表示素數
{
int i = 0;
for (i = 2; i <= num; i++)
{
int j = 0;
for (j = 2; j <=sqrt(i); j++)//修改部分
{
if (i%j == 0)
{
break;
}
}
if (j > sqrt(i))//修改部分
{
printf("%d\t", i);
}
}
}
上面所有的程式碼在找素數的時候是從2到n,在這個範圍內除了2之外的偶數都不是素數,所以可以跳過這些偶數。
還有試除範圍內除了2之外的偶數也是沒有必要的,因為如果不能被2整除,必然不能被大於2的偶數整除。
優化3:
尋找素數時和試除範圍內跳過除2之外的偶數。程式碼如下:
int print_prime(int num)//prime表示素數
{
int i = 0;
printf("%d\t", 2);//修改部分
for (i = 3; i <= num; i+=2)//修改部分
{
int j = 2;
for (j = 3; j <= sqrt(i); j+=2)//修改部分
{
if (i%j == 0)
{
break;
}
}
if (j > sqrt(i))
{
printf("%d\t", i);
}
}
}
在上面的程式碼中,試除範圍內的一些數也是不必要的。比如判斷101是否為素數時,要分別試除小於10的2和所有奇數,即2、3、5、7、9,其中對9的試除是不必要的。即對所有的非素數的試除是不必要的,因為非素數必然可分解為比它小的素數的乘積,既然它的質因數不能整除某個數,這個數必然也不能。故試除的範圍可縮小到小於等於√n的所有素數。
優化4:
只試小於√n的素數,那麼問題來了,要試除這些素數時必然要將前面求出的素數儲存起來,開闢多大一塊空間合適呢?因為n的大小未知,所以無法確定開闢多少空間。這裡暫時沒有完全解決的辦法,我的做法是先開闢大小為1000的空間,需要時再修改。
#include<stdio.h>
#include<math.h>
#define MAX 1000 //定義陣列的大小
int print_prime(int num)//prime表示素數
{
int arr[MAX] = { 0 };
int i = 0,j=1;
printf("%d\t", 2);
arr[0] = 2;
for (i = 3; i <= num; i += 2)
{
int k = 0;
while (arr[k]>0&&arr[k]<=sqrt(i))
{
if (i%arr[k] == 0)
{
break;
}
k++;
}
if (!arr[k]||(arr[k] > sqrt(i)))
{
printf("%d\t", i);
arr[j] = i;
j++;
}
}
}
int main()
{
int num;
printf("請輸入一個正整數:");
scanf("%d",&num);
if (num > 1)
{
printf("打印出所有的素數:");
print_prime(num);
}
else
{
printf("不存在小於等於%d的素數",num);
}
printf("\n");
return 0;
}
方法二:篩選法
這種方法求素數的思想就是,不斷篩去最小的數的倍數。這個最小的數必然是素數。
比如最小的素數是2,去掉所有2的倍數;接下來最小的數是3,3就是素數,去掉所有的3的倍數;依次類推,直到最小的數小於等於√n為止。為什麼是√n呢? 在上面的試除法中講到只要試除小於等於√n的所有素數即可判斷出小於等於n的所有素數,這裡同樣適用,只要去掉所有的小於等於√n的所有數的倍數,剩下的數就是小於等於n的所有素數。
程式碼如下:
#include<stdio.h>
#include<math.h>
#define MAX 1000 //定義陣列的大小
int print_prime(int num)//prime表示素數
{
int arr[MAX] ;
int i = 0,j=0;
for (i = 0; i < num - 2; i++)//初始化陣列[2,num]
{
arr[i] = i + 2;
}
while (arr[j]<=sqrt(num))//除數的範圍
{
for (i = j + 1; i < num - 1; i++)
{
if (arr[i]%arr[j] ==0)//篩去arr[i]的倍數
{
arr[i] = 0;
}
}
j++;
while(!arr[j])//確定最小數
{
j++;
}
}
for (i = 0; i < num - 1; i++)
{
if (arr[i])
{
printf("%d\t",arr[i]);
}
}
}
int main()
{
int num;
printf("請輸入一個正整數:");
scanf("%d",&num);
if (num > 1)
{
printf("打印出所有的素數:");
print_prime(num);
}
else
{
printf("不存在小於等於%d的素數",num);
}
printf("\n");
return 0;
}
上面的程式碼有一個很明顯的缺陷就是開闢空間過大,如何來解決這個問題呢?
上述程式碼所開闢的空間為int型,佔用4個位元組,佔用空間太多,可以構造一個bool型陣列,bool型是一個基本的資料型別,佔1個位元組,以下標來儲存資料,節省了75%的空間。
優化:
構造bool型陣列,以下標來儲存資料,每個數只佔一個位元組。
程式碼如下:
#include<stdio.h>
#include<stdbool.h>
#include<math.h>
#define MAX 1000 //定義陣列的大小
void print_prime(int num)//prime表示素數
{
bool arr[MAX] ;
int i = 0,j=2;
for (i = 0; i <num ; i++)//初始化陣列為真
{
arr[i] = 1;
}
while (arr[j]<=sqrt(num))//除數的範圍
{
for (i = j + 1; i <= num ; i++)
{
if (i%j ==0)//篩去arr[i]的倍數
{
arr[i] = 0;
}
}
j++;
while(!arr[j])//確定最小數
{
j++;
}
}
for (i = 2; i <=num ; i++)//列印素數
{
if (arr[i])
{
printf("%d\t",i);
}
}
}
int main()
{
int num;
printf("請輸入一個正整數:");
scanf("%d",&num);
if (num > 1)
{
printf("打印出所有的素數:");
print_prime(num);
}
else
{
printf("不存在小於等於%d的素數",num);
}
printf("\n");
return 0;
}
結果1:
請輸入一個正整數:9
打印出所有的素數:2 3 5 7
請按任意鍵繼續. . .
結果2:
請輸入一個正整數:1
不存在小於等於1的素數
請按任意鍵繼續. . .