【題目】求n以內的素數個數


最近在leetCode上刷提,還是滿鍛煉人的,為以后面試打基礎吧。不多說下面開始。

 

問題:求[2,n]之間的素數的個數。

來源:leetCode OJ

提示:

Let's start with a isPrime function. To determine if a number is prime, we need to check if it is not divisible by any number less than n. The runtime complexity of isPrime function would be O(n) and hence counting the total prime numbers up to n would be O(n2). Could we do better?

 

先讓我們寫一個函數isPrime用於判斷給定的數是否是素數,為了判別總一個數是否是素數,我們需要依次檢查比這個數小的數能否整除n。那么函數isPrime的時間復雜度就將是O(n),因此總體的時間復雜度就會達到O(n2),我們能做的更好嗎?

 

 

第一次嘗試

預備知識

① 什么是素數:素數(又叫質數),與之相反的是合數。如果只考慮非負數范圍內,一個大於1的數,它只能被 1 和 他本身整除。這樣的數就是素數。

② 0 和 1既不是質數也不是合數。

③ 素數定理:如果一個數x是素數,那么在整數范圍[2,√x ]之間,找不到任何能整除x 的整數。因此,我們不必對 [2, n) 的所有整數去嘗試,而只需要對 [2,√x]之間的數嘗試整除就OK了,節約了時間。

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判斷一個數是否是素數 
參數x:待判斷的素
返回:是素數返回true,否則返回false 
*/
bool isPrime(int x)
{
    for(int i=2;i<=int(sqrt(x));++i)
    {
        if(x%i==0) return false;
    }
    return true;
}

/**
作用:統計[2,n]之間的素數的個數 
參數:n
返回:素數的個數 
*/
int countPrimes(int n) {
    int count=0;
    
    if(n<2) return 0;

    for(int i=2;i<=n;++i)
    {
        if(isPrime(i))   //如果 i為素數
        {
            ++count;
        }
    }
    return count;

}


int main()
{
    
    clock_t start = clock();
    int total = countPrimes(700000);
    clock_t end   = clock();
    
    cout<<"耗時:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl;
    
    
    return 0;
}


/**********測試數據**************************
n=200000    81毫秒 
n=700000    385毫秒
*/

 

這是很多人第一感覺寫出來的solution,它最大問題在於使用了sqrt函數。第一:sqrt是用來處理浮點數的,而浮點數的計算速度遠遠慢於integer。第二,函數調用也會造成時間的浪費。第三:浮點數的存儲誤差可能引出致命錯誤,如  sqrt(9)  可能等於 2.9999999 ,那么 int(sqrt(9)) 就等於2 而不是3。

但是我從來都不是這樣寫的,這個讓我印象很深,在我的編程啟蒙書 《C prime Plus》書中,它教我使用  C/C++內置於語言的運算符乘 * ,而不是sqrt()函數。

第二次嘗試

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判斷一個數是否是素數 
參數x:待判斷的素
返回:是素數返回true,否則返回false 
*/
bool isPrime(int x)
{
    for(int i=2;i*i<=x;++i)   //改進:使用 i*i<=x 而不是 i<= sqrt(x)  
    {
        if(x%i==0) return false;
    }
    return true;
}

/**
作用:統計[2,n]之間的素數的個數 
參數:n
返回:素數的個數 
*/
int countPrimes(int n) {
    int count=0;
    
    if(n<2) return 0;

    for(int i=2;i<=n;++i)
    {
        if(isPrime(i))   //如果 i為素數
        {
            ++count;
        }
    }
    return count;

}


int main()
{
    
    clock_t start = clock();
    int total = countPrimes(700000);
    clock_t end   = clock();
    
    cout<<"耗時:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl;
    
    
    return 0;
}


/*********************測試數據*****************************
n=200000    46毫秒 
n=700000    194毫秒
*/

 

可以發現,小小的改變,時間復雜度減少了大半!!!讓我覺得很遺憾的是,很少有人知道這個技巧,leetCode上,也沒有發現使用這個技巧的。

 

第三次嘗試

前面的2個solution是不能Accept的,時間復雜度不達標。下面我又開始了google,找到了如下的優化方案。

預備知識:

① 合數一定能分解為 若干個 質素相乘 。如 27 = 3x3x3 ,155 = 5x31

② 原命題的真假性 和他的逆否命題相同。因此我們可以推導出第①命題的逆否命題--->: 不能分解為 素數相乘的數一定是質素(素數)。

因此根據這個推導出的定理,我們不再需要將待判斷的所有數x去對 [2,sqrt(x)]之間的數試除,而只需要去對 [2,sqrt(x)]之間的素數試除就OK了。所以:我們必須在迭代判斷素數的過程中,將已經判定為素數的數用數組存儲起來,因為后面更大的數判斷時,需要用比他小的素數(在[2,sqrt(x)]之間的)去試除做判斷。那么這個數組需要多大呢?

[0,n]之間有 n +1 個整數,然而0 和 1不是素數也不是質素,因此剩下 n +1 -2 = n-1 個數。素數偶數各一半,除了2,偶數一定不是素數,因此我們大致將這個數組大小定義為  (n-1)/2 +1 

好了,時間 和 空間上都過了優化,下面試試吧。

 

#include<iostream>
#include<cassert>
#include<time.h>
#include<cmath>
using namespace std;

/**
作用:判斷一個數是否是素數 
參數x:待判斷的素
參會primes:存儲比x小的所有素數 的數組 
返回:是素數返回true,否則返回false 
*/
bool isPrime(int x,int *primes)
{
    for(int i=0;primes[i]*primes[i]<=x;++i) //用primes中存儲的素數做為試除因子。 
    {
        if(x%primes[i]==0) return false;
    }
    return true;
}


/**
作用:統計[2,n]之間的素數的個數 
參數:n
返回:素數的個數 
*/
int countPrimes(int n) {
    
    if(n<2) return 0;
    
    int count=0;
    int* primes = new int[(n-1)/2+1] ;
    
    if(n >=2) primes[count++] = 2;   //2是第一個素數,直接將他放入數組。 

    for(int i=3;i<=n;++i)
    {
        if(isPrime(i,primes))   //如果 i為素數
        {
            primes[count++] = i; //則將 i存儲到數組中 
        }
    }

delete[] primes;
return count; } int main() { clock_t start = clock(); int total = countPrimes(700000); clock_t end = clock(); cout<<"耗時:"<<(double(end-start))/CLOCKS_PER_SEC*1000<<"毫秒"<<endl; return 0; } /****************測試數據****************** n=200000 13毫秒 n=700000 50毫秒 */

 

時間復雜度又大大減少了!!!可見,我們的程序優化的空間往往很大。

最后,我的程序終於被AC了 ,不過,還可以繼續優化,下面是我的程序的所處的時間復雜度的位置,前面有70% 的solution比我更優 。優化尚未成功,碼農仍需努力。

 :)

 

 

 

 

 更佳的solution 請看這篇

 

 

 歡迎轉載,請注明出處:www.cnblogs.com/lulipro

 為了獲得更好的閱讀體驗,請訪問原博客地址。

限於本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。

代碼鋼琴家

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM