3n+1問題的考察及python程式設計

m0_52427882發表於2020-11-12
'''3n+1問題,雖然很多文章提到,但是這裡還是簡單的說一下它的運算過程:
任意一個大於0的整數,
(1)假如它是偶數,則除以2;
(2)假如它為奇數,則乘以3再加1;
不斷重複這兩種操作,最後必將得到數字1。

用python 實現的演算法就是: '''
def collatz(n):
    if n<0: print("初始值",n,"不符合條件");return 
    while n != 1:
        print(n)
        if n%2 ==0: # (1)
            n=n//2
        elif n%2==1:# (2)
            n=n*3+1
    print(n)
''' 不用擔心程式停不下來,因為沒人 can 證明這個猜想不成立,至少我都沒有碰到某個死迴圈。
假如去掉“等於1”的判斷條件,把1放程式序算一下:'''
def test(n):
    ct=50
    while True:
        print(n)
        if n%2 ==0: # (1) fa
            n=n//2
        elif n%2==1:# (2) fb
            n=n*3+1
        ct-=1
        if ct==0:break
#test(1)

'''可以看出程式是在整數1這裡不斷打圈圈的: 1-奇數- 4-偶數-2-偶數-1 》》》,因此1是個黑洞,程式掉進去就出不來了。
嚴格來說,這個黑洞是由1,4,2 這3個整數(3點環)構成的。因此collatz 在 1 這裡做了個踩剎車動作,結果結論就像是總會得到 1 的
最終結果。完整的事實是得到一個[1,4,2]構成的三角形旋轉黑洞...閒話就此打住,下面繼續討論第一個程式的演算法問題.


這個演算法的流程控制,是由數值的奇偶性控制的。也就是說整個過程的暫態結果,處於奇數和偶數交越狀態之中。
當暫態結果是偶數時,它將由第(1)分支處理,直到變成奇數。
當暫態結果是奇數時,它將由第(2)分支處理,直到變成偶數。
為了凸現奇偶性之間的轉變,我們可以把分支(1)提取出來,主要用於實現從偶數到奇數的狀態轉變,新建一個輔助
函式,提取某個整數因子'''
def abstr(n,mod=2):# m 是要提取的因子
    ''' n = kore * (m ** time) '''
    time=0    # 次方
    kore=n    # 核
    while kore%mod==0 and kore > 0 :
        kore=kore//mod
        time+=1
    return kore,time
    
'''現在改寫原來的collatz函式,這樣列印的出來的數字會少一些'''

def collatz_abstr(n):
    if n<0: print("初始值",n,"不符合條件");return 
    while n != 1:
        print(n)
        if n%2 ==0: # (1)
            n=abstr(n)[0]
        elif n%2==1:# (2)
            n=n*3+1
    print(n)

    
#collatz_abstr(27)

'''事實上collatz猜想 等價於 :把任意一個大於1 奇數數丟程式序,最終都會得到 1這個結果。
因為分支(1)總會把偶數變成奇數,也就是不斷抽取2這個因子後得到的核,也就是初始值是偶數並不重要,它將變成奇數核。
因此這個猜想只需考慮:任意奇數最終轉換成 1 就行了。
要想觀察數列,可以開始觀察一下,奇數核時觀察一下,最終結果觀察一下'''
def collatz_abstr(n):
    if n<0: print("初始值",n,"不符合條件");return
    print(n)
    while n != 1:
        if n%2 ==0: # (1)
            n=abstr(n)[0]
            print(n)
        elif n%2==1:# (2)
            n=n*3+1

#collatz_abstr(27)

'''下面考慮一個符合條件的奇數q的轉變過程:
q奇數 ->  3*q+1 == 2*q + q+1 偶數
     -> q+ (q+1)/2  未知。

現在設q 被分成 大b小a兩部分,且滿足 a+b=q, b-a=1;
那麼  a= (q-1)/2  ; a 為小半部
     b= (q+1)/2   ; b 為大半部
     
那麼 上面 q+ (q+1)/2 未知 == (a+b)+b = 2b+a== 3b-1 ==3a+2,它的奇偶性取決於 a。
即
[1]若a 為奇數,則 q ->> 2b+a 是一個奇數 且大於 q(=b+a);
[2]若a 為偶數,則q ->> 2b+a 是 偶數,然後可以除以2 ->> b+ a/2 = 3a/2 +1 且小於 q 。

從[2]可以推匯出一條公式:一個初始奇數q,若表達成k*(2^n)-1的形式(其中k是奇數,n>=1),那麼會到達一個暫態結果k*3^n-1.
q =k*(2^n)-1 
q -> k*3^n-1
進一步推導,會有如果 q=k*(3^m)(2^n)-1 ,會到達一個暫態結果 (k*3^(m+n)-1)/2,其中k是奇數,n>=1。
這個公式也可以看出,好多初始值不同整數被“簡併”到同一個暫態結果上了。
假如你下過飛行棋,那麼這就像投到好的色子點數,在空中直接越過許多區域。
‘’‘‘'''
def fast(q):
    v=q+1
    k,n=abstr(v,mod=2)
    k,m=abstr(k,mod=3)
    v=(k*3**(n+m)-1)//2
    return v

'''現在把這個快速的暫態放進collatz的第二分支'''
print("----hh--")
def collatz_abstr(n):
    if n<0: print("初始值",n,"不符合條件");return
    print(n)
    while n != 1:
        if n%2 ==0: # (1)
            n=abstr(n)[0]
            print(n)
        elif n%2==1:# (2)
            n=fast(n)

#collatz_abstr(27)
'''假如你的運氣特別好,初始值是2^n,那麼這個函式一路到底,運氣次好一點的就是那些一步可以到2^n的奇數,
那麼是哪些奇數?
由 3*q+1=2^n 得 q=(2^n-1)/3,很明顯不是所有的2^n都可以整除以3,但是所有 2^(2*n)-1 可以。n>=1'''
def goodluck(n,show=1):
    if show==1:
        for i in range(1,n):
            v=(4**i-1)//3
            print(i,v)
    else:
        v=(4**n-1)//3
        return (v)

#goodluck(10)

'''假如把這些數轉換為二進位制數,你會看到奇特的現象,都是零一相間的數,這樣它的小一半加上它再加一就是4^n次方'''
n=10
for i in range(1,n):# 雖然 1 可以變成4 ,但是觸碰了collatz的底線,根據前面的討論,會落入迴圈
    v=goodluck(i,show=0)
    a=(v-1)//2 # v=2a+1;3a+2=v+a+1
    v=bin(v)
    a=bin(a)
    print("v={0:>20}".format(v[2:]))
    print("a={0:>20}".format(a[2:]))
    print("-----")


'''我們知道,“整數”這個概念是對“奇數”和“偶數”這兩個概念的簡併,
假如把整數分成奇數和偶數,那就是一種細分結構。
那麼再把奇偶細分,可以得到更細的概念。
因為奇偶代表著兩態,n%2==1 為奇,n%2==0 為偶。

假如我們把整數當成四態的數,類似地,

n%4 ==1 為1態,稱 n 為1態數 (或a數)
n%4 ==2 為2態,稱 n 為2態數 (或b數)
n%4 ==3 為3態,稱 n 為3態數 (或c數)
n%4 ==0 為0態,稱 n 為0態數 (或d數)

那麼這時的整數性質就成為 四態性質,不再是奇偶性質,(當然,此時也可以討論奇偶性,不過會發生簡併)
四態的運算性質和奇偶運算性質類似,不過框架不同了。

比如 1態數 +  1態數 變成 2態數 ,如 5+5=10,5是1態數,10是2態數。

又比如 3態數 x  3態數 變成 1態數, 如 3*7=21

這些性質可以由同餘理論推導。

那麼現在回到collatz上面來,前面我們說它的流程由奇偶行控制,那麼,現在由4態性控制。假如要讓程式能夠執行下去,那麼該如何改寫呢?

為了方便表達,我們把整數轉換成4態數數列,每個整數(正的)由它的態和“態中索引位置”決定,如下:
真值:0,  1, 2, 3, 4, 5, 6, 7, 8, 9,
態值:d0, a0,b0,c0,d1,a1,b1,c1,d2,a2

整數9就是a3,即a態裡排第三,按照列表的記法,可以寫成a[3]。

在collatz變換下,每一次“可推測”的態性變化規律如下:

c[i] -> a[i+j],j=(i+2)/2, 其中 i 為偶數。↑  *3+1)/2 
c[i] -> c[i+j],j=(i+1)/2, 其中 i 奇數。  ↑  *3+1)/2 


a[i]-> d[i+j], j=(i+1)/2,  其中 i 為奇數。↑  *3+1)/2
a[i]-> b[i+j], j=1/2 其中 i 為偶數。      ↑  *3+1)/2

b[i]-> a[j], j=i/2, 其中 i 偶數。        ↓  /2
b[i]-> c[j], j=(i-1)/2, 其中 i 為奇數。   ↓ /2

d[i]-> abstr(i)-> kore -> a c       ↓ '''
    #如果 i%4==1,轉化成 a[j],j=(i-1)//4
    #如果 i%4 ==3 ,轉化成 c[j], j=(i-3)//4
    #如果 i%4 ==2, m=i//2, 又
                        #如果m%4 ==1 轉化成 a[m//4]
                        #如果m%4 ==3 轉化成 c[m//4]
    #如果 i%4==0: -> d[i//4]

def what_is_the4(n):# 一個整數屬於哪個態及索引
    states="dabc"
    st=states[n%4]
    i=n//4
    return st,i
    

'''有意思的是,d[i]在向a和c轉換時,形成一種週期自相似結構。比如以4為週期列印轉換關係,則右邊的dom和原來的dom一樣。
d[i]的collatz轉換,在對映上形成分形結構。假如用分形的角度分析,那麼“態數”是尺度。再假如參考物理術語,那麼索引位置是“能級”
可以通過下面的列印觀察出來。'''
ct=0
print(" 數值, ran, dom       4_dom")
print("-----------------------")
for i in range(1,2000):
    if i%4==0:
        
        v=what_is_the4(n)
        v=v[0]+str(v[1])
        
        w=abstr(i)[0]
        w=what_is_the4(w)
        w=w[0]+str(w[1])
        print ("{0:>5}{1:>5s}{2:>5s}".format(i,v,w))
        ct+=1
        if ct% 16==0:ct=0;print("---------------------**",w)
        elif ct%4==0:print("-----------------------",w)
        
'''在研究那些轉換規律時,有些帶來數值的爬升,有些則下降,但是能否一直爬升或者一直下降?在同態爬升中
只有c->c 的轉換出現,很明顯,一個有限的能級值i, 它的若要保持後繼的i+j奇性不變,只能 i+1=2^n,經過n次轉轉後,
還是要改變奇偶性,也就是說,一直c態爬升是不可能的。c態最終變成a態。a態又經過b態或者d態下降變回a或c態,
但也有可能變成d態後就一直下降到a0=1了。詳細討論這些路徑應當很有趣,比如 a-> b-> a-> b-> a,最初a 和最終a的升降。
下面考察一下collatz(27)'''
print()
def collatz_see_me(n):
    if n<0: print("初始值",n,"不符合條件");return
  
    while n != 1:
        
        if n%2 ==0: # (1)
            v=what_is_the4(n); v=v[0]+str(v[1])
            print(n,v,"↓")
            n=n//2
            
        elif n%2==1:# (2)
            v=what_is_the4(n);v=v[0]+str(v[1])
            print(n,v,"\n------")
            
            n=n*3+1
    print(n)

#collatz_see_me(27)
'''collatz變換,將對態和能級進行轉變。collatz(27)的峰值達到 9232 d2308 ,d態的2308級
假如態數目(尺度)變大的話,那麼一個整數的能級將變小,比如把它放到128態數上面觀察,峰值是9232 16s:72 ,第16態的72級'''
def states(n,mod=128,s='s'):# n是任意正整數,mod為尺度,s為態的標記符號
    nevel=n//mod
    st=n%mod
    return str(st)+s+":"+str(nevel)

def collatz_see(n):
    if n<0: print("初始值",n,"不符合條件");return
  
    while n != 1:
        
        if n%2 ==0: # (1)
            v=states(n)
            print(n,v,"↓")
            n=n//2
            
        elif n%2==1:# (2)
            v=states(n)
            print(n,v,"\n------")
            
            n=n*3+1
    print(n)
#collatz_see(27)
'''可以看出,假如態數數目很大,比如16384=2^14 > 9232,那麼collatz(27)的整個過程將會在0級裡面打轉。
特定態數目下的collatz變換的規律,是和能級之間的轉換(跳躍)密切相關的,但我們也看到,態數目很大的時候,
一定範圍內的整數,沒有能級之間的跳躍,只在0級中進行態的轉變。這說明了什麼?把地圖放大看?'''
#十進位制 可以說是10態數,到底有什麼規律?我說不出來。但是下面列印出來的右邊的能級是有一些規律的,就是間隔著插入10個0,10個1,10個2...
for n in range(500):
    if n%2 ==0:
         v=states(n,mod=10)
         w=states(n//2,mod=10)
         print(n,v," ",w)
    elif n%2 ==1:
         v=states(n,mod=10)
         w=states((n*3+1)//2,mod=10)
         print(n,v," ",w)


'''=============
因為colatz涉及到(同餘)和分形,下面以同樣的方式來構造一個類似的函式。
可以看到其中有一個大的(迴圈)節點,還有類似歸吸引力的0,1,2這三個迴圈節點。
==========='''
def run(w, stop_counter, blackhole): #為了防止陷入死迴圈,設定了stop_counter
    t=stop_counter
    c=[];
    ori=w   # 保留原始值 
    print(ori)
    dangerous= blackhole | {0,1,2,3} 
        
    while w not in dangerous: # 類似於 collatz 不等於 1 的條件
        if w%3==0:   # 類似於 collatz 偶數
              w=w//3
                
              if w not in c: # 這個判斷是原來不知道有黑洞的情況下,用來探測黑洞的
                  c+=[w];
              else:
                  #print(c)
                  p=c.index(w)
                  return c[p:]
              
        else: 
            r=w%3
            w=5*w-2*r # 類似於 collatz 奇數*3+1 的條件;經過這樣處理,w%3=0

        t-=1
        if t==0: return 2, ori,len(c) # 原始值可能 導向黑洞,這時採取退出機制
    
    return 1

q=  [227, 377, 627, 209, 347, ## 這是默默無聞的“彌伽”黑洞
   577, 961, 1601, 2667, 889, 
   1481, 2467, 4111, 6851, 
   11417, 19027, 31711, 
   52851, 17617, 29361, 
   9787, 16311, 5437, 9061, 
   15101, 25167, 8389, 13981, 
   23301, 7767, 2589, 863, 
   1437, 479, 797, 1327, 2211, 
   737, 1227, 409, 681]

blackhole=set(q)


for i in range(1,20000):
   v=run(i, 30*i ,blackhole)
   if v is not 1: # 在充分的測試下(stop_counter= 30*i 全跑完 ),可能遇到 危險區域
      print(i,v);
      break
'''假如你的cpu很弱,那麼,這個程式會有些卡頓,因此這也是一個測試cpu的程式'''

相關文章