1. 程式人生 > >『素數(Prime)判定和線性尤拉篩法(The sieve of Euler)』

『素數(Prime)判定和線性尤拉篩法(The sieve of Euler)』

  <更新提示>
  
  <第一次更新>
  
  <正文>
  
  素數(Prime)及判定
  
  定義
  
  素數又稱質數,一個大於1的自然數,除了1和它自身外,不能整除其他自然數的數叫做質數,否則稱為合數。
  
  1既不是素數也不是合數。
  
  判定
  
  如何判定一個數是否是素數呢?顯然,我們可以列舉這個數的因數,如果存在除了它本身和1以外的因數,那麼這個數就是素數。
  
  在列舉時,有一個很簡單的優化:一個合數nn必有一個小於等於n−−√n的因數。
  
  證明如下:
  
  假設一個合數nn沒有小於等於n−−√n的因數。
  
  由於nn為合數,所以除了nn與11以外,它至少還有兩個因數p1(p1>n−−√)p1(p1>n)和p2(p2>n−−√)p2(p2>n),滿足p1p2=np1p2=n。
  
  與p1>n−−√,p2>n−−√p1>n,p2>n矛盾,故假設不成立。
  
  所以我們得到了O(n−−√)O(n)效率的素數判定演算法。
  
  Code:Code:
  
  inline bool check(k)
  
  {
  
  for(int i=2;i*i<=k;i++)
  
  if(k%i==0)return 0;
  
  return 1;
  
  }
  
  篩法(Sieve)求素數
  
  現在有一個新的問題模型,如果我們需要求解1−n1−n的所有素數,那麼直接用判定法效率顯然太低了。我們需要更高效率的演算法,由此我們引入篩法。
  
  埃氏篩法(The sieve of Eratosthenes)
  
  這是篩法思想的基本模型。根據算數基本定理,我們得知:
  
  k=pa11⋅pa22⋅...⋅pakk
  
  k=p1a1·p2a2·...·pkak
  
  即任意一個數kk都是由若干素數相乘得到的。
  
  那麼我們可以列舉2−n2−n的每一個數,如果這個數沒被標記,則說明這個數是素數,記錄這個數,並標記這個數的所有倍數不是素數。
  
  那麼這樣就可以求解1−n1−n的所有素數了。時間複雜度為O(n ln(ln n))O(n ln(ln n))。
  
  實現
  
  這就是OI競賽中最常用的素數求解演算法了,實現也非常簡單。
  
  Code:Code:
  
  #include<bits/stdc++.h>
  
  using namespace std;
  
  int cnt=0,n,flag[100080]={},Prime[100080]={};
  
  inline void sieve(void)
  
  {
  
  for(int i=2;i<=n;i++)
  
  {
  
  if(!flag[i])Prime[++cnt]=i;else continue;
  
  for(int j=i*2;j<=n;j+=i)flag[j]=true;
  
  }
  
  }
  
  int main(void)
  
  {
  
  cin>>n;
  
  sieve();
  
  for(int i=1;i<=cnt;i++)cout<<Prime[i]<<" ";
  
  cout<<endl;
  
  }
  
  尤拉篩法(The sieve of Euler)
  
  尤拉篩法就是基於埃氏篩法的優化。
  
  在模擬埃氏篩法的過程中,我們不難發現有很多合數會被它的各個素因子篩好幾次,我們可以基於這種情況進行優化:每個合數必有一個最小素因子,用這個因子篩掉合數
  
  所以,我們直接利用之前求出的素數進行篩數,如果發現當前這個數已經是之前某個素數的倍數時,那就說明這個數在以後會由某個更大的數乘以這個小素數篩去,同理,之後的篩數也是沒有必要的,這時候就可以跳出迴圈了。
  
  這樣,我們就能保證每一個數只被篩一次,就實現了線性時間複雜度的篩法。
  
  實現
  
  尤拉篩法和埃氏篩法大體相似,但細節有所不同,注意不要搞混。
  
  Code:Code:
  
  #include<bits/stdc++.h>
  
  using namespace std;
  
  int cnt=0,n,flag[100080]={},Prime[100080]={};
  
  inline void seive(void)
  
  {
  
  for(int i=2;i<=n;i++)
  
  {
  
  if(!flag[i])Prime[++cnt]=i;
  
  //注意,這裡沒了continue,因為在篩某個數時需要用到它的最大因數,而這個數可能是個合數,所以不管是素數還是合數,都要執行以下的篩數過程
  
  for(int j=1;j<www.michenggw.com =www.mcyllpt.com cnt&&i*Prime[j]<=n;j++)
  
  {
  
  flag[i*Prime[j]]=1;
  
  if(i%Prime[j]==0)break;
  
  }
  
  }
  
  }
  
  int main(void)
  
  {
  
  cin>>n;
  
  seive(www.gcyL157.com);
  
  for(int i=1;i<=cnt;www.mhylpt.com/  i++)cout<<Prime[i]<<" ";
  
  cout<<endl;
  
  }