【跟着英雄学算法第⑧讲】素数筛选——枚举法+埃氏筛法+欧拉筛法(C语言实现)

✨前言✨

       在这个系列中,博主准备分享每日在万人千题社区打卡学习的算法。博主也是小白,因此也很能理解新手在刷题时的困惑,所以关注博主,每天学习一道算法吧。同时也欢迎大家加入万人千题习活动,正所谓:一个人可以走的很快,但一群人才能走的更远。

万人千题社区https://bbs.csdn.net/forums/hero?category=0&typeId=17913https://bbs.csdn.net/forums/hero?category=0&typeId=17913


目录

✨前言✨

一、算法思想笔记

①枚举法:

②埃氏筛法:

③欧拉筛法      

二、计算质数

①题目呈现

三、解决方法 

①枚举法

​②埃氏筛法

③欧拉筛法


一、算法思想笔记

①枚举法:

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只被筛一次。

(代码在题目中呈现)

二、计算质数

204. 计数质数https://leetcode-cn.com/problems/count-primes/https://leetcode-cn.com/problems/count-primes/

 ①题目呈现

三、解决方法 

 ①枚举法

显然这里用枚举法会超出时间限制,不是一个好办法,那我们也试一试吧

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而停止

运行结果 :

看来这道题目不适合用欧拉筛法🤣

  • 66
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罅隙`

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值