簡體   English   中英

最長的Collat​​z(或Hailstone)序列優化 - Python 2.7

[英]Longest Collatz (or Hailstone) sequence optimization - Python 2.7

我制作了一個程序,打印出一個數字列表,每個數字都有一個步驟(根據Collat​​z猜想 )需要比前一個更多的步驟:

limit = 1000000000
maximum = 0
known = {}
for num in xrange(2, limit):
    start_num = num
    steps = 0
    while num != 1:
        if num < start_num:
            steps += known[num]
            break;
        if num & 1:
            num = (num*3)+1
            steps += 1
        steps += 1
        num //= 2
    known[start_num] = steps
    if steps > maximum:
        print start_num,"\t",steps
        maximum = steps

我緩存了我已經知道的結果以加速程序。 這種方法可以達到10億的限制,我的計算機內存不足(8GB)。

  1. 是否有更有效的方法來緩存結果?
  2. 有沒有辦法進一步優化這個程序?

先感謝您。

看起來很難加速Collat​​z計划; 我所知道的最好的程序是使用全球數百(數千)台PC上的空閑周期進行分發的。

雖然速度和空間優化通常不一致,但是在純CPython中可以使用一些簡單的方法來優化程序。

  • 速度 :Python中的計算量很大的程序應始終作為函數編寫,而不是作為主程序編寫。 那是因為局部變量訪問明顯快於全局變量訪問。
  • 空間 :使known一個列表,而不是一個字典需要顯著的內存更少。 你要為每個號碼存儲一些東西; dicts更適合稀疏映射。
  • 空間array.array仍然需要更少的空間 - 盡管比使用列表慢。
  • 速度 :對於奇數n3*n + 1必然是偶數,所以你可以通過轉到(3*n + 1)//2 == n + (n >> 1) + 1將2步折疊成(3*n + 1)//2 == n + (n >> 1) + 1直。
  • 速度 :給定一個最終結果(數量和步數),則可以跳躍前進,並在結果這個數字時代2.一切權力。例如,如果填寫ns的步驟,然后2*ns+14*n將取s+2 8*n將取s+3 ,依此類推。

這里有一些包含所有這些建議的代碼,雖然我使用的是Python 3(在Python 2中,你至少要將range改為xrange )。 請注意,啟動時會有很長的延遲 - 這是用大量的32位無符號零填充大型array所需的時間。

def coll(limit):
    from array import array
    maximum = 0
    known = array("L", (0 for i in range(limit)))
    for num in range(2, limit):
        steps = known[num]
        if steps:
            if steps > maximum:
                print(num, "\t", steps)
                maximum = steps
        else:
            start_num = num
            steps = 0
            while num != 1:
                if num < start_num:
                    steps += known[num]
                    break
                while num & 1:
                    num += (num >> 1) + 1
                    steps += 2
                while num & 1 == 0:
                    num >>= 1
                    steps += 1
            if steps > maximum:
                print(start_num, "\t", steps)
                maximum = steps
            while start_num < limit:
                assert known[start_num] == 0
                known[start_num] = steps
                start_num <<= 1
                steps += 1

coll(1000000000)

獲得GONZO

1992年寫的一份技術報告提供了許多方法來加速這種搜索: “3x + 1搜索程序”,由Leavens和Vermeulen編寫 例如,@ Jim Mischel的“基於先前峰值的切斷”理念基本上是論文的引理20。

另一個:對於2的簡單因子,請注意你甚至可以“幾乎總是”忽略起始數字。 為什么:讓s(n)表示達到1所需的步數。您正在尋找s()值的新峰值。 假設最近的峰值在n找到,並且您正在考慮一個均勻的整數i其中n < i < 2*n 然后特別是i/2 < n ,所以s(i/2) < s(n) (通過“峰值”的定義並且在n達到新的峰值)。 但是s(i) == s(i/2) + 1 ,所以s(i) <= s(n)i不能成為新的峰值。

因此,在n處找到新峰值后,您可以跳過所有偶數整數(但不包括) 2*n

本文還有許多其他有用的想法 - 但它們並不是那么容易;-)

你只需要緩存奇數。 在你的程序中考慮當你開始編寫一個數字時會發生什么。

如果您使用起始編號X,並執行mod 4 ,則最終會出現以下四種情況之一:

  • 0或2:重復除以2最終會得到一個小於X的奇數。你有緩存的值。 因此,您可以將除數除以2,將其添加到緩存值,並且您具有序列長度。
  • 1:(3x + 1)/ 2將產生偶數,再將其除以2會產生小於X的數字。如果結果是奇數,那么你已經有了緩存值,所以你可以只加3就可以了。 如果結果是偶數,則重復除以2,直到得到奇數(已經緩存),將3和除以2的除法數加到緩存值,然后得到序列長度。
  • 3:執行標准的Collat​​z序列計算,直到得到小於起始編號的數字。 然后你要么緩存值,要么數字是偶數,你重復除以2,直到你得到一個奇數。

這可能會使您的程序稍微減慢一點,因為您有兩個除以2的除數,但它會使您的緩存容量加倍。

您可以通過僅保存x mod 4 == 3數字的序列長度來再次使緩存容量翻倍,但代價是處理時間更長。

那些只能讓你在緩存空間中線性增加。 你真正需要的是一種修飾緩存的方法,這樣你就不必保存那么多結果。 以一些處理時間為代價,您只需要緩存產生目前為止發現的最長序列的數字。

考慮到當你計算27有111步時,你已經保存:

starting value, steps
1, 0
2, 1
3, 7
6, 8
7, 16
9, 19
18, 20
25, 23
27, 111

所以,當你看到28時,你除以2得到14.搜索你的緩存,你會看到14到1的步數不能超過19(因為沒有少於18的數字需要超過19步)。 所以最大可能的序列長度是20.但是你已經有了最多111個。所以你可以停下來。

這可能會花費您更多的處理時間,但它會極大地擴展您的緩存。 您只有44個條目一直到837799.請參閱https://oeis.org/A006877

有趣的是,如果您對這些數字進行對數散點圖,則會得到非常接近的直線近似值。 請參閱https://oeis.org/A006877/graph

您可以通過保留第二個緩存來組合方法,對於數字大於當前最大值的數字,將該數字降低到當前最大值所需的步數。 因此,在上面的情況中,27有當前最大值,你將為數字35存儲26,因為它需要6次操作(106,53,160,80,40,20)才能得到35到20。你不能超過20步才能達到1,最多可能有26步。 因此,如果任何其他值減少到35,則將當前步數添加到26,如果該數字小於111,那么您知道使用此數字可能無法獲得新的最大值。 如果數字大於111,則必須繼續計算整個序列。

每當您找到新的最大值時,都會將生成它的數字添加到第一個緩存中,並清除第二個緩存。

這比較慢(我的直覺是在最壞的情況下它可能會使處理時間加倍),而不是緩存每個值的結果,但它會大大擴展你的范圍。

關鍵在於擴展你的范圍將以某種速度為代價。 這是一個常見的權衡。 正如我在上面所指出的,你可以做很多事情來保存每個第n項,這將為你提供一個更大的緩存。 因此,如果保存每個第4個值,則緩存基本上是保存每個值的4倍。 但是你很快就會達到收益遞減的程度。 也就是說,比原始緩存大10倍的緩存並不比9倍緩存大很多。

我的建議基本上給你一個指數增加的緩存空間,代價是一些處理時間。 但它不應該是處理時間的巨大增加,因為在最壞的情況下,具有下一個最大值的數字將是先前最大值的兩倍。 (想想27,有111步,有54步,有112步。)需要更多的代碼來維護,但它應該擴展你的范圍,目前只有30位,遠遠超過40位。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM