C++學習之【使用位操做符求素數分析】

先放普通代碼:ios

#include <iostream>
using namespace std;

void getPrime_1()
{
    const int MAXN = 100;
    bool flag[MAXN];
    int primes[MAXN / 3 + 1], pi=0;
    primes[pi++]=2; //2 是一個素數,先記錄下來
    int i, j;
    for(i=0;i<MAXN;++i)flag[i]=false;//所有置假,沒訪問一個,相應位置真
    for (i = 3; i < MAXN; i+=2){//大於2的偶數必定不是素數,因此只要判斷奇數便可
        if (!flag[i])//若是是素數
        {
            primes[pi++] = i;
            for (j = i; j < MAXN; j += i)//i的倍數必定都不是素數
                flag[j] = true;
        }
    }
    for(i=0;i<pi;++i)
        cout<<primes[i]<<" ";
    cout<<endl;
}

這裏使用了素數表,每個bool型佔用1個字節,共8位二進制位。並且這裏除了多用了不少的無用bool變量在flag數組裏,能夠看到,咱們使用的flag數組只用到2號位以後的全部奇數位。所以,這裏能夠進行壓縮改進一下:git

  1. flag數組減小一半
  2. 使用位操做符使空間佔用減小爲原來的八分之一

這裏使用到對指定位置置1的操做:對於一個整數X能夠經過將1左移n位後,與X進行操做,使X的第n置1github

int X=0;
int n=10;
X |= 1<< n; // 將 X 的第 n 位置 1

因此根據上面兩條,優化後的代碼以下:數組

void getPrime_2()
{    
    const int MAXN = 200;
    const int BitN=(MAXN/2)/32+1;//   
    int flag[BitN];
    int primes[MAXN / 3 + 1], pi=0;
    primes[pi++]=2;  //2 是一個素數,先記錄下來
    int i, j; 
    for(i=0;i<BitN;++i)flag[i]=0;//全置0,每訪問過一個,相應位置1
    for (i = 3; i < MAXN; i+=2){ //大於2的偶數必定不是素數,因此只要判斷奇數便可
        if (!((flag[(i/2) / 32] >> ((i/2) % 32)) & 1))
        {
            primes[pi++] = i;
            //i的倍數必定都不是素數,其中,j加上一個i後爲偶數,上一級已經不考慮了,因此還要加上一個i
            for (j = i; j < MAXN; j =j+i+i)
                flag[(j/2) / 32] |= (1 << ((j/2) % 32));
        }
    }
    for(i=0;i<pi;++i)
        cout<<primes[i]<<" ";
    cout<<endl;
}

首先,根據最大數,判斷須要32的整型多少個:優化

BitN=(MAXN/2)/32+1;

MAXN/2去除了全部偶數位,(MAXN/2)/32+1表明須要多少32位的整型。
其次,全部求位的操做,都要除以2以去除偶數位的影響。
同時:spa

for (j = i; j < MAXN; j =j+i+i)
                flag[(j/2) / 32] |= (1 << ((j/2) % 32));

其中的 j=j+i+i; 是由於 ji 自己都是一個奇數,相加後爲偶數,不考慮,因此還要加上一個 i
經過這種方式,縮小了佔用空間。
代碼文件code

MAXN=200 時,實際的素數只有45個,但primes的大小倒是66,那麼這裏怎麼能更進一步的優化呢?blog

附:

因爲這裏使用的C++,那麼爲何不使用STL中已有的容器類bitset呢:get

#include <bitset>
void getPrime_3(){
    const int MAXN = 100;
    bitset<(MAXN/2+1)> flag(0); //不考慮偶數位
    int primes[MAXN / 3 + 1], pi=0;
    primes[pi++]=2;
    int i, j;
    for (i = 3; i < MAXN; i+=2){ //大於2的偶數必定不是素數,因此只要判斷奇數便可
        if (!(flag.test(i/2)))//若是對應位爲false
        {
            primes[pi++] = i;
            //i的倍數必定都不是素數,其中,j加上一個i後爲偶數,上一級已經不考慮了,因此還要加上一個i
            for (j = i; j < MAXN; j =j+i+i)
                flag.set(j/2);//設置對應位爲true
        }
    }
    for(i=0;i<pi;++i)
        cout<<primes[i]<<" ";
    cout<<endl;
}

能夠看到,使用STL的代碼仍是比較簡潔的!it

下面去除重複計算的部分

剛纔已經將偶數的計算去除了,但仍然還會有一部分的重複計算,好比:
i=3時,會訪問15,同時,當i=5時,也會訪問15
先上代碼:

void getPrime_4()
{
    const int MAXN = 100;
    bitset<(MAXN/2+1)> flag(0); //不考慮偶數位
    int primes[MAXN / 3 + 1], pi=0;
    primes[pi++]=2;
    int i, j;
    for (i = 3; i < MAXN; i+=2)
    {
        if (!(flag.test(i/2)))
            primes[pi++] = i;
        for (j = 1; (j < pi)  && (i * primes[j] < MAXN); j++) // 1
        {
            flag.set(i*primes[j]/2);
            if (i % primes[j] == 0) // 2
                break;
        }
    }
    
    for(i=0;i<pi;++i)
        cout<<primes[i]<<" ";
    cout<<endl;
}

註釋 1 表示讓當前奇數和已經查出來的素數進行逐個相乘,相乘後的結果數確定不是素數!
註釋 2 對於任何數來講,若是它若是是該素數的倍數那麼它就不能再與素數表中該素數以後的素數相乘了,如9是3的倍數,因此在9*3以後就不能再去用計算9*5了。
當數據量很大時,getPrime_4()getPrime_1()的差異將在時間和空間上是很大的!

若是閱讀效果很差,請查看GitHub對應頁面:

使用位操做符求素數

GitHubBlog /

相關文章
相關標籤/搜索