首页 > 其他分享 >素数筛法及其优化策略

素数筛法及其优化策略

时间:2022-11-18 15:13:50浏览次数:36  
标签:index square 筛法 int 素数 factor marker 优化 first

暴力算法寻找素数的效率是底下的,可以通过素数筛法来在一个自然数表中标记处素数。

Eratosthenes筛法

首先是Eratosthenes筛法,基本方法就是首先排除所有大于2的偶数,然后从3开始在奇数中寻找素数。具体操作就是选取一个素数,然后在数表中删去它的倍数。以3到50为例,寻找所有的素数过程如下

3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51

首先删去3的倍数(3除外)

3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51

然后删去5的倍数(5除外)

3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51

再然后7的倍数(7除外)

3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51

直到根号50为止,以上就是Eratosthenes筛法的过程。

程序与优化

首先发现规律,在删除3的倍数的时候,我们删去了:9、15、21、27、35、39、45,是一个9为首项6为公差的等差数列。

在删除5的倍数的时候,我们删除了:25、35,25为首项10为公差。

删除7的倍数的时候,删去了49。

由此可以发现:

每次删去某个素数p的倍数的时候,第一个删去的就是p^2,下一个就是p^2+2p、p^2+4p、...

第一个删去的是p^2而不是2*p是因为小于p^2的合数一定会被某个小于p的素数所删去,例如在删去5的倍数的时候我们从25开始,因为15已经作为3的倍数被删去了。

删去倍数的时候没有考虑p^2+p、p^2+3p、...是因为这些数是偶数,所以每次跳过一个倍数去删除。

 

为了作为数组用程序去实现,把以上所有数标出数组索引,

0 1 2 3   4   5   6   7   8   9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51

以3为例:

删去的倍数有:9、15、21、27、33、39、45

对应的索引是:3、  6、  9、12、15、18、21

由此可知,在这些数中,索引为i的数的值为:value(i) = 2*i+3,反过来已知数value,其索引为 index=(value-3)/2。

 

我们知道,在删除第i个数的倍数中,删去的第一个元素为i的平方,它的下标是:

index(value(i)) = [(2*i+3)^2-3]/2 = 2i^2+6i+3

 

删去的相邻两个元素为i^2+2i、i^2+4i、...

需要算出一个数的k倍和(k+2)倍中间相差多少个元素,就需要计算两数索引的差值,

即step=index((k+2)(2*i+3)) - index(k(i*2+3)) = 2*i+3

至此,已经可以写出筛法程序了:

def make_sieve(marker, first, last, factor):
    # 标记为False,表明这个索引的数为合数
    marker[first] = False
    while last - first > factor:
        first += factor
        marker[first] = False


# 求n以内的素数
def prime_table(n):
    if n < 3:
        return [2]
    marker = [True] * n
    last = n
    i = 0  # 第0个素数
    # 最开始从索引3开始,删去3的倍数
    index_square = factor = 3
    while index_square < n:
        if marker[i]:
            # 在数表marker中以索引index_square开始,每隔factor标记一个合数,直到last为止
            # index_square为当前迭代中的第一个合数的索引,即素数p的平方的索引
            # factor即两个合数之间的索引间隔
            make_sieve(marker, index_square, last, factor)
        i += 1
        factor = 2 * i + 3
        index_square = 2 * i * (i + 3) + 3
    res = [2]
    for ind in range((n-2) // 2):
        if marker[ind]:
            res.append((ind+1)*2+1)
    return res




if __name__ == '__main__':
    print(prime_table(101))

 

打印0-100的素数,结果为:

[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]

最后的优化

    目前仍有可以优化的空间,即factor和index_square的计算。

    首先观察两者在每次迭代中的增量。

    factor(i+1)-factor(i)=2

    index_square(i+1)-index_square(i)=(2i+3)+(2(i+1)+3)=factor(i)+factor(i+1)

    这样的规律就有了优化的空间,不必再在每一次迭代中通过表达式来进行计算了,可以直接通过增量(加法),代替表达式计算中的乘法,通过开销低的运算(加法)等效替代开销高的运算(乘法)。

把:

        i += 1
        factor = 2 * i + 3
        index_square = 2 * i * (i + 3) + 3

修改为:

        i += 1
        index_square += factor
        factor += 2
        index_square += factor

完整代码:

def make_sieve(marker, first, last, factor):
    # 标记为False,表明这个索引的数为合数
    marker[first] = False
    while last - first > factor:
        first += factor
        marker[first] = False


# 求n以内的素数
def prime_table(n):
    if n < 3:
        return [2]
    marker = [True] * n
    last = n
    i = 0  # 第0个素数
    # 最开始从索引3开始,删去3的倍数
    index_square = factor = 3
    while index_square < n:
        if marker[i]:
            # 在数表marker中以索引index_square开始,每隔factor标记一个合数,直到last为止
            # index_square为当前迭代中的第一个合数的索引,即素数p的平方的索引
            # factor即两个合数之间的索引间隔
            make_sieve(marker, index_square, last, factor)
        i += 1
        index_square += factor
        factor += 2
        index_square += factor
    res = [2]
    for ind in range((n-2) // 2):
        if marker[ind]:
            res.append((ind+1)*2+1)
    return res




if __name__ == '__main__':
    print(prime_table(101))

 

打印0-100的素数,结果为:

[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]

C++程序如下:

#include <iostream>
using namespace std;

void make_sieve(bool* marker, int first, int last, int factor) {
    marker[first] = false;
    while (last - first > factor) {
        first += factor;
        marker[first] = false;
    }
}

int* prime_sieve(int n) {
    if (n < 3)return new int[1] {2};
    bool *marker = new bool[n];
    std::fill(marker, marker + n, true);
    int last = n;
    int i = 0;
    int index_square = 3;
    int factor = 3;
    while (index_square < n) {
        if (marker[i]) {
            make_sieve(marker, index_square, last, factor);
        }
        ++i;
        index_square += factor;
        factor += 2;
        index_square += factor;
    }
    int count = 1;
    for (int j = 0; j < ((n - 2) >> 1); j++) {
        if (marker[j]) {
            count++;
        }
    }
    int ind = 0;
    int *res = new int[count];
    res[ind++] = 2;
    for (int j = 0; j < ((n - 2) >> 1); j++) {
        if (marker[j]) {
            res[ind++] = (j + 1) * 2 + 1;
        }
    }
    return res;
}

int main(){
    int *prime_table = prime_sieve(1000000);
    for (int j = 0; j < 120784; j++) {
        cout << prime_table[j]<<" ";
    }
    if (prime_sieve) {
        delete[]prime_table;
    }
}

 

Java程序如下:

private static void makeSieve(boolean[] marker, int first, int last, int factor) {
    marker[first] = false;
    while (last - first > factor) {
        first = first + factor;
        marker[first] = false;
    }
}

public static int[] sift(int n) {
    if (n<3) {
        return new int[] {2};
    }
    boolean[] marker = new boolean[n];
    Arrays.fill(marker, true);
    int last = marker.length;
    int i = 0;
    int indexSquare = 3;
    int factor = 3;
    while (indexSquare < n) {
        if (marker[i]) {
            makeSieve(marker, indexSquare, last, factor);
        }
        ++i;
        indexSquare += factor;
        factor += 2;
        indexSquare += factor;
    }
    int count = 1;
    for (int j = 0; j < ((marker.length-2)>>1); j++) {
        if (marker[j]) {
            count++;
        }
    }
    int ind = 0;
    int[] res = new int[count];
    res[ind++] = 2;
    for (int j = 0; j < ((marker.length-2)>>1); j++) {
        if (marker[j]) {
            res[ind++] = (j+1)*2+1;
        }
    }
    return res;
}
public static void main(String[] args) {
    System.out.println(Arrays.toString(sift(100)));
}

 

标签:index,square,筛法,int,素数,factor,marker,优化,first
From: https://www.cnblogs.com/zhaoke271828/p/16903265.html

相关文章

  • 63:循环代码优化技巧(极其重要)
    ###循环代码优化虽然计算机越来越快,空间也越来越大,我们仍然要在性能问题上“斤斤计较”。编写循环时,遵守下面三个原则可以大大提高运行效率,避免不必要的低效计算:1.尽量......
  • 运筹优化在地服人员派工系统中的应用简介
    运筹优化在地服人员派工系统中的应用简介——以HCC项目为例人员派工是运筹优化的一个常见的应用领域,本文结合目前正在进行的项目对这方面的内容进行一个简单的介绍。概......
  • 详解webpack构建优化
    当项目越来越复杂时,会面临着构建速度慢和构建出来的文件体积大的问题。webapck构建优化对于大项目是必须要考虑的一件事,下面我们就从速度和体积两方面来探讨构建优化的策略......
  • 解读Vue3模板编译优化
    今天的文章打算学习下Vue3下的模板编译与Vue2下的差异,以及VDOM下Diff算法的优化。编译入口了解过Vue3的同学肯定知道Vue3引入了新的组合Api,在组件mount阶......
  • React性能优化的8种方式
    一引沿Fiber架构是React16中引入的新概念,目的就是解决大型React应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是R......
  • nginx高并发优化之缓冲配置
    一、配置http{proxy_buffer_size256k;proxy_buffers4256k;proxy_busy_buffers_size256k;proxy_temp_file_write_size256k;proxy_max_temp_file_size128m;......
  • 数学规划优化系统 工具选型
    目录GurobiCBCCPLEXSCIP成套优化工具XPRESSMATLABSASGurobihttp://www.gurobi.cn/about.asp?id=1Gurobi是全局优化器,支持的模型类型包括:(1)连续和混合整数线性问题(2)凸......
  • SqlServer 优化的技巧
    1、避免使用select*select*不会走覆盖索引,会出现大量的回表操作,从而导致SQL的查询性能很低    2、用unionall代替union1、使用union后,可以获取排重......
  • nginx高并发优化之upstream模块设置
    一、配置http{upstreamhttp_backend{hash$remote_addrconsistent;server192.168.10.131:3306max_fails=2fail_timeout=10sweight=1;server192.168......
  • 【SSL 1590】旅游(线段树优化DP)
    旅游题目链接:SSL1590题目大意要从x号点依次按编号走到y号点,每次可以选择跳最多z个点,即从i到i+z。每到一个点都要支付a的费用,到一些给出的特定点有其对应的......