今天在博文C語言初學者代碼中的常見錯誤與瑕疵(5)看了一個關於素數的算法題,以下:html
素數算法
在世博園某信息通訊館中,遊客可利用手機等終端參與互動小遊戲,與虛擬人物Kr. Kong 進行猜數比賽。ide
當屏幕出現一個整數X時,若你能比Kr. Kong更快的發出最接近它的素數答案,你將會得到一個意想不到的禮物。函數
例如:當屏幕出現22時,你的回答應是23;當屏幕出現8時,你的回答應是7;post
若X自己是素數,則回答X;若最接近X的素數有兩個時,則回答大於它的素數。測試
輸入:第一行:N 要競猜的整數個數優化
接下來有N行,每行有一個正整數Xurl
輸出:輸出有N行,每行是對應X的最接近它的素數spa
樣例:輸入rest
4
22
5
18
8
輸出
23
5
19
7
看到這個算法題咱們首先要作的就是實現一個函數,來求出一個數是不是質數。下面咱們來簡單的實現一下:
bool isPrime(int num) { if(num < 2) return false; for(int i=2; i*i<num; ++i){ if(num % i == 0) return false; } return true; }
因爲這個函數在算法中會屢次用到,咱們用下面的測試來查看這個基本函數的效率
void test(){ clock_t start = clock(); for(int i=1; i <= 100000; ++i){ isPrime(1000000007); } clock_t end = clock(); cout << endl << static_cast<double>(end - start)/CLOCKS_PER_SEC << endl; }
運行,獲得結果12.158
由於期間咱們可能會進行重複的計算,對這個問題我一開始想到的解決方法就是創建一個質數表,咱們能夠直接經過查找表來快速的肯定一個數是不是質數。當要判斷的數很大時,須要佔用很大的空間來建表,爲了節約空間,我將每一位都充分用上了。
#define GETNUM(x) psum[((x)>>3)]&(1<<(((x)&7)-1)) #define SETNUM(x) psum[((x)>>3)] &= (~(1<<(((x)&7)-1))) bool isPrime(int num) { if(num < 2) return false; int size = (num>>3)+1; unsigned char *psum = new unsigned char[size]; memset(psum, 0xFF, size); for(int i=2; i*i<num; ++i){ if(GETNUM(i)){ for(int j=i<<1; j<=num; j+=i){ SETNUM(j); } } } bool result = GETNUM(num); delete [] psum; return result; }
由於質數表創建起來之後,以後的判斷直接取值就好了,因此咱們就不作循環了,直接看它運行一次的時間,居然用了29.853!耗時太長了,建這個表的時間能夠進行20萬次試除法判斷了。
在通過必定的分析後,我將這個過程進行了一下優化
#define GETNUM(x) psum[((x)>>3)]&(1<<(((x)&7)-1)) #define SETNUM(x) psum[((x)>>3)] &= (~(1<<(((x)&7)-1))) bool isPrime2(int num) { if(num < 2) return false; int size = (num>>3)+1; unsigned char *psum = new unsigned char[size]; memset(psum, 0xFF, size); for(int j=4; j<num; j+=2){ SETNUM(j); } for(int i=3; i*i<num; ++i){ if(GETNUM(i)){ int step = i<<1; for(int j=i*i; j<=num; j+=step){ SETNUM(j); } } } bool result = GETNUM(num); delete [] psum; return result; }
上面的優化,我先是直接將2的倍數都淘汰掉,接着,基於在進行i的倍數判斷時,全部i的i-1如下的倍數都已經被淘汰掉了這一點,直接從i的平方開始淘汰,並且基於偶數倍能被2整除這一點,將步長調整爲i*2.
通過優化,時間縮短爲16.429,但是這個結果明顯是不能讓人滿意滴。。。
這時我參看了一下博文一個超複雜的間接遞歸——C語言初學者代碼中的常見錯誤與瑕疵(6),發現只是計算部分質數表,再利用質數表來加快質數的試除法這個方案頗有可行性,因而趕忙行動。先進行預算,再進行試除法判斷質數。
void precalc(int size, int * primes, int &pnum) { bool *psum = new bool[size+1]; for(int j=4; j<=size; j+=2){ psum[j] = false; } memset(primes, 0, size * sizeof(int)); primes[0] = 2; pnum = 1; int i=3; for(; i*i<=size; ++i){ if(psum[i]){ primes[pnum] = i; ++pnum; int step = i<<1; for(int j=i*i; j<=size; j+=step){ psum[j] = false; } } } for(;i<=size; ++i){ if(psum[i]){ primes[pnum] = i; ++pnum; } } delete [] psum; } bool isPrime(int num, const int * primes, int pnum) { for(int i=0; i<pnum; ++i){ if(num % primes[i] == 0) return false; } return true; } void test(){ clock_t start = clock(); const int num = 1000000007; int size = static_cast<int>(sqrt(static_cast<double>(num))); int *primes = new int[size]; int pnum; precalc(size, primes, pnum); for(int i=1; i <= 100000; ++i){ isPrime(num, primes, pnum); } delete [] primes; clock_t end = clock(); cout << endl << static_cast<double>(end - start)/CLOCKS_PER_SEC << endl; }
改進的結果是使人振奮滴,時間縮短爲0.021.
解決了素數判斷問題,獲得想要的算法就很容易了
void precalc(int size, int * primes, int &pnum) { bool *psum = new bool[size+1]; for(int j=4; j<=size; j+=2){ psum[j] = false; } memset(primes, 0, size * sizeof(int)); primes[0] = 2; pnum = 1; int i=3; for(; i*i<=size; ++i){ if(psum[i]){ primes[pnum] = i; ++pnum; int step = i<<1; for(int j=i*i; j<=size; j+=step){ psum[j] = false; } } } for(;i<=size; ++i){ if(psum[i]){ primes[pnum] = i; ++pnum; } } delete [] psum; } bool isPrime5(int num, const int * primes, int pnum) { for(int i=0; i<pnum && primes[i]*primes[i] < num; ++i){ if(num % primes[i] == 0) return false; } return true; } int get_nearest(int num, const int * primes, int pnum) { if(isPrime5(num, primes, pnum)) return num; int len; if(num % 2 == 0){ len = 1; } else{ len = 2; } while(true){ if(isPrime5(num+len, primes, pnum)) return num + len; if(isPrime5(num-len, primes, pnum)) return num - len; len += 2; } } int _tmain(int argc, _TCHAR* argv[]) { cout<<"請輸入數據"<<endl; int count; cin>>count; int *data = new int[count]; //最大的一個數 int maxnum = 0; for(int i=0; i<count; i++){ cin>>data[i]; if(maxnum < data[i]){ maxnum = data[i]; } } int size = static_cast<int>(sqrt(static_cast<double>(maxnum))); int *primes = new int[size]; int pnum; precalc(size, primes, pnum); for(int i=0; i<count; i++){ cout<<get_nearest(data[i], primes, pnum)<<endl; } delete [] primes; delete [] data; cin.get(); return 0; }