8.2.2 数组例子----素数(难)

1.判断素数

我们再来看看老朋友哈,我们之前做过怎么判断素数的程序.如果我们现在在学过函数之后,我们知道我们可以写一个isprime函数,所以我们这组程序可以写成这样.

如果isprime说是true,那这个x就是素数.否则就不是素数. 我们接下来研究的重点就是这个isprime是怎么做的,我们知道我们之前判断素数好像也没有用到数组.为什么现在放到数组这呢.我们来看:

我们最开始做的代码是这样的,我们从2测试到x-1,逐一的测试说你这个数能不能把x整除,如果可以的话x就不是素数.所以我们这样的代码要走多少遍呢,对于n来说,我们要走n-1遍,因为从2走到x嘛.当n很大的时候就是n遍(和后面学的时间复杂度有关).这是我们用来估计这个程序效率的一种办法.效率在于它循环的次数,因为程序里头别的代码都跑得很快,比如像ifelse这种只用做一遍的,可循环是要你一遍一遍跑的,所以当n很大的时候,你循环的次数就很大,你循环的语句就会重复执行很多次.循环次数越多,效率越低;循环次数越少,效率越高.

所以有人就说我可以有另外一种办法,因为我们说除了2以外,所有的偶数都不是素数,因此我们可以先来一句判断,如果它是偶数,ok那我们就拿掉了,我们就说这不是素数.如果偶数都拿掉之后,就意味着我们接下来的判断没有偶数了,下次判断可以从用从3开始的所有奇数开始.如果这样子就意味着我们只用做(n-3)/2+1遍,当n很大时为n/2遍.

这应该是一个很大的进步了.然后,有人就说了,你不用走到x-1啊,你走到sqrt(x)就可以了.这里的数学推论是这样的(我自己概括的,老师没说):设x=a*b;我们假设a是较小的因数(a最大和b相等),那么就有x>=a*a;假设这个数是非素数,那么我们只要从2到a整除x就可以得出结果为非素数,而如果是素数,因为a必然是1,那么从2到sqrt(x)都不能整除x,那么也可以判断x是素数了.而这个a必然是是肯定小于等于sqrt(x)的,所以我们可以只走sqrt(x)遍就得到结果.这显然又进一步优化了代码.(sqrt函数需要包含math.h)

🔺sqrt(x)遍已经很好了,那我们还有没有更好的,当然有,不然我们放数组讲什么.下一个方案是,我们不需要拿比x小的这些数,而是比x小的素数来测试它是不是素数就可以了.(这里应该是题目从一开始的判断一个数是不是素数到了求前n个素数.如果是前n个素数,那因为会有很多非素数,这些数我们的大部分我们会做的很快,次数加起来远比这些数的平方根加起来来的少)但是这就有一个前提 ,我们需要有一张已有的素数的表,然后根据这张表我才能来判断x是不是素数(合数可以有一个素数*其他部分组成,而这个素数一定是小于这个合数的数,我们可以通过这样子的方式来判定是否是合数,如果不是那就是素数).

所以这程序是这样的,我们构造了一个前100个素数的素数表prime[number];这个数组里面一共有一百个元素,将来出现在这个表里的每一个数都是素数,我们让它初始化为2,因为我们知道2是第一个素数,然后后面的99个单元都初始化为0.然后由于它里面已经有一个素数了,所以定义count=1;然后我们定义i=3(第二个素数),从3开始判断是不是素数.如果用我们的isprime发现是一个素数的话,我们就把i加到prime数组里去.count表明的是什么?也就是说我们有这么一个数组了,它的第一格(即下标为0)放了一个2,此时我们count变量的值是1,指向第二个单元,意思是直到把下一个素数3存储到这第二个单元里,然后count++变成2,指向第三个单元.即:

我们用这样一个count变量来表达我们在数组当中接下来要写进去的位置,我们把下一个素数写进去,然后再把count这个指针往后移.

所以如果我们的isprime给了我们这个结果,i是素数,我们就会把它写道下一个位置上去,如果他不是素数我们就不写进去,然后我们再来测试下一个i.当然最后我们去做那个输出.

我们很想看看,这个程序运行过程中prime数组的变化,这些变量count,i的变化是不是我们如想象的那样子.要看一个程序运行过程中这些变量的变化,就需要我们之前用到的手段,debug,也就是调试.我们在dev c++里面可以单步跟踪,可以查看那些变量的值.我们还讲过另外一个技巧,在程序适当的地方加入一些适当的输出语句,然后我们可以看到这些变量是怎么变化的.我们在if后面加上

大括号的好处是我在这里面可以有自己的变量i,我们这个i和外面的i是没有关系的.在定义这个i之前,我们还可以先把外面的i输出直到遇到我自己定义的那个i之前,我的i还是外面的i(限c99).如果不是c99,可以再给打印之后的这段代码套上一个大括号,所以我们为什么在调试的的时候要平白无故来上这么一对大括号,就是因为我们可以在里面定义自己的变量,而不会影响到外面的那个变量.那么我们这段代码很简单,就是做个for循环遍历一下prime数组,把prime数组所有的值都输出了.那在这个之前我们还会输出变量i和count的值.当然,为了输出好看,我们还可以在while循环之前加上这么一段,意思是在for循环之前输出一个表头

现在我们来看一下这件事情:

我们要求前10个素数,所以我们的cnt只要到10为止,但我们的i要一直走到29.在最开始,我们的第一行的这个表头,表明的是prime数组里面的第零位,第一位一直到第九位.for循环会一直到把所有的数组单元填完,把所有的十个素数找出来为止.

2.构造素数表

我们前面看了这么多求素数的方法,不断地做了改进,让它越来越快,但是基本的思路都是一样的,都是说我拿一个数出来,然后呢想办法构造一些东西让他去做整除,看看它能不能被整除,基本的思路都是一样的.但是求素数呢,我们还有另一个方法,我们可以反向来思考这件事情,我们不是去判断这个数是不是素数,我们去构造出一张表来,当这张表构造完成的时候,这张表里面的全部都是素数,怎么做这件事情呢,我们的算法列在这:

其实做法是这样子的:我们已经有2,3,4,5,6,7,8,9,10,11,12,13...我们把所有的数都先列在这边,假设它们全都是素数, 然后以2为基准,把所有2的倍数都删掉,即删掉4,6,8,10...这些肯定都不是素数,接下来下一个没有被标记为非素数的是3,然后把3所有的倍数都删掉,即6,9,12,15...我们看下一个没有被删掉的数是5,我们认定它是素数,然后从它开始,它的倍数10,15,20,25...然后下一个是7,把7的倍数删掉,再下一个是11,把11的倍数都删掉...最后留在这个表里面的就是素数.那么根据这个算法,我们可以进一步写出这样的伪代码:

这比算法又要更进一步了,因为在算法里面没有说,我们具体要怎么去构造这些东西,具体怎么去实现它,但是还没有到代码这个级别,所以我们把它叫做伪代码.根据这样一个伪代码,我们可以写出这样一个代码来:

我们从2开始到x,逐一的来构造这个东西,如果现在这个2是素数,那么我们对于从它的两倍开始,到这个倍数乘以它小于maxNumber为止,把它所有的倍数都标记为0,也就是都标记为非素数,那接下来x++,那么如果这时候x还是一个素数,我们就要再去标记它,如果它已经被标记为非素数,那么显然它的所有倍数也已经被标记过了,所以不需要再去标记了,这个循环做完我们再来遍历这个isprime数组.把所有是素数的,也就是isprime保留为为1的,那么就把这个i输出.我们可以加一些调试用的语句来看一下这个isprime数组到底是怎么变化的, 这是我们加进来的调试的输出:

一开始有一段表头,然后在循环的每一轮,我们来看看我们的isprime这个数组是怎么变化的,我们来看它的输出结果:

我们的表头表明的是数组当中的这一个数是不是素数,那么从2一直到24,当它是2的时候,它把所有的2的倍数都标记为0了;当它是3的时候,它把所有的3的倍数也都标记为0了,当它是4的时候,因为4已经被标记为非素数了,所以它并没有去做标记为素数的动作,以此类推.最后留下来的isprime还是1的,那么那些就是素数了.

所以我们见得,算法(计算机)的思考方式不一定和人相同,因为对于人类来说,告诉你素数的定义是什么,告诉你素数就是除了1以外只能被1和自身整除的那个数,那么我们去想这个素数的算法,一定会想,我们怎么去破这个题,我要怎么去让它证明它能够被别人整除,但是其实作为计算机来说,也许我们还有其他的方式,我们可以用和人不一样的方式,去想这件事情,我们可以从小到大来构造这么一个数数表.

(终于写完了!!!!!!!!!!!!!!!!!!!花了半天码字系列...)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值