[.py]寻找第n个默尼森数(初级版)

来源:互联网 发布:剑灵灵女捏脸数据下载 编辑:程序博客网 时间:2024/05/16 09:53

经典程序设计问题:找第n个默尼森数。

P是素数且M也是素数,并且满足等式M=2^P-1,则称M为默尼森(monisen)数。

例如,P=5,M=2**P-1=31,5和31都是素数,因此31是默尼森数。

本题要求input一个值k(k<9),输出第k个默尼森数

输入样例:4

输出样例:127



问题分析

按照自底向上的编程思想,要实现问题,则需要有以下几个部分:
判断一个数x是否为素数;
已知x为素数时,判断2^x-1是否为素数;
已知2^x-1是素数,则可知它是默尼森数,要知道它是第几个默尼森数;

其实为了简化问题,把k设置成了只计算前8个monisen数,因为实际上第8个monisen数是2^32-1,是signed int型数的可表示的最大值,写好了程序如果要计算更大的数,需要换用其它的数据格式,所以这里为了仅强调计算方法而不考虑更大的monisen数。


可以先试一下简单的情况:素数数列前几项k=[2,3,5,7,11,13,...],对应的2^k-1值是[3,7,31,127,2047,8191,...],其中前4项[3,7,31,127]都是默尼森数,而2047,8191不是质数,就不是默尼森数。

所以可以这样设计:


程序描述:

#输出第num个默尼森数
monisen(num):
若num<1,应报告ERROR
k=0
i=1
while(k<num):
t是第i个素数
i+=1
如果,2^t-1也是素数,则:
计数器k+=1;

跳出while循环时,k=num,直接输出2^t-1的值,即为第num个monisen数

概要设计

有了程序的描述,我们看到,monisen(num)是由这样几个主要函数组合而得:prime(num),输出第num个素数;isPrime(x),判断x是否为素数
要得到第num个素数,应先有isPrime(x)函数做判断
由数论的知识,要判断一个整数x是否为素数,就要用x对从2到sqrt(x)之间的素数取余,若余数全不为0,则x就是素数,否则只要有任何一个为0,那么x就不是素数。
这里有两点需要思考,除数序列是从2到sqrt(x)之间的素数,那么序列上限选sqrt(x)是否有必要,二是选择这其中这素数是否有必要,因为若选择不恰当就会影响运行速度。
首先是上限选择,若选sqrt(x)意味着要计算sqrt函数,如果允许使用库函数的话,在python中可以调用math.sqrt(x)即可,因为这是循环的上限。如果不能调用库函数,则要自己另外编sqrt(x)的函数,嫌麻烦可以直接省去,,取序列的上限为x-1即可。
另外,是否有必要真的选取这个序列中的素数呢,这个一般是不需要的。假设我们真用一个素数序列来判断,那么我们要先生成从2到int(sqrt(x))的所有素数的列表,再从这个列表里选出每一项分别比对,若判断x是素数,又要把这个素数加到列表里。程序的运行效率没有显著提升多少,却使编写程序的工作量大大增加,所以这是不太合算的。


综上,判断x是否是素数,可以先和2取余,若得0,说明它是偶数,就一定不是质数。若它与2取余为1,则再从3到sqrt(x)的step=2的序列里做取余判断,只要当中有一步是0,就立即返回False结束循环,然后在循环出口处返回True,说明走遍全部的序列都没有余0的情况,它是一个素数。


import mathdef isPrime(num):k=int(math.sqrt(num))if (i % 2 == 0) :return Falsefor i in range(3,k+1,2):if (num % i ==0):return Falsereturn True


那么有了isPrime函数,得到第i个素数的函数prime(num)就不难实现。令初始化k为计数器,num=1时直接输出2,然后从3开始依次找奇数,如果这个奇数t是素数,则给计数器k自增1,直到计数器k的值等于num时,输入这个数t就是第num个素数。


def prime(num):if num==1: return 2k=1i=1while(k<num):i+=2if(isPrime(i)==True):k+=1return i

这样有了函数的定义,寻找默里森数就可以完成了,根据程序描述,写出morisen(num)的python代码

def monisen(no):k=0i=1t=0while(k<no):t=prime(i)if(isPrime(2**t-1)==True):k+=1i+=1return 2**t-1

程序设计

主程序可以只有一条,就是输入一个数k,输出第k个默尼森数,那么就是把k传到monisen()函数里就行,因为monisen()函数接收的参数就是第k个默尼森数

print(monisen(input()))

反思与评价

程序有哪些细节需要注意?

1.python里当用range时,它是有多种表示类型的,最完整的是range([start=0,]end[,step=1),也就是只有一个变量的话是end值,初值缺省值为0。只写前2个参数则表示步长为1的从start到end的序列。但要注意的一点是,range()序列是取不到终值end的,也就是它的序列x是在start<=x<end当中取得,所以在isPrime()中用到range(3,k+1,2),实际上x的取值就是3<=x<=k
2.range()函数的参数需要是整数,而math.sqrt()返回值是float,所以要先类型转换,转换成int再在range()函数里使用。


程序是否可以改进,以提高运算速度?

关键在于prime(num)函数,它是求第num个素数,每调用一次它,就要做一趟循环,去求出第1个素数,第2个素数,第3个素数,...第num个素数,而每次要求一个素数时,又要去调用isPrime(k),它也是一个循环。仔细想来,求第n个素数时,前n-1个已经在上一趟循环里求过了,但是程序没留下来,我们可以用列表把前已经求过的素数放在列表里,而不需要每次再从头求一遍。不过对于此问题来说,数据规模较小,此处影响不太大。但是要成为专业的程序员的话,就一定要试着优化它