1. 程式人生 > >幾個常見演算法的整理

幾個常見演算法的整理

1.gcd演算法

int gcd(int a, int b)  
{  
    return (a==0)?b:gcd(b%a, a);  
}  
//遞推法
int gcd(int m, int n)  
{  
    while(m>0)  
    {  
        int c = n % m;  
        n = m;  
        m = c;  
    }  
    return n;  
}  
/* 連續整數試探演算法,計算最大公約數 */  
int gcd(int m, int n)  
{  
    if(m>n) {  
        int temp = m;  
        m = n;  
        n = temp;  
    }  
    int t = m;  
    while(m%t || n%t)  
    {   
        t--;  
    }  
    return t;  
}  

2.素數判斷(該內容轉載自http://blog.csdn.net/arvonzhang/article/details/8564836)
//最笨做法
bool IsPrime(unsigned n)  
{  
    if (n<2)  
    {     
        //小於2的數即不是合數也不是素數  
        throw 0;  
    }  
    for (unsigned i=2;i<n;++i)  
    {   
        //和比它小的所有的數相除,如果都除不盡,證明素數  
        if (n%i==0)  
        {  
            //除盡了,則是合數  
            return false;  
        }  
    }  
    return true;  
}  
//一般做法
   num = 0;
    for(i=2; i<=n; i++)
    {  for(j=2; j<=sqrt(i); j++)
         if( j%i==0 ) break;
       if( j>sqrt(i) ) prime[num++] = i;  
    }

//素數表做法
bool IsPrime2(unsigned n)  
{  
    if ( n < 2 )  
    { // 小於2的數即不是合數也不是素數  
        throw 0;  
    }  
    static unsigned aPrimeList[] = { // 素數表  
        1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,  
        43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 113,   
        193, 241, 257, 337, 353, 401, 433, 449, 577, 593, 641,   
        673, 769, 881, 929, 977, 1009, 1153, 1201, 1217, 1249,   
        1297,1361, 1409, 1489, 1553, 1601, 1697, 1777, 1873,   
        1889, 2017, 2081, 2113, 2129, 2161, 2273, 2417, 2593,   
        2609, 2657, 2689, 2753, 2801, 2833, 2897, 3041, 3089,   
        3121, 3137, 3169, 3217, 3313, 3329, 3361, 3457, 3617,   
        3697, 3761, 3793, 3889, 4001, 4049, 4129, 4177, 4241,   
        4273, 4289, 4337, 4481, 4513, 4561, 4657, 4673, 4721,   
        4801, 4817, 4993, 5009, 5153, 5233, 5281, 5297, 5393,   
        5441, 5521, 5569, 5857, 5953, 6113, 6257, 6337, 6353,   
        6449, 6481, 6529, 6577, 6673, 6689, 6737, 6833, 6961,   
        6977, 7057, 7121, 7297, 7393, 7457, 7489, 7537, 7649,   
        7681, 7793, 7841, 7873, 7937, 8017, 8081, 8161, 8209,   
        8273, 8353, 8369, 8513, 8609, 8641, 8689, 8737, 8753,   
        8849, 8929, 9041, 9137, 9281, 9377, 9473, 9521, 9601,   
        9649, 9697, 9857   
    };  
      
    const int nListNum = sizeof(aPrimeList)/sizeof(unsigned);//計算素數表裡元素的個數  
    for (unsigned i=2;i<nListNum;++i )  
    {   
        if(n/2+1<aPrimeList[i])  
        {  
            return true;  
        }  
        if(0==n%aPrimeList[i])  
        {  
            return false;  
        }  
    }  
    /*由於素數表中元素個數是有限的,那麼對於用素數表判斷不到的數,就只有用笨蛋辦法了*/  
    for (unsigned i=aPrimeList[nListNum-1];i<n/2+1;i++ )  
    {   
        if (0==n%i)  
        {   
            // 除盡了,合數   
            return false;  
        }  
    }  
    return true;   
}  
費馬小定理:對於兩個互質的素數N和P,必有:N^(P-1)%P=1 
演算法思路:
對於N,從素數表中取出任意的素數對其進行費馬測試,如果取了很多個素數,N仍未測試失敗,那麼則認為N是素數。當然,測試次數越多越準確,但一般來講50次就足夠了。
bool IsPrime3(unsigned n)  
{  
    if ( n < 2 )  
    {   
        // 小於2的數即不是合數也不是素數  
        throw 0;  
    }  
    static unsigned aPrimeList[] = {  
        2, 3, 5, 7, 11, 17, 19, 23, 29, 31, 41,  
        43, 47, 53, 59, 67, 71, 73, 79, 83, 89, 97  
    };  
    const int nListNum = sizeof(aPrimeList) / sizeof(unsigned);  
    for (int i=0;i<nListNum;++i)  
    {   
        // 按照素數表中的數對當前素數進行判斷  
        if (1!=Montgomery(aPrimeList[i],n-1,n)) // 蒙格馬利演算法  
        {  
            return false;  
        }  
    }  
    return true;  
}  
拉賓米勒測試演算法---目前最快的判斷素數演算法
拉賓米勒測試是一個不確定的演算法,只能從概率意義上判定一個數可能是素數,但並不能確保。演算法流程如下:
       1.選擇T個隨機數A,並且有A<N成立。
       2.找到R和M,使得N=2*R*M+1成立。
       快速得到R和M的方式:N用二進位制數B來表示,令C=B-1。因為N為奇數(素數都是奇數),所以C的最低位為0,從C的最低位的0開始向高位統計,一直到遇到第一個1。這時0的個數即為R,M為B右移R位的值。
       3.如果A^M%N=1,則通過A對於N的測試,然後進行下一個A的測試
       4.如果A^M%N!=1,那麼令i由0迭代至R,進行下面的測試
       5.如果A^((2^i)*M)%N=N-1則通過A對於N的測試,否則進行下一個i的測試 
       6.如果i=r,且尚未通過測試,則此A對於N的測試失敗,說明N為合數。
       7.進行下一個A對N的測試,直到測試完指定個數的A
       通過驗證得知,當T為素數,並且A是平均分佈的隨機數,那麼測試有效率為1 / ( 4 ^ T )。如果T > 8那麼測試失誤的機率就會小於10^(-5),這對於一般的應用是足夠了。如果需要求的素數極大,或著要求更高的保障度,可以適當調高T的值。
bool RabbinMillerTest( unsigned n )  
{  
    if (n<2)  
    {   
         // 小於2的數即不是合數也不是素數  
        throw 0;  
    }  
    const unsigned nPrimeListSize=sizeof(g_aPrimeList)/sizeof(unsigned);//求素數表元素個數  
    for(int i=0;i<nPrimeListSize;++i)  
    {  
        // 按照素數表中的數對當前素數進行判斷  
        if (n/2+1<=g_aPrimeList[i])  
        {  
            // 如果已經小於當前素數表的數,則一定是素數  
            return true;  
        }  
        if (0==n%g_aPrimeList[i])  
        {  
            // 餘數為0則說明一定不是素數  
            return false;  
        }  
    }  
    // 找到r和m,使得n = 2^r * m + 1;  
    int r = 0, m = n - 1; // ( n - 1 ) 一定是合數  
    while ( 0 == ( m & 1 ) )  
    {  
        m >>= 1; // 右移一位  
        r++; // 統計右移的次數  
    }  
    const unsigned nTestCnt = 8; // 表示進行測試的次數  
    for ( unsigned i = 0; i < nTestCnt; ++i )  
    {   
        // 利用隨機數進行測試,  
        int a = g_aPrimeList[ rand() % nPrimeListSize ];  
        if ( 1 != Montgomery( a, m, n ) )  
        {  
            int j = 0;  
            int e = 1;  
            for ( ; j < r; ++j )  
            {  
                if ( n - 1 == Montgomery( a, m * e, n ) )   
                {  
                    break;  
                }  
                e <<= 1;  
            }  
            if (j == r)  
            {  
                return false;  
            }  
        }  
    }  
    return true;  
}  
素數篩法

原理就是標記,防止重複判斷。比如2是素數,所有是2的倍數都不是素數,然後標記上,接著判斷3,執行到根下,這樣就會得到一個素數表,所有沒有被標記的都是素數

素數篩法
以下轉載自http://blog.csdn.net/tongyongzh/article/details/6693409
#include <cstdlib>  
#include <cmath>  
using namespace std;  
const int MAX=1000;  
  
int main()  
{  
     int i=0,j=0,n=sqrt(MAX)+1;  
     int a[MAX+1]={0};  
  
     for(i=2;i<=n;i++)  //篩選迴圈  
       for(j=2;j<=MAX/i;j++)  
           a[j*i]=1;  
  
        
     for(i=2;i<=MAX;i++)  
        if(a[i]==0)  
        {  
          cout.width(7);  
          cout<<i<<" ";  
        }  
     system("pause");  
     return 0;  
       
}  

線性篩法

int a[MAX_N+1];  
int p[MAX_N+1];  
int nCount=0;  
  
void Init(int n) //線性篩法,不過在小範圍上(約n<1e7)不比上一個方法快  
{  
    for (int i=2;i<=n;i++)  
    {  
        if (a[i]==0)  
        {  
            p[++nCount]=i;  
        }  
        for (int j=1,k; (j<=nCount) && (k=i*p[j])<=n; j++) //篩選迴圈  
        {  
            a[k] = 1 ;  
            if (i%p[j] == 0) break;  
        }  
    }  
}  
  
#include <iostream>  
int main(void)  
{  
    Init(MAX_N);  
    for(int n=1; p[n]>1; ++n)  
    {  
        printf("%5d", p[n]);  
    }  
    return 0;  
}


未完待續