快速尋找勾股數算法的實現和優化
深夜隔壁寢室的老哥來訪,說他用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/