✨前言✨
在这个系列中,博主准备分享每日在万人千题社区打卡学习的算法。博主也是小白,因此也很能理解新手在刷题时的困惑,所以关注博主,每天学习一道算法吧。同时也欢迎大家加入万人千题习活动,正所谓:一个人可以走的很快,但一群人才能走的更远。
目录
一、算法思想笔记
①枚举法:
1.思想:一一枚举从2~n-1之间可能被n整除的数
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n ; i++)
{
return 0;//能被2~n之间的数整除说明是合数,所以直接返回0
}
return 1;
}
优化一:排除所有除2外的偶数
int is_prime(int n)
{
int i = 2;
if (n % 2 == 0)
return 0;
for (i = 3; i < n ; i+=2)
{
return 0;
}
return 1;
}
优化二:枚举到 i*i<=n时即可终止遍历 (时间复杂度:O(√n))
解释:设A=B*C(2<=B<C<A),所以只要说明B的存在就可以说明A必然是合数。而B最大值 为根号A,所以只要枚举到i*i<=A即可判断是否为质数。
int is_prime(int n)
{
int i = 2;
for (i = 2; i * i <= n; i++)
{
if (n % 2 == 0)
return 0;
}
return 1;
}
【注意】如果写成 i<=(int)sqrt(n),要考虑到浮点数的误差 ,应该写成 i<=(int)(sqrt(n)+1E-6),或者可以写成i<=(int)floor(sqrt(n)+0.5)
②埃氏筛法:
时间复杂度:
1.思想:
总而言之,就是第一次筛去2所有的倍数, 第二次筛去3的倍数,第三次筛去5的倍数以此类推。
(代码在题目中呈现)
③欧拉筛法
时间复杂度:O(n)
1.背景:
埃氏筛存在对一个数重复筛的缺陷,比如30=2*15=5*6,这样30既可以被2筛,又会被5筛,造成了不必要的浪费。而欧拉筛的出现解决了这个问题。
2.数学原理
不言而喻,每一个数的最小因数肯定是确定的。假设A=B*C,如果我们确定了A的最小的因数B,只要C不同,那么得到的A必然不同。换句话说,我们以这种方式可以保证A只被筛一次。
(代码在题目中呈现)
二、计算质数
①题目呈现
三、解决方法
①枚举法
显然这里用枚举法会超出时间限制,不是一个好办法,那我们也试一试吧
int is_prime(int n)
{
int i = 0;
for (i = 2; i * i <= n; i++)
{
if (n % i == 0)
return 0;
}
return 1;
}
int countPrimes(int n)
{
int cnt = 0;
if( n > 2)
cnt++;
for (int i = 3; i < n; i+=2)
{
if (is_prime(i))
cnt++;
}
return cnt;
}
运行结果:
②埃氏筛法
int countPrimes(int n)
{
if(n<3)
return 0;
int i;
int cnt = 0;
long long j;
bool f[n];
memset(f, 0, sizeof(bool) * n);
f[0] = 1;f[1] = 1;
for (i = 2; i < n; ++i)
{
if (!f[i])
{
++cnt;
for (j = (long long)i * i; j < n; j += i)
{
f[j] = 1;
}
}
}
return cnt;
}
1.算法技巧
每次 j 从 i * i 开始,为什么呢?我们可以试想 2*i 3*i ……(i-1)*i 分别被2 ,3……i-1筛过,所以没有必要再次筛。
2.核心算法思想:
①创建一个布尔类型的数组,f[n-1]存放着数字n-1是否为质数的真假值,以此类推,就是我们之前学过的桶排序 没看过?点这里
②若i为质数,内循环的j则用来筛去所有i的倍数,将他们的f[ ]值标记为1,表示不是质数
③在判断质数得同时cnt++,实现计数
3.本段代码注意点
①若n<3,则n最大为2,肯定不存在质数。并且这里不排除n<3的情况,之后f[0],f[1]的赋值会出现数组下标越界的情况。
②若编译器不支持bool类型,则可用int代替,只不过占用内存变多
③为什么j是long long类型呢?因为i*i的过程中可能会出现“溢出”的情况,若溢出则j会变成负数,所以我么要避免这种情况发生,所以要类型强转
运行结果
③欧拉筛法
int countPrimes(int n)
{
bool flag[n+1];
int cnt = 0;
int prime[n+1];
memset(flag, 0, sizeof(flag));
for (int i = 2; i < n; i++)
{
if (!flag[i])
{
prime[++cnt] = i;//prime数组记录素数
}
for (int j = 1; i * prime[j] < n; j++)//筛去合数
{
flag[i * prime[j]] = 1;
if (i % prime[j] == 0)//精髓
break;
}
}
return cnt;
}
1.核心算法
①首先我们要创建两个数组。用flag数组标记每个对应下标的数是否为质数,为0则为质数,为1则为合数。用prime数组记录遍历过程中找到的质数
②在筛去合数过程中,用j来遍历prime数组。此时prime数组里的质数相当于A=B*C模型中的最小因数B,而i则相当于是C。不过每次循环是先确定C,再由B的改变来筛去A的
③为什么 i % prime[j] == 0 时要break?原因是为了避免重复筛,这是这段代码的精髓。怎么理解呢?上面我们也提到B需要是最小因数,以这个逻辑我们可以筛掉所有的数而不重复。
如果 i % prime[j] == 0,则不能保证prime[j]是此时的最小因数,还可能是 i / prime[j] 。例如i==6,prime[j] == 3 的时候。那怎么直观点的理解呢? i % prime[j] != 0则说明了二者之间的“唯一性”,那么这样进行遍历时就不会出现重复。那为什么要break呢?如果此时继续运算,那么 i * prime[j+1] = prime[j] * k * prime[j+1],那么么显然prime[j+1]不会是最小质数。
2.本段代码注意点
①flag和prime数组都是通过变长数组的方式实现的,他是C99才有的标准,在你的编译器下可能并不支持,这样的话也不妨开辟一个很大的数组
②flag和prime数组长度为n+1,是为了防止n=0的时候创建出长度为0的数组
③内部for循环不用担心j会大于cnt。因为这个数如果是质数,那%自己 == 0;如果是合数,那么因子中肯定有之前收集到的质数,所以一定也会因break而停止
运行结果 :
看来这道题目不适合用欧拉筛法🤣