我們都知道任何正整數都可以分解為質因數之乘積,稱為質因數分解。同樣的,任何正整數也都可以分解為「相異的費波那契數之和」,而且可能有多種組合,每種組合的費波那契數都不同。例如:
101 = F(1) + F(4) + F(6) + F(11)
之前提過,費波那契數的密度比質數低很多,到19位整數也不過92個費波那契數。19位整數相當於10億的10億倍,這麼多數字裡面只有92個費波那契數,可見之稀少,但卻只要這92個費波那契數,就能夠組合出10億的10億倍的數字,不由得讓人吃驚。所以老子說:「道生一,一生二,二生三,三生萬物」,的確很有道理。
我們用程式來驗證一下,看任意整數是否都能分解為相異費波那契數之和。
// 1-6a 整數分解(費波那契數之和)
// Created by Heman, 2021/07/22
var 數列 = [0, 1]
var 最大索引 = 1
func 費氏數列(_ n: Int) -> Int {
if n < 0 { return -1 }
if n <= 最大索引 {
return 數列[n]
}
let m = 最大索引 + 1
for i in m...n {
let 下一個 = 數列[i-1] + 數列[i-2]
數列 = 數列 + [下一個]
}
最大索引 = n
return 數列[n]
}
func 整數分解(_ n: Int) -> [Int] {
var 數列: [Int] = []
var 索引 = 0
if n <= 0 { return [] }
while 費氏數列(索引) < n {
索引 = 索引 + 1
}
if 費氏數列(索引) == n { return [n] }
let 最近費數 = 費氏數列(索引-1)
let 差值 = n - 最近費數
數列 = 整數分解(差值) + [最近費數]
return 數列
}
print(整數分解(2021))
利用上一課1-5c的「費氏數列()」函式,我們只需要再增加一個函式「整數分解(n)」,傳回n分解為費波那契數的數列。
基本的想法很直覺,就是先找到最接近(但小於或等於)參數n的費波那契數,這用一個 while 迴圈句就能搞定。
while 費氏數列(索引) < n {
索引 = 索引 + 1
}
當脫離迴圈時,「索引」所指的費波那契數會大於或等於n。接下來用一個條件句:
if 費氏數列(索引) == n { return [n] }
如果n剛好是費波那契數,直接命中,那就傳回 [n] 這個小陣列,不用再加別的費波那契數了。否則的話:
let 最近費數 = 費氏數列(索引-1)
let 差值 = n - 最近費數
數列 = 整數分解(差值) + [最近費數]
最接近的費波那契數應該是「索引-1」的那個數,這樣就找到數列的最後一個元素了。然後將差值再傳入「整數分解(差值) 」函式中,傳回結果再與前次找到的最後一個元素的陣列,相加(陣列合併)在一起。
這裡函式有個特殊的用法,就是在函式的宣告裡面呼叫自己,像這樣:
func 整數分解(_ n: Int) -> [Int] {
....
整數分解(差值)
....
}
這叫做「遞迴」呼叫(Recursive call),有點像佛教裡的輪迴,呼叫上一輩子的自己,問他上輩子的事情。以上面程式中「整數分解(2021)」為例,遞迴的過程如下圖所示。