如果您有兴趣,我会在math stackexchange, "Detecting perfect squares faster than by extracting square root" 对类似问题进行纯数学回复。
我自己的 isSquare(n) 实现可能不是最好的,但我喜欢它。我花了几个月的时间学习数学理论、数字计算和 python 编程,将自己与其他贡献者进行比较等,才真正点击了这种方法。我喜欢它的简单性和效率。我没见过更好的。告诉我你的想法。
def isSquare(n):
## Trivial checks
if type(n) != int: ## integer
return False
if n < 0: ## positivity
return False
if n == 0: ## 0 pass
return True
## Reduction by powers of 4 with bit-logic
while n&3 == 0:
n=n>>2
## Simple bit-logic test. All perfect squares, in binary,
## end in 001, when powers of 4 are factored out.
if n&7 != 1:
return False
if n==1:
return True ## is power of 4, or even power of 2
## Simple modulo equivalency test
c = n%10
if c in {3, 7}:
return False ## Not 1,4,5,6,9 in mod 10
if n % 7 in {3, 5, 6}:
return False ## Not 1,2,4 mod 7
if n % 9 in {2,3,5,6,8}:
return False
if n % 13 in {2,5,6,7,8,11}:
return False
## Other patterns
if c == 5: ## if it ends in a 5
if (n//10)%10 != 2:
return False ## then it must end in 25
if (n//100)%10 not in {0,2,6}:
return False ## and in 025, 225, or 625
if (n//100)%10 == 6:
if (n//1000)%10 not in {0,5}:
return False ## that is, 0625 or 5625
else:
if (n//10)%4 != 0:
return False ## (4k)*10 + (1,9)
## Babylonian Algorithm. Finding the integer square root.
## Root extraction.
s = (len(str(n))-1) // 2
x = (10**s) * 4
A = {x, n}
while x * x != n:
x = (x + (n // x)) >> 1
if x in A:
return False
A.add(x)
return True
非常直接。首先,它检查我们是否有一个整数,并且是一个正整数。否则没有意义。它让 0 作为 True 滑过(必要的,否则下一个块是无限循环)。
下一个代码块使用位移位和位逻辑运算在一个非常快速的子算法中系统地去除了 4 的幂。如果可能的话,我们最终不是找到原始 n 的 isSquare,而是找到已按 4 的幂按比例缩小的 k第三个代码块执行一个简单的布尔位逻辑测试。在二进制中,任何完美正方形的最低有效三位数字都是 001。总是。无论如何,除了由 4 的幂产生的前导零,这已经被解释过了。如果它未通过测试,您立即知道它不是正方形。如果它通过了,你不能确定。
此外,如果我们以 1 作为测试值结束,那么测试数最初是 4 的幂,可能包括 1 本身。
与第三个块一样,第四个块使用简单的模运算符测试十进制中的个位值,并倾向于捕获通过前一个测试的值。还有一个 mod 7、mod 8、mod 9 和 mod 13 测试。
第五个代码块检查一些众所周知的完美正方形图案。以 1 或 9 结尾的数字前面是 4 的倍数。并且以 5 结尾的数字必须以 5625、0625、225 或 025 结尾。我曾包括其他数字,但意识到它们是多余的或从未实际使用过。
最后,第六段代码与最佳答案者 Alex Martelli 的答案非常相似。基本上使用古老的巴比伦算法找到平方根,但将其限制为整数值而忽略浮点数。为速度和扩展可测试值的大小而完成。我使用集合而不是列表,因为它花费的时间要少得多,我使用位移而不是除以二,而且我巧妙地选择了一个更有效的初始起始值。
顺便说一下,我确实测试了 Alex Martelli 推荐的测试号,以及一些大几个数量级的数字,例如:
x=1000199838770766116385386300483414671297203029840113913153824086810909168246772838680374612768821282446322068401699727842499994541063844393713189701844134801239504543830737724442006577672181059194558045164589783791764790043104263404683317158624270845302200548606715007310112016456397357027095564872551184907513312382763025454118825703090010401842892088063527451562032322039937924274426211671442740679624285180817682659081248396873230975882215128049713559849427311798959652681930663843994067353808298002406164092996533923220683447265882968239141724624870704231013642255563984374257471112743917655991279898690480703935007493906644744151022265929975993911186879561257100479593516979735117799410600147341193819147290056586421994333004992422258618475766549646258761885662783430625 ** 2
for i in range(x, x+2):
print(i, isSquare(i))
打印了以下结果:
1000399717477066534083185452789672211951514938424998708930175541558932213310056978758103599452364409903384901149641614494249195605016959576235097480592396214296565598519295693079257885246632306201885850365687426564365813280963724310434494316592041592681626416195491751015907716210235352495422858432792668507052756279908951163972960239286719854867504108121432187033786444937064356645218196398775923710931242852937602515835035177768967470757847368349565128635934683294155947532322786360581473152034468071184081729335560769488880138928479829695277968766082973795720937033019047838250608170693879209655321034310764422462828792636246742456408134706264621790736361118589122797268261542115823201538743148116654378511916000714911467547209475246784887830649309238110794938892491396597873160778553131774466638923135932135417900066903068192088883207721545109720968467560224268563643820599665232314256575428214983451466488658896488012211237139254674708538347237589290497713613898546363590044902791724541048198769085430459186735166233549186115282574626012296888817453914112423361525305960060329430234696000121420787598967383958525670258016851764034555105019265380321048686563527396844220047826436035333266263375049097675787975100014823583097518824871586828195368306649956481108708929669583308777347960115138098217676704862934389659753628861667169905594181756523762369645897154232744410732552956489694024357481100742138381514396851789639339362228442689184910464071202445106084939268067445115601375050153663645294106475257440167535462278022649865332161044187890625 True
1000399717477066534083185452789672211951514938424998708930175541558932213310056978758103599452364409903384901149641614494249195605016959576235097480592396214296565598519295693079257885246632306201885850365687426564365813280963724310434494316592041592681626416195491751015907716210235352495422858432792668507052756279908951163972960239286719854867504108121432187033786444937064356645218196398775923710931242852937602515835035177768967470757847368349565128635934683294155947532322786360581473152034468071184081729335560769488880138928479829695277968766082973795720937033019047838250608170693879209655321034310764422462828792636246742456408134706264621790736361118589122797268261542115823201538743148116654378511916000714911467547209475246784887830649309238110794938892491396597873160778553131774466638923135932135417900066903068192088883207721545109720968467560224268563643820599665232314256575428214983451466488658896488012211237139254674708538347237589290497713613898546363590044902791724541048198769085430459186735166233549186115282574626012296888817453914112423361525305960060329430234696000121420787598967383958525670258016851764034555105019265380321048686563527396844220047826436035333266263375049097675787975100014823583097518824871586828195368306649956481108708929669583308777347960115138098217676704862934389659753628861667169905594181756523762369645897154232744410732552956489694024357481100742138381514396851789639339362228442689184910464071202445106084939268067445115601375050153663645294106475257440167535462278022649865332161044187890626 False
它在 0.33 秒内完成。
在我看来,我的算法与 Alex Martelli 的算法一样,具有所有优点,但还有一个额外的好处是高效的简单测试拒绝,可以节省大量时间,更不用说减少测试数量的大小了通过 4 的幂,这提高了速度、效率、准确性和可测试数字的大小。在非 Python 实现中可能尤其如此。
在巴比伦根提取之前,大约 99% 的整数被拒绝为非平方,而巴比伦拒绝整数所需的时间是巴比伦人的 2/3。尽管这些测试并没有显着加快这一过程,但通过除以 4 的所有幂真正将所有测试数字减少到奇数会加速巴比伦测试。
我做了一个时间比较测试。我连续测试了从 1 到 1000 万的所有整数。仅使用巴比伦方法本身(根据我特别定制的初始猜测),我的 Surface 3 平均花费了 165 秒(100% 准确度)。仅使用我的算法中的逻辑测试(不包括巴比伦),它花了 127 秒,它拒绝了 99% 的所有整数作为非平方,而没有错误地拒绝任何完美的平方。在那些通过的整数中,只有 3% 是完美的正方形(密度更高)。使用上面使用逻辑测试和巴比伦词根提取的完整算法,我们有 100% 的准确度,并且测试仅在 14 秒内完成。测试前 1 亿个整数大约需要 2 分 45 秒。
编辑:我已经能够进一步缩短时间。我现在可以在 1 分 40 秒内测试整数 0 到 1 亿。大量时间浪费在检查数据类型和积极性上。消除前两次检查,我将实验缩短了一分钟。必须假设用户足够聪明,知道负数和浮点数不是完美的正方形。