素数查找 && 二分查找
背景
今天在刷洛谷的题的时候,遇到了 质数口袋 这道题,作为刷题人的第一直觉肯定就是去想对应的算法(狭义的算法),但是我并没有想到什么合适的算法,看了一下旁栏的算法标签,竟然空空如也。然后我就开始了正常解题(暴力枚举 和 模拟)
判断素数(一般思路)
废话不多说,先上代码 (注:本人不是很喜欢 C/C++ 里面的 Bool 类型)
int isprime(int n) {
if (n <= 1) { //对 1 和 2 这种情况进行单独判断
return 0;
} else if (n == 2) {
return 1;
} else {
int i = 0;
for (i = 2; i < n; ++i) {
if (n % i == 0) {
break;
}
}
if (i == n ) { //若 i == n 则证明循环是正常结束的,而不是因为break跳出的
return 1;
} else {
return 0;
}
}
}
-
当然 for循环也可以这样写
for(i=2; i<sqrt(n)+1; ++i)
这样优化,可以大大减少循环的次数,但是需要一定的数学知识
判断素数(埃氏筛选法)
导入
-
要得到自然数n以内的全部素数,必须把不大于 根号n 的所有素数的倍数剔除,剩下的就是素数。
-
在对普通方法的 for 循环进行优化的时候,就已经提到了 根号n ,在上面的循环中,它的作用主要是为了减少循环的次数
/* 解释一些为什么是 根号n (算是数学问题,可以不看,只记住结论) --------------------------------------------------------------------------------------------- 在这里就不去说公式的推导了(个人不喜欢推导的公式) 如果一个数可以看成两个数乘积(先不论是不是素数),那么这两个 **乘数** 肯定是一大一小 (极限情况是相等) 所以说,其中较小的一个乘数的取值范围为 (1 ~ sqrt(n)) 所有我们需要排查, n 能否被其中较小的积整除 即可 --------------------------------------------------------------------------------------------- 我感觉我已经用了非常通俗的语言来讲述这个问题了,希望对你能有所帮助 */
埃氏筛选法的代码实现
int prime[0X7ffffff] = {0}; //返回从 1 到 n 的所有的素数 int *isprime(int n) { int num = 0; //用来记录素数的个数 //flag[i] 代表的含义是:i 是否是素数 int *flag = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0 ; i < n; ++i) { flag[i] = 1; //初始化所有数字为素数 } for (int i = 2; i <= sqrt(n); ++i) { if (flag[i] == 1) { for (int j = i * i; j <= n; j += i) { flag[j] = 0; //将素数的倍数全部叉掉 } } } for (int i = 2; i < n; ++i) { if (flag[i] == 1) { prime[num] = i; num++; } } return prime; }
其实这两种解法的思想是不一样的,埃氏筛选法不存在判断,而是通过排除,而第一种则是一个数一个数去判断(无论是否优化)
二分查找解素数
话说,我是怎样想到用二分查找的呢?根据二分查找的特点
- 二分查找适合用在对增删 要求不大的 有序表 中
- 从 0 到 n 还不叫有序表吗(手动狗头)
- 第一次直接模拟的时候 TML 了,也就是说时间太长了,而二分查找的时间复杂度是 logn, 接近 n
所有就很顺其自然的想到了二分查找。
一个二分查找的模板
可能你还没有了解过 二分查找 ,所有我们先用一个经典的二分查找的模板来引入这个概念
//如果目标元素存在,则返回其的下标,如果不存在返回-1; int search(int num[], int target) { int left = 0; int right = sizeof(num) / sizeof(num[0]) - 1; //left 和 rigiht 代表的是下标 while (left <= right) { //当情况允许存在时(或说合理时) int min = left + (right - left) / 2; //防止左右边界相加会直接爆掉 if (num[mid] == target) { return mid; } else if (num[mid] < target) { left = mid + 1; } else if (num[mid] > target) { right = mid - 1; } } return -1; }
二分查找本身也并不难,我觉得通过上面这个小例子,就算是没有了解过二分查找的也应该对其有一个大概的了解了
至于,有些地方我没加注释,你却又没有看明白的,建议多看几遍:(下面的话仅代表个人的观点,不喜勿喷)
- 首先是可以锻炼你对知识的理解能力(或说你学习知识的能力)
- 其实我认为,完成一件较难的事情比完成简单的事情更有成就感;
- 再者,我如果每一行都加注释,该不明白的也不一定明白(毕竟每个人理解问题的角度不一样),还会破坏我代码整体的美观度
- 最后,我这有一个听来的道理 “ 我认为,一个真正好的老师讲课时,不应该把所有的东西都讲完,要留三分让学生自己去悟,学生学的似懂非懂就会去自己思考,然后某一天,突然恍然大悟,原来是这么回事啊!如果一个老师能带动学生的思考,那么我觉得这个老师就算是一个好老师
- 当然,我没有认为我是一个老师,但我是以 及其负责任的态度 来写这篇题解的。
二分查找找素数的思路
- 第一步,打表()
- 第二步,调用二分查找,主函数一个循环进行查找目标数是否在表内。
至于打表的代码我就不再赘述了,我其实不太喜欢打表,之所以讲这个方法,只是为了引出 二分查找
一些数学味很大的代码
辗转相除
int gcd(int a, int b) { int t; while (b != 0) { t = a % b; a = b; b = t; } return a; }
证明过程太数学了,放在这篇文章里不太合适,如果你感兴趣的话,可以移步哔哩哔哩辗转相除的数学解释
回文数判断模板
- 你可能也想知到判断回文数的函数怎么写
int ispalindrome(int num) { int temp=num,ans=0; while (temp!=0) { ans=ans*10+temp%10; temp/=10; } if (ans==num) return 1; else return 0; }
至此,这篇题解就算是写完了。(完结撒花)
- 因为是第一次写题解,我总共花费了 165 分钟(其实不止)
- 浅浅谈一下自己对写题解的看法吧
- 写题解是一件利人利己的好事,写题解不仅可以帮你自己深入理解掌握知识点,还可以帮助别人。感谢HAL_20大佬的带动作用
- 写一篇题解很简单,但是写一篇好的题解并不容易,我昨天晚上已经规划了这篇文章的大致内容,但是仍然用了近三个小时的时间(也可能是我菜,或者表达能力不强)
- 我认为并不是说只有难题适合写题解,只要你写的有营养就好。(不知道我写的怎么样,希望大家在下面给我评论指正一下啊,我必定虚心接受所有批评,并且逐步改正)
- 以后没有什么事情的话打算三天或者两天一篇 高质量 (高质量并不是说题解的质量,而是我投入的程度)题解
最后,你看我都这么努力了,你不打算点个赞吗!