求素数的几种高效方法

转贴文章请注明:逸学堂
 
求解一个算法,我们首先要知道它的数学含义.依据这个原则,首先我们要知道什么是素数.; 素数是这样的整数,它除了表示为它自己和1的乘积以外,无论他表示为任何两个整数的乘积。
找素数的方法多种多样。
1:是从2开始用“是则留下,不是则去掉”的方法把所有的数列出来(一直列到你不想再往下列为止,比方说,一直列到10,000)。第一个数是2,它是一个素数,所以应当把它留下来,然后继续往下数,每隔一个数删去一个数,这样就能把所有能被2整除、因而不是素数的数都去掉。在留下的最小的数当中,排在2后面的是3,这是第二个素数,因此应该把它留下,然后从它开始往后数,每隔两个数删去一个,这样就能把所有能被3整除的数全都去掉。下一个未去掉的数是5,然后往后每隔4个数删去一个,以除去所有能被5整除的数。再下一个数是7,往后每隔6个数删去一个;再下一个数是11,往后每隔10个数删一个;再下一个是13,往后每隔12个数删一个。就这样依法做下去。
但是编程我们一般不采用上面的方法,并不说这中方法计算机实现不了,或者说实现算法比较复杂。因为它更像一个数学推理。最后我们也给一个算法。
下面我们介绍几种长用的编程方法。
       2: 遍历2以上N的平方根以下的每一个整数,是不是能整除N ;( 这是最基本的方法)
       3 :遍历2以上N的平方根以下的每一个素数,是不是能整除N ;( 这个方法是上面方法的改进,但要求 N 平方根以下的素数已全部知道 )
       4
:采用 Rabin-Miller 算法进行验算;
例如:N = ^ 127 - 1是一个38位数,要验证它是否为素数,用上面几个不同的方法:
验算结果,假设计算机能每秒钟计算1亿次除法,那么
算法 2 要用4136年,算法 3 要用93年,算法 4 只要不到1秒钟! ( 这些数据是通过计算得到 )
 
另外印度有人宣称素数测试是 P 问题,我一直没有找到那篇论文,听说里面有很多数学理论。如果那位大人有这篇论文,麻烦转发一份( E-Mail:exuetang@126.com )有任何问题也可以写信与我讨论。
 
下面我们分别实现上面的三种算法:
以下算法我们不涉及内存溢出,以及大数字的问题。如果测试数字超过2^32,发生内存溢出,你需要自己使用策略解决这个问题,在这里只讨论32位机有效数字算法。
1 //  算法0:是从2开始用“是则留下,不是则去掉”的方法把所有的数列出来
//  最后数组中不为0的数字就是要查找的素数。
void  PrimeNumber0()
{
     //   int time ::GetTickCount();
     //   cout << "start time:" << time << endl;
 
     int Max[MAX_NUMBER];        //  在栈上分配,栈上空间要求一般都在2M之间,
     //    如果你需要更大空间,请在堆上申请空间(就是通过malloc,new来申请).
     memset(Max,0,MAX_NUMBER);
     for(int i = 0 ; i < MAX_NUMBER; ++i)
     {
          Max[i] = i;
     }
     int cout = 0;//  记录当前i的位置
 
     //  遍历整个数组
     for(i = 1; i < MAX_NUMBER; ++i)
     {
         if(Max[i] != 0 )//  如果数据不为0,说明是一个素数
         {
              int iCout = i;
              int j = Max[i];//  记录数组中数组位的数字,以便设置
              while((iCout+=j) < MAX_NUMBER)
              {
                   //  把不是素数的数位在数组中置为0
                   Max[iCout] = 0;
              }
              ++cout;
         }
     }
 
     //   int time ::GetTickCount();
     //   cout << "end time:" << time << endl;
}
 
2 :这个算法可以修改成为,验证一个给定数字是否是一个素数。
//  因为我们讨论多个算法,所以我们把每个算法都单独
//  写在一个或多个函数内。这些函数并不要求输入值和返回值
//  如果你需要这些结果,可以自己修改。
 
//  算法1:遍历2以上N的平方根以下的每一个整数,是不是能整除N;
void  PrimeNumber1()
{
//   int time ::GetTickCount();
//   cout << "start time:" << time << endl;
 
     int Max[MAX_NUMBER/2];      //  在栈上分配,栈上空间要求一般都在2M之间,
//    如果你需要更大空间,请在堆上申请空间(就是通过malloc,new来申请).素数的个数很少
//  所以没有必要申请和所求数字同样大小的空间。
     memset(Max,0,MAX_NUMBER);
     Max[0] = 2;//  放入第一个素数,有人说2不是素数,如果你是其中一员,就改成3吧
     int cout = 1;//  记录素数个数
 
     //  挨个数进行验证
     bool bflag = true;
     for(int i = 3; i < MAX_NUMBER; ++i)
     {
         bflag = true;
         //  需要是使用数学库(math.h)中sqrt
         int iTemp = (int)sqrt((float)i);//  强制转换成int类型,有的人在这里使用i+1就是为了增加sqrt的精度
         //  没有特殊函数,你也可以使用int iTemp = (int)sqrt(i)+1;来提高进度
         for (int j = 2; j < iTemp; ++j)
         {
              if(i%j == 0)//  求余,如果为0说明,可以整除,不是素数。
              {
                   bflag = false;
                   break;
              }
         }
         //  经过验证是素数,放入数组。
         if(bflag)
         {
              Max[cout++] = i;
         }
     }
 
//   int time ::GetTickCount();
//   cout << "end time:" << time << endl;
 
}
 
3 :这个方法是上面方法的改进,但要求N平方根以下的素数已全部知道
//  算法2:遍历2以上N的平方根以下的每一个素数,是不是能整除N;
// ( 这个方法是上面方法的改进,但要求N平方根以下的素数已全部知道)
void  PrimeNumber2()
{
     //   int time ::GetTickCount();
     //   cout << "start time:" << time << endl;
 
     int Max[MAX_NUMBER/2];      //  在栈上分配,栈上空间要求一般都在2M之间,
     //    如果你需要更大空间,请在堆上申请空间(就是通过malloc,new来申请).素数的个数很少
     //  所以没有必要申请和所求数字同样大小的空间。
     memset(Max,0,MAX_NUMBER);
     Max[0] = 2;//  放入第一个素数,有人说2不是素数,如果你是其中一员,就改成3吧
     int cout = 1;//  记录素数个数
 
     //  挨个数进行验证
     bool bflag = true;
     for(int i = 3; i < MAX_NUMBER; ++i)
     {
         bflag = true;
         //  需要是使用数学库(math.h)中sqrt
         int iTemp = (int)sqrt((float)i);//  强制转换成int类型,有的人在这里使用i+1就是为了增加sqrt的精度
         //  没有特殊函数,你也可以使用int iTemp = (int)sqrt(i)+1;来提高进度
/
         //  修改的是这里以下的部分
         for (int j = 0; j < cout; ++j)
         {
              if(i%Max[j] == 0)//  求余,如果为0说明,可以整除,不是素数。
              {
                   bflag = false;
                   break;
              }
         }
         //  修改的是这里以上的部分
//
         //  经过验证是素数,放入数组。
         if(bflag)
         {
              Max[cout++] = i;
         }
     }
 
     //   int time ::GetTickCount();
     //   cout << "end time:" << time << endl;
 
}
 
4 采用 Rabin-Miller 算法进行验算, Rabin-Miller 算法是典型的验证一个数字是否为素数的方法。判断素数的方法是Rabin-Miller概率测试,那么他具体的流程是什么呢。假设我们要判断n是不是素数,首先我们必须保证n 是个奇数,那么我们就可以把n 表示为 n = (2^r)*s+1,注意s 也必须是一个奇数。然后我们就要选择一个随机的整数a (1<=a<=n-1),接下来我们就是要判断 a^s=1 (mod n) 或a^((2^j)*s)= -1(mod n)(0<=j<R),如果任意一式成立,我们就说n通过了测试,但是有可能不是素数也能通过测试。所以我们通常要做多次这样的测试,以确保我们得到的是一个素数。(DDS的标准是要经过50次测试)
 
//  算法3:采用Rabin-Miller算法进行验算
// 首先选择一个代测的随机数p,计算b,b是2整除p-1的次数。然后计算m,使得n=1+(2^b)m。
 
// (1) 选择一个小于p的随机数a。
// (2) 设j=0且z=a^m mod p
// (3) 如果z=1或z=p-1,那麽p通过测试,可能使素数
// (4) 如果j>0且z=1, 那麽p不是素数
// (5) 设j=j+1。如果j<b且z<>p-1,设z=z^2 mod p,然后回到(4)。如果z=p-1,那麽p通过测试,可能为素数。
// (6) 如果j=b 且z<>p-1,不是素数
 
//  判定是否存在 a^s=1 (mod n) 或a^((2^j)*s)= -1(mod n)(0<=j<R),
 
bool  Witness(int a,int n)
{
     //  解释一下数学词汇:
     // ceil 求不小于x的最小整数,函数原型extern float ceil(float x);求得i的最大值
     // log 计算x的自然对数,函数原型extern float log(float x);
     long i,d=1,x;
     for (i=(int)ceil(log((double)n-1)/log(2.0))-1;i>=0;--i)
     {
         x=d;
         d=(d*d)%n;
         if ((1==d) && (x!=1) && (x!=n-1))
         {
              return 1;
         }
         if ((n-1)&(1<0))
         {
              d=(d*a)%n;
         }
     }
     return (d!=1);
 
}
 
//  参数n,是要测定的数字,s是要内部测试的次数。
bool  Rabin_Miller(int n,int s)
{
     for (int j = 0;j < s; ++j)
     {
          int a = rand()*(n-2)/RAND_MAX + 1;//  获得一个随机数1<=a<=n-1
         if (Witness(a,n))//  利用这个随即数和n进行判断对比,只要有一次返回true,就说明n不是一个素数
         {
              return false;
         }
     }
     return true;//  通过验证是一个素数
}
 
//  算法3:采用Rabin-Miller算法进行验算
//  这个算法是求大素数使用的。所以你的必须想办法支持大数字运算,
//  不然极易造成内存访问失效,我在我的机子上,MAX_NUMBER=10000时就会出现问题,1000就没有问题
void  PrimeNumber3()
{
    
     int Max[MAX_NUMBER/2];//  在栈上分配,栈上空间要求一般都在2M之间,
     //    如果你需要更大空间,请在堆上申请空间(就是通过malloc,new来申请).素数的个数很少
     //  所以没有必要申请和所求数字同样大小的空间。
     int cout = 0;//  记录素数个数
     memset(Max,0,MAX_NUMBER/2);
 
     for(int i = 2; i < 1000; ++i)
     {
         if(Rabin_Miller(i,20))
         {
              Max[cout++] = i;
         }
     }
}
 
以上程序都经过测试,测试环境Window 2003+VC7.1
小结:以上只是简单介绍一下求素数的几种常用方法,实际方法远远不知这些。对于普通用户来说算法2就可以方便应用了。Rabin-miller算法,确实很高效,但是如果你对数学,编程知识不是很熟悉,建议不要使用。
如果使用过程中有任何疑问请与我联系。
QQ :35091551
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值