【问题描述】
如果有两个正整数a和b,a的所有除本身以外的因数之和等于b,b的所有除本身以外的因数之和等于a,则称a,b是一对相亲数。请输出10000以内的相亲数对。
输出结果:
[(284,220),(1210,1184),(2924,2620),(5564,5020),(6368,6232)]
【题前思考】
根据问题描述,填写表5-2-1。
表5-2-1 问题分析
【解题思路】
从问题描述来看,判断两个数是否是相亲数,主要操作就是分别求它们的约数之和。为避免重复计算,用一个列表sf来表示约数之和。为了表示上的方便,约定整数i的约数之和保存在a[i]中。这样规定之后,相亲数的表示就比较方便了。只要i==a[a[i]],那么i和a[i]就是一对相亲数。
【程序代码】
【代码分析】
①:定义局部函数sumfactor(m)用于求m的约数之和。因为它定义在函数amicable(n)内部,所以只有在amicable(n)内才能调用这个函数。
②:sum(j for j in range(1,m)if m%j==0)使用生成器求m的约数之和。如果表达式j for j in range(1,m)if m%j==0用[ ]括起来,它就是列表推导式,表达式会一次性产生所有值作为列表的项保存到内存中。像本例中的这种用法,用( )括起来或者不用( )括起来,它就是生成器,生成器与列表推导式的最大区别是它不会把项一次性产生出来,而是取一个生成一个,它比列表推导式更省内存。
③:使用列表推导式产生各个数的约数之和,保存规则就是将i的约数之和保存到a[i]中。sumfactor(x)if x>1 else x表示0的约数之和为0,1的约数之和为1,其他数的约数之和用局部函数sumfactor(x)求取。
④:[(i,a[i])for i in range(2,n+1)if a[i]<i and i==a[a[i]]]使用列表推导式筛选相亲数对。if a[i]<i and i==a[a[i]]中,a[i]<i既可以防止a[a[i]]越界,又可以防止重复输出和输出完全数,i==a[a[i]]是判断相亲数的条件。
【优化提升】
上例中给出的程序虽然简单,但是在求约数和时速度比较慢,而且求约数和还是关键代码,运行次数非常多。所以,要提高整个程序的运行速度,关键在于提高求约数和的速度。
我们经过观察会发现,如果j是m的约数,那么m//j肯定也是m的约数,因为此时j*m//j==m。采用这个方法,可以减少一半的判断。为了避免重复要求j<=,此外还需要考虑完全平方数的问题。优化后的代码如下:
表达式s if root*root!=m else s-root表示对完全平方数要减去一个平方根,这是因为当m是完全平方数且j==root的时候,j和m//j值相同,平方根被多加了一次。