我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情
前言
前两天有个小兄弟问我一道数据结构简单的算法题怎么做,题目如下:
一看到题目,心里想:这不就是简单的求素数(除了1和它本身,其他数均无法整除的数)
题目吗,于是我很快就给他写了出来,后面发现才发现没那么简单。。。
穷举解法
看到题目后,这不就是直接暴力破解就好了吗,于是我很快就写了出来了~
# include<stdio.h>
# include<math.h>
// 求素数
int primeNumber(long int num) {
int result = 0;
int isInsert = 1;
if (num > 10000000) {
return 0;
}
for(long int i = 2; i <= num; i++) {
isInsert = 1;
// 穷举整除
for (long int j = 2; j < i;j++) {
if (i % j == 0) {
isInsert = 0;
break;
}
}
if (isInsert == 1) {
result++;
}
}
printf("%d\n", result);
return result;
}
int main() {
long int inNum;
if (scanf("%ld", &inNum)) {
primeNumber(inNum);
}
return 0;
}
然后我很快就写好给那位小兄弟参考了,没过一会那个小兄弟又来找我了。他说:不行,执行超时了,而且这题还要用两种不同的解法出来。
原来我忽略了时间限制,以为作业而已,只要解答出来就行了。这题目还有一个时间和内存的限制。
去平方根
上面的穷举法在统计10000以内的素数还可以,但是遇到十万,百万,千万的统计时,穷举的次数就会呈指数级的增长,于是我就上网冲浪去找数学里求素数的优解法,找到了一个开平方根的解法:原理
#include <stdio.h>
#include <math.h>
#include <time.h>
// 求素数
int primeNumber(long int num)
{
int result = 0;
int isInsert = 1;
if (num > 10000000)
{
return 0;
}
for (long int i = 2; i <= num; i++)
{
isInsert = 1;
// 穷举整除
for (long int j = 2; j <= (int)sqrt(i); j++)
{
if (i % j == 0)
{
isInsert = 0;
break;
}
}
if (isInsert == 1)
{
result++;
}
}
printf("%d\n", result);
return result;
}
int main()
{
long int inNum;
int startTime;
int endTime;
if (scanf("%ld", &inNum))
{
startTime = clock();
primeNumber(inNum);
endTime = clock();
printf("time: %dms\n", (endTime - startTime) / 1000);
}
return 0;
}
看了一下结果,计算一千万以内的数时,依然需要15秒左右的时间,一百万则需要600ms。
依然是没有达到要求。。。
继续优化
当继续然后我发现规律,素数除了2以外,其他数都是奇数
,因为偶数必然能被2整除~
那么继续来优化吧~
#include <stdio.h>
#include <math.h>
#include <time.h>
// 求素数
int primeNumber(long int num)
{
int result = 0;
int isInsert = 1;
if (num > 10000000)
{
return 0;
}
if (num >= 2) {
result++;
}
for (long int i = 3; i <= num; i+=2)
{
isInsert = 1;
// 穷举整除
for (long int j = 2; j * j <= i; j++)
{
if (i % j == 0)
{
isInsert = 0;
break;
}
}
if (isInsert == 1)
{
result++;
}
}
printf("%d\n", result);
return result;
}
int main()
{
long int inNum;
int startTime;
int endTime;
if (scanf("%ld", &inNum))
{
startTime = clock();
primeNumber(inNum);
endTime = clock();
printf("time: %dms\n", (endTime - startTime) / 1000);
}
return 0;
}
得出的结果与上面的几乎没有什么差别,比较在大数面前/2的操作也只是杯水车薪,时间复杂度摆在那里。所以必须要再换一种方法去去解题。
枚举法
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
// 求素数
bool isPrime(int x)
{
for (int i = 2; i * i <= x; ++i)
{
if (x % i == 0)
{
return false;
}
}
return true;
}
int primeNumber(int n)
{
int ans = 0;
for (int i = 2; i < n; ++i)
{
ans += isPrime(i);
}
printf("%d\n", ans);
return ans;
}
int main()
{
long int inNum;
int startTime;
int endTime;
if (scanf("%ld", &inNum))
{
startTime = clock();
primeNumber(inNum);
endTime = clock();
printf("time: %dms\n", (endTime - startTime) / 1000);
}
return 0;
}
在尝试了一晚上之后,还是没有解决出来... 在200毫秒,64MB的内存,写出一个能统计1千万以内的素数的算法(估计需要时间复杂度<= O(n)且空间复杂度 < O(n))
思考
最后,还是没解出来,这位小兄弟就交作业了~
虽然没写出来,但是还是引发了一些思考:
-
当我们需要遇到难题的时候,我们常常会说要迎难而上,但是实际上又有多少人会认真研究。很多人都是只要做到了行了,没有必要优化。等到出问题时再来想着优化,那时候事情可能已经变得不可控了。最终可能会演变成给后面接盘的小伙伴一个大坑。
-
同样,在我工作中遇到的很多领导和开发,遇到问题时大部分都会直接说:看看网上有没有现成的库直接拿来用。又没有考虑到以后的维护性与扩展性,为了赶进度而不管业务需求去使用现成的库,可能会无形中埋下一些严重的问题。
-
我经常在想:工作了也有好几年了,当初刚刚学习编程的热情现在还有多少分,刚刚接触编程的时候,没遇到一个新的特性或功能时,都会非常地兴奋。每次自己写出一些有意思的程序时都会非常高兴,觉得很有意思。到了工作时候,工作经历多了,觉得大部分时间都在拧螺丝,都是一些流水线的工作一样。在遇到这个问题是不去选择躺平,而是在业余时间继续自己去研究自己喜欢的方向。
-
或许这才是我们程序猿应该做的事情吧,保持自己当初的热情,去发掘和研究,发现有意思的东西。尽管这可能是与现实背道而驰,但是不忘初心,坚持自己的爱好。或许能让自己走的更远。