默认计划
491人加入学习
(0人评价)
【研发工程师】C语言程序设计
价格 ¥ 970.00
该课程属于 名企研发核心能力课 请加入后再学习

线性筛:一套框架算法

EP07题:找第10001个素数

估算:开10000 * 20大小的数组,筛选。

 

素数筛O(loglogN)速度还慢,线性筛O(N)更快

#include <stdio.h>
#define MAX_N 200000

void is_prime(int *arr) {
    for (int i = 2; i < MAX_N; i++) {
        if (arr[i]) continue;
        arr[++arr[0]] = i;
        for (int j = i; j < MAX_N / i; j++) {
            arr[j * i] = 1;
        }
    }
    return ;
}

int main() {
    int arr[MAX_N] = {0};
    is_prime(arr);
    printf("%d\n", arr[10001]);
    return 0;
}

 

如果一个数字N的素数因式分解中有m种不同的素数,N被标记了m次

而线性筛只被标记1次。速度更快。

 

标记->数组

线性筛:空间复杂度时间复杂度都为 O(N)

作用:筛选一定范围内所有的素数。

核心思想:用整数 i * prime[j] (小于 i 最小素因数的素数表)去 数组 合数 prime[j] * i。(PS: 素数筛的 i 是素数,这里的 i 是素数+合数)


其中 prime[j] * ii 有如下性质:

  1. prime[j] * i 中最小的素数为 prime[j]
  2. prime[j] * i 可以表示成为 prime[j] * i
  3. prime[j] 一定 <= i 中最小的素因子
  4. 利用 i * prime[j](所有不大于 i 中最小素数的集合,如 2,3,5... <= i 的最小素数)数组 prime[j] * i

判断 i 的最小素数的方法:如果 i % prime[j] == 0,那么 prime[j] 即为 i 的最小素数。


推导:

由 ①② 得:
prime[j] * i = prime[j] * i
i 是除了 prime[j] * i 以外的最大因数。

③ 保证了 i 是最大因数
prime[j] * i = 30:

  • prime[j] * i = prime[j] (2) * i (15)
  • 其中 i 的最小素因数为 3 (3*5 = 15)

如果 prime[j] = 3,则 i = 10,此时 i 的最小素因数为 2,不满足条件 ③

④ 表示数组,i 从 2 开始,prime[j] 依次取值 2,3,5... 依次数组 prime[j] * i

例如 数组 prime[j] * i = 30

  • 30 的因子是 2,3,5,6,10,15。
  • i = 15 时,才会数组 prime[j] * i = 30

随堂练习

被数组的 prime[j] * i = i * prime[j]

prime[j] * i i prime[j]
30 15 2
8 4 2
45 15 3

i 对应可数组的 prime[j] * i

  • i=4,可以数组的 prime[j] * i:4×2 = 8
  • i=25,可以数组的 prime[j] * i:25×2, 25×3, 25×5
  • i=45,可以数组的 prime[j] * i:45×2, 45×3
  • 能数组 90 的 i 等于:45

 

#include <stdio.h>
#define MAX_N 100


// 素数0 合数1 
// 被标记的N = 标记M * P(素数集合) 
void linnear_init(int *arr) {
	for (int i = 2; i <= MAX_N; i++) {
		if (!arr[i]) arr[++arr[0]] = i;
		for (int j = 1; j <= arr[0] && arr[j] * i <= MAX_N; j++) {
			arr[arr[j] * i] = 1;
			if (!(i % arr[j])) break;
		}
	}
}

void su_prime(int *arr) {
    for (int i = 2; i < MAX_N; i++) {
        if (!arr[i]) arr[++arr[0]] = i;
        for (int j = i; j <= MAX_N / i; j++) {
            arr[j * i] = 1;
        }
    }
    return ;
}

int main() {
    int arr[MAX_N] = {0};
//    su_prime(arr);
	xian_init(arr); 
    for (int i = 1; i <= arr[0]; i++) {
    	printf("%d\n", arr[i]);
	}
    return 0;
}

 

比线性筛更快的判断素数方式:

罗宾米勒测试,有误差(很低),需要数论的知识。

 

-----------------------------------------------

线性筛提升为算法框架(重要)

求100以内所有数字的因素的个数

 

F[MAX_N]记录因素个数

先写一个线性筛。 然后改

 

素数的因子个数肯定为2(1和本身)

F[M] = 2;

 

M,P互素的话,F(M * P)= F(M) * F(P)

if 互素   F[M * arr[P]] = F[M] * F[arr[P]]

 

else 不互素(M % arr[p] == 0)

任何的N都可以写成  素因子次幂 连乘 Pi ^ ai

cnt[MAX_N]记录次幂

F[M * arr[P]] = F[M] / (cnt[M] +1) * (cnt[M] + 2)

cnt[M * arr[P]] = cnt[M] +1

 

 

作业:求解因子和问题

 

 

[展开全文]

线性筛:

  求第几个素数可以简单的扩大20倍;

   如果一个数字N的素数分解式中含有m种不同的素数,N被标记了几次?

   要求范围内的数字仅被标记1次;

   总体思想是用一个整数M去标记合数N,其中N和M具有如下性质:1)N中最小的素数为p;  2)N可以表示为p * M;3)p一定小于等于M中最小的素因子;4)利用M * p‘ (所有不大于M中最小素数的集合)标记N1、N2、N3、...............

   N\, =\, {{p}_{1}}^{a}\, *\, {{p}_{2}}^{a}

1).2) N = p * M => M是最大的因子;  3)M最大的因子 

#include<stdio.h>

#define MAX_N 100

int prime[MAX_N + 5] = {0};
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (!prime[i]) prime[++prime[0]] = i;
        for (int j = 1; j <= prime[0]; j++) {
            if (prime[j] * i > MAX_N) break;
            prime[prime[j] * i] = 1;
            if (i % prime[j] == 0) break;
        }
    }
    return;
}
int main() {
    init();
    for (int i = 1; i <= prime[0]; i++) {
        printf("%d\n", prime[i]);
    }

    return 0;
}

罗宾米勒测试

 12 = 2^2 *3^1  =>    (1 +2) * (1 + 1) = 6 z中因子组合。

F(a * b) = F (a) *F (b) (a, b) 互素 )  F(a) 表示a的因子个数

求解范围内数字因子个数问题(3:01:00 代码)

 

[展开全文]
张三厢 · 2022-10-17 · 线性筛 0

授课教师

C++算法工程师

课程特色

视频(31)