arr代表数组首地址,type arr[n]将在内存中开辟一块连续的nsizeof(type)存储空间,arr + 1代表在原来地址基础上偏移1个type类型地址大小,32位sys中就是32位,同理64位。
在函数内部建数组,是在栈区,不赋初始值可能会有奇怪的数字;而在函数外,则变量默认在堆区开辟空间,自动进行初始化操作【都为0】。栈区默认8MB【约可存储2百万个整形变量,8102410248/32 ~=2097152】,大于200万的变量数最好开在堆区。
函数与数组有一定类似性,数组也可以理解为下标与值的一个映射关系,
素数筛
核心思想:用素数去枚举/标记合数。
优化1
由于一个合数,肯定是由n * m合成,其中质数n < m,对于这种数,肯定在之前n的倍数标记中被标记过了,如6=2 * 3,那么在3的倍数中,就可以从3 * 3开始表示,不去理睬比当前质数更小的乘数,因为肯定乘过了。
优化2
素数筛中prime[0]无用,可以用来记录素数个数,素数标记从prime[2]开始,逐个检测标记是否为0,然后第一个素数的值放在prime[1] = 2,第二次检测3位素数,prime[2] = 3,第三次检测5为素数,prime[3] = 5.。。。因此素数值变化越来越快,但素数个数增长较慢。程序可以记为:
prime[++prime[0]] = i。
素数筛引申问题
- 求每个数字的最大素因子
- **最小素因子
- 每个合数被标记了几次
线性筛
每一个数的最小素数是惟一的,因此最小素数 * 另一因数的乘数对是可以被唯一确定的,比如30 = 15 * 2, 45 = 15 * 3,但225 = 15 * 5就不对,而必须是225 = 75 * 3,这是因为15 = 5 * 3,因此以15为因数的数字,其最小素因数必须<=3。利用这个唯一的表达式,可以保证每个合数都只被标记了一次。
程序的大致思路为:
表达式:N = M * p
枚举M,并对任意小于等于(M的最小素因数p),进行M * p,标记这个数字为合数。其中M线性增长,合数的筛查快于M,而M一定大于当前检测出的所有p。
相比于素数筛,i^2仅仅作为筛子的起始元素:
2 * 2 = 4, 2 * 3 = 6, 2 * 4 = 8, 2 * 5 = 10, 2 * 6 = 12…N_MAX
3 * 3 = 9, 3 * 4 = 12, 3 * 5 = 15, 3 * 6 = 18…N_MAX
线性筛中,i则作为最小素因数的另一乘数M:
2 * 2 = 4
3 * 2 = 6, 3 * 3 = 9
4 * 2 = 8
5 * 2 = 10,5 * 3 = 15, 5 * 5 = 25
6 * 2 = 12
…
素数筛时间复杂度为O(N * loglogN), 线性筛为O(N)
Code
线性筛
#include<stdio.h>
#define MAX_N 100
//求100以内素数
int prime[MAX_N + 5];//约定0位素数,1为合数
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;
}
素数筛
#include<stdio.h>
#define MAX_N 100
//求100以内素数
int prime[MAX_N + 5];//约定0位素数,1为合数
void init(){
for (int i = 2; i <= MAX_N; i++){
if (prime[i])
continue;
prime[++prime[0]] = i;//记录素数及素数个数
for (int j = i * i; j <= MAX_N; j+=i){
prime[j] = 1;
}
}
return ;
}
int main(){
init();
for (int i = 1; i <= prime[0]; i ++){
printf("%d\n", prime[i]);
}
return 0;
}