在做第四章(循环结构程序设计)的作业时,有一道“求1-100之间的所有素数”的题目,有意思的是最后出现了三种不同的写法,这三种写法的基本思想都差不多但其核心算法不同,做过求素数算法的人都知道,所谓素数就是除了1和它本身外没有其他数可以整除它的数,固然其核心算法就是检验这个数是否可以被其他数整除。毋庸置疑这个核心算法应当由循环结构来完成。明确了这些之后我们回来再说说这三种写法:
第一种写法是我最初尝试写这个题目时设计的算法(由于其核心算法采用REPEAT UNTIL型循环,故我们以下简称为REPEAT循环),它的代码是这样的:
REPEAT.PAS
第二种写法是我交作业前对第一种算法的改进(由于其核心算法采用DO WHILE型循环,故我们以下简称为WHILE循环),它的代码是这样的:
WHILE.PAS
第三种写法是我们教科书上的答案(由于其核心算法采用FOR型循环,故我们以下简称为FOR循环),它的代码是这样的:
FOR.PAS
下面我简单分析一下这三种核心算法:
首先说说FOR循环的算法,这个算法是书上给出的,它比较“循规蹈矩”一些,采用了2到这个数的平方根作为除数的方法,通过取余数判断当前数是否能被这些除数整除,当然如果可以的话flag将被标记为false即当前数不是素数。值得一提的是这个算法并没有逐一尝试所有的其他数,只是尝试了2到这个数的平方根。这样做很大程度上提高了程序执行的效率!为了理解其原理这里原引书上的一段话:
用2,3,4,…,根号i去除,如果都除不尽,则i是素数。
这是因为,如果小于等于根号i的数都除不尽i,则大于根号i的数也不能除尽i。
用反证法证明:假设有大于根号i的数j能除尽i,则它的商k必小于根号i,且k能除尽i(其商为j)。这是与原命题(小于等于根号i的数都除不尽i)相矛盾的,假设不成立,原命题得到证明。
但为什么说其“循规蹈矩”呢?那让我们来看看下面两种算法就知道了,REPEAT循环的算法是我最初对这个程序的设计。众所周知REPEAT循环是一种条件型循环,也就是说它的循环是由一个或多个条件来控制的,换句话说我们可以人为干涉它的循环次数。另一方面,判断一个数是否为非素数我们必须把2到这个数的平方根都试一遍么?当然不是,正如大于16的偶数我们只要知道它能被2整除就不用再考虑它能否被4整除的道理一样,只要我们知道一个数已经是非素数了我们就可以大胆地终止这个循环。基于以上两点,我在循环条件中加入了”NOT flagnum”,使循环可以及时退出避免了循环体被无为地执行,提高了程序的执行效率!但采用REPEAT循环有一个致命的缺点,那就是由于其循环条件位于循环体的后边使得循环体被无条件执行了一次,然而正如文章前面提到的这种核心算法将会把2判断为非素数,作为补救的办法只得在第一层循环的外面首先输出2为素数的结果之后再用核心算法判断3-100之间的数,无形中降低了代码的可读性。
那么如何避免REPEAT循环带来的副作用呢?作为改进我使用了WHILE循环的算法,这样就从本质上解决了REPEAT循环的副作用(避免了循环体被无条件执行)。下面我们来看看WHILE循环的算法,其循环条件位于循环体的前边,内容为” flagnum AND (i<=round(sqrt(num)))”,这样既提高了程序的执行效率又避免了可读性下降的弊端。另外值得注意的是判断2是否为非素数时,由于round(sqrt(2))=1即条件” i<=round(sqrt(num))”为假,所以循环体并没有被执行,故flagnum保持为真即得到2为素数的结果,这与FOR循环的算法有异曲同工之妙。(其实如果循环体被执行的话2仍然会被判断为非素数)
作为这篇文章的小结,本人在这里主观地评价一下这三种算法:
从“代码可读性”上来分析REPEAT循环的算法显然没有另外两种循环的算法简洁,而FOR循环的算法要比WHILE循环的算法更容易理解;从“程序执行效率”上来分析采用FOR循环算法的程序执行效率明显低于采用另外两种循环算法的程序,而WHILE循环算法的程序执行效率应当是最高的(所求数的值和范围越大此算法优势越明显)。
综上所述对于这个问题我们可以得出以下结论:
“WHILE循环算法”优于“FOR循环算法”优于“REPEAT循环算法”
史晨 2007年4月15日