快速寻找勾股数算法的实现和优化
深夜隔壁寝室的老哥来访,说他用python实现的寻找2000以内勾股数的算法跑了20秒钟。邀请我一起讨论优化思路,完成后记录如下:
朴素探数法寻找勾股数
首先实现那个需要20秒钟的朴素算法,思路非常简单,三重for循环遍历,利用了勾股数的以下性质:
a2 + b2 == c2
python代码实现:
def gcd(m,n): return m if n == 0 else gcd(n,m%n) def nfgg(max): results = [] for a in range(1,max+1): for b in range(a,max+1): for c in range(b,max+1): if a*a + b*b == c*c and gcd(a,b) == 1: results.append([a,b,c]) for l in results: print(l) print("total : " + str(len(results))) return results
我已经对这种朴素算法进行了简单优化,以下是注意事项:
1*,gcd()方法用于递归求公因数,此处用于除去派生勾股数,如6,8,10
2,b,c两变量无需从1开始遍历
3*,max是遍历边界,注意range()方法左闭右开的性质参数需填写(1,max+1)
算法优化
尝试了以上算法,发现确实慢的离谱,我通过查阅资料发现一种数学上的优化思路:
1. 定义:凡符合a2+b2=c2的正整数a,b,c我们称之为一组勾股数。a和b是直角边,c是斜边。 2. 凡有公约数的勾股数我们称之为派生勾股数,例[30,40,50] 等; 3. 无公约数的勾股数,例[3,4,5];[8,15,17]等,我们称之为勾股数。 有:全是偶数的勾股数必是派生勾股数,三个奇数不可能符合定义公式。两偶一奇和两奇一偶都可以被证明不符合公式条件,因此,勾股数唯一的可能性是: a和b分别是奇数和偶数(偶数和奇数),斜边c只能是奇数。 4. 勾股数具有以下特性: 斜边与偶数边之差是奇数,这个奇数只能是某奇数的平方数, 例1,9,25,49,…… 斜边与奇数边之差是偶数,这个偶数只能是某偶数平方数的一半, 2,8,18,32,…… 5. 由以上定义我们推导出勾股公式: a = p2 + q2 b = q2/ 2 + pq c =p2+ q2/ 2 + pq 6,此公式涵盖了自然界的全部勾股数,包括派生勾股数。 以任意奇数代入P ,任意偶数代入Q ,即可得到唯一一组勾股数。 例如P = 5 ,Q = 8 ,得到 X = 25 + 5×8 = 65 Y = 32 + 5×8 = 72 Z = 25 + 32 + 5×8 = 97 当P与Q有公约数时,例如9与12 ,再例如21与28等,推导出来的是派生勾股数; 当P与Q无公约数时,例如9与8 ,再例如21与16等,推导出来的是勾股数;
(引用来自百度,有修改)
根据以上数学方法,可以得到一种代码实现思路:
双重for循环遍历max内的奇数a和偶数b,
如果gcd(a,b) == 1:
如果t = aa + bb <= max :
寻找到一组勾股数(a,b,c=pow(t,0.5))
优化:当 aa + bb > max时,跳出第二重循环
最后对结果进行简单处理
python实现如下:
def gcd(m,n): return m if n == 0 else gcd(n,m%n) def fgg(max): # find numbers results = [] for i in range(1,max+1,2): for j in range(2,max+1,2): if gcd(i,j) == 1 : t = pow(i*i+j*j,0.5) if t > max: break elif int(t) == t: t = int(t) results.append([i,j,t]) # handle the resulta for l in results: l.sort() results.sort() for l in results: print(l) print("total : " + str(len(results))) return results
效率提升非常明显,如果没有特殊需要,代码中的列表排序完全可以去除。
https://tieba.baidu.com/p/6435191370 http://blog.sina.com.cn/s/blog_184e9f38b0102yyi5.html https://www.douban.com/group/topic/162687100/