有趣的素数(附C++源码)

前言

素数问题可能是数论中最引人入胜的课题了。最著名的莫如“哥德巴赫猜想”。几乎小学生都可以明白它的意思。但是穷尽世界上最聪明的脑袋,迄今为止都无人能够证明它,也无法举出反例。本文试图运用计算机去探索一些有趣的素数问题。例如,计算指定整数 n n n 以内的素数、素因子分解、在 n n n 以内验证“哥德巴赫猜想”等等。这里, n n n 可以很大,例如几千万到几十亿。当然,这跟无穷比起来,又是非常渺小了。本文并不涉及大整数的内容,而仅仅是普通的PC机和普通的算法及语言能够处理的数据范围。只是作一些有趣的探索罢了。权当娱乐。本文讨论到的问题,都用C++实现,并附上所有源码,供大家探讨。

筛法求 n 以内的素数

筛法即埃拉托斯特尼( E r a t o s t h e n e s Eratosthenes Eratosthenes,即计算出地球周长的那位古希腊数学家)筛法。方法如下:

  1. 将自然数从小到大顺序排列。
  2. 1不是素数,划去。
  3. 2是素数,保留。划去所有2的倍数。
  4. 3是素数,保留。划去所有3的倍数。
  5. 循环往复,直到指定的整数 n n n

这是最古老的方法,也是经典的方法。效率不算高,但胜在简单易懂。编程的时候,稍微作了改动。算法如下:

  1. 清空素数数组primeNums。
  2. i = 2 i=2 i=2 开始循环执行,直到 n n n
  3. 如果 i i i 在素数数组primeNums中找到素因子,说明 i i i 不是素数, i i i 自增1,执行下一步循环。
  4. 如果遍历素数数组primeNums都找不到素因子,说明 i i i 是新的素数,将 i i i 加入素数数组primeNums中。 i i i 自增1,执行下一步循环。
  5. 结束。这时候,素数数组primeNums中就保存了不大于n的所有素数。

应该说明的是,本文的算法和埃氏算法还是有所不同的。埃氏的算法是在一个已有的自然数表格中不断划去已发现素数的倍数,最后剩下的就是不大于n的素数表。这样的结果造成素数表中有许多空间是浪费的。而本文的算法是,从2开始,从小到大不断将新发现的素数加入素数表中,最后生成的素数表存储的都是素数,没有浪费空间。第二是算法的时间复杂度上,埃氏算法会有许多重复计算,例如,2是素数,划去2的倍数,6就给划去了;当运行到3时,6也是3的倍数,又给划去一次,产生重复计算。而本文的算法,运行到6时,搜索素数表,发现6是2的倍数,说明6是合数,就不再往下搜索了。不产生重复计算。

观察以上算法,可以发现,其实2之后的偶数肯定不是素数,可以直接跳过。将算法作一个小小的改进,从3开始搜索,每次加2。提高一下效率。如下:

  1. 清空素数数组primeNums。
  2. 将2加入素数数组primeNums。
  3. i = 3 i=3 i=3 开始循环执行,直到 n n n
  4. 如果 i i i 在素数数组primeNums中找到素因子,说明 i i i 不是素数, i i i 自增2,执行下一步循环。
  5. 如果遍历素数数组primeNums都找不到素因子,说明 i i i 是新的素数,将 i i i 加入素数数组primeNums中。 i i i 自增2,执行下一步循环。
  6. 结束。这时候,素数数组primeNums中就保存了不大于 n n n 的所有素数。

算法的时间复杂度为 O ( m n ) O(mn) O(mn),空间复杂度为 O ( m ) O(m) O(m)。这里 m m m n n n 以内素数的数量。当 n n n很大时, m ≈ log ⁡ n m\approx{\log{n}} mlogn。所以,算法的时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),空间复杂度为 O ( log ⁡ n ) O(\log{n}) O(logn)

几个有趣的想法

现在,我们有了 n n n 以内所有的素数。看着这些素数,不禁萌生几个有趣的想法。

  1. 素数之间的间隔。有没有最大间隔?即间隔是无限大还是有限的。
  2. 利用这个素数表作素因子分解。
  3. n n n 之内验证“哥德巴赫猜想“。

在程序中,做了几个小实验,分别探讨以上问题。详细参见主程序。许多问题或者猜想,都可以通过所得的素数表,编程予以验证。

程序说明

程序分三部分。第一是素数类的定义,放在头文件:SievePrime.h中;第二是类的实现,放在文件:SievePrime.cpp中;第三是主程序,主要是调用类的各个方法,输出实验结果。放在文件main.cpp中。
程序的核心是素数类的定义及其实现。素数类中,定义了字段 n u m num num 即对应于 n n n;容器 p r i m e N u m s primeNums primeNums 用于存储不大于 n u m num num 的素数;方法 g e n P r i m e N u m s ( ) genPrimeNums() genPrimeNums() 用筛法生成不大于 n u m num num 的素数存储于容器 p r i m e N u m s primeNums primeNums 中;其它接口的方法和函数,定义了参数 n u m num num 的读取和设置、素数的输出、素数的判断、输出素因式分解、输出“哥德巴赫”分解式等等。详细参见源代码。

程序全部在 W i n d o w s 10 + C o d e B l o c k s + M i n G W Windows 10+CodeBlocks+MinGW Windows10+CodeBlocks+MinGW环境下,编译运行。由于这是与平台无关的,可以很容易移植到 L i n u x Linux Linux等其它操作系统。

下面是源代码,给大家分享:

头文件SievePrime.h

#ifndef SIEVEPRIME_H
#define SIEVEPRIME_H
#include <vector>
class SievePrime
{
   
public:
        SievePrime( unsigned n ) : num( n ) {
    genPrimeNums(); }
        virtual ~SievePrime() {
    std::vector<unsigned>().swap(primeNums); }
        unsigned getNum() {
    return num; }
        void setNum( unsigned n ) {
    num=n; genPrimeNums(); return; }
        void printPrimeNums( unsigned lineSize ); //print prime numbers to screen
        int isPrimeNumber(unsigned n); //判断参数n是不是num以内的素数。返回0:不是素数;1:是素数;-1:超范围。
        int printGoldbachNums(unsigned n); //对给定参数n,作哥德巴赫分解--分解为两个素数之和。返回1:分解成功;返回0:分解不成功;返回-1:超范围。
        void printFactors(unsigned n); //对给定参数n,利用素数表作因式分解。
        unsigned maxGap(); //计算素数表中相邻素数的最大间隔
 private:
        unsigned num;
        std::vector<unsigned> primeNums;
        void genPrimeNums();
        
 };
 #endif // SIVEVPRIME_H

类的实现,SievePrime.cpp:


#include <iostream>
#include "SievePrime.h"

void SievePrime::genPrimeNums()  //生成num以内的素数,加入素数组。
{
   
    bool isPrimeNum=true;

    std::vector<unsigned>().swap(primeNums); //清空

    primeNums.push_back(2); // 首先加入2
    for (unsigned n=3; n<=num; n+=2) {
    //从3开始搜索,步长为2,绕开所有偶数。
   
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值