關(guān)于斐波那契數(shù)列求值問題

斐波那契數(shù)列

斐波那契數(shù)列是一個(gè)經(jīng)典的數(shù)學(xué)問題,同時(shí)也是算法中的經(jīng)典案例莉擒,并且衍生出了很多類似的問題贿条,這個(gè)問題簡單來說就是當(dāng)前數(shù)列的元素是由前兩個(gè)數(shù)的和構(gòu)成粹懒。不如舉個(gè)栗子:

比如 F(0) = 0, F(1) = 1, 那么 F(2) = F(0) + F(1),也就是說F(2) = 0 + 1 = 2勺像,依次循環(huán)得出相關(guān)的數(shù)列內(nèi)容障贸。

所以依據(jù)這樣的規(guī)律特點(diǎn),我們可以寫出下面的遞推內(nèi)容:

F(3) = F(2) + F(1)
F(4) = F(3) + F(2)
F(5) = F(4) + F(3)
F(6) = F(5) + F(4)
......

遞歸解法

這就明顯感覺是迭代求值的關(guān)系吟宦,所以依據(jù)這樣的特點(diǎn)篮洁,可以采用最基本的遞歸的方法去完成求值。實(shí)現(xiàn)內(nèi)容如下:

func fibRecurrence(_ n: Int) -> Int {
        if n == 0 {
            return 0
        }
        if n == 1 {
            return 1
        }
        return fib(n - 1) + fib(n - 2)
    }

但是仔細(xì)看一下當(dāng)前的時(shí)間復(fù)雜度殃姓,會(huì)發(fā)現(xiàn)是O(2^n)的袁波,效率略微有點(diǎn)差瓦阐。

正推法求值

遞歸的方法求值實(shí)際上是從n往0,反向遞推求其值篷牌,但是這樣有一個(gè)不好的地方在于重復(fù)計(jì)算了已經(jīng)算過的值睡蟋,例如在求解F(5)的時(shí)候內(nèi)容如下:

F(5) = F(4) + F(3)

再去求解F(4)的時(shí)候,你會(huì)發(fā)現(xiàn)F(3)重復(fù)計(jì)算了兩次枷颊,如下:

F(4) = F(3) + F(2)

按照此內(nèi)容推下去戳杀,計(jì)算就會(huì)增倍了。如果按照正向遞推的方式的話偷卧,就剛好解決了這樣的問題豺瘤,在求解F(5)的時(shí)候已經(jīng)正向求解F(4)與F(3)的結(jié)果了,所以直接累加即可得其結(jié)果听诸。所以按照這樣的思路坐求,我們可以正向遞推求值:

func fibCount(_ n: Int) -> Int {
        if n == 0 {
            return 0
        }
        if n == 1 {
            return 1
        }
        var curValue = 1
        var preValue = 0
        var resValue = curValue + preValue
        for _ in 2...n {
            resValue = curValue + preValue
            preValue = curValue
            curValue = resValue
        }
        return resValue
    }

這個(gè)時(shí)間復(fù)雜度是O(n)的。

矩陣乘法

真正的O(n)就是最優(yōu)解了嘛晌梨?答案是否定的桥嗤,因?yàn)橛袀€(gè)神級的解法,叫做升維跨越仔蝌,可以將其時(shí)間復(fù)雜度變成O(logn)的泛领,具體做法就是利用矩陣乘法。

矩陣推理

矩陣推理

所以經(jīng)過一系列的推倒敛惊,矩陣變換成了如下的內(nèi)容:


最終形式

所以這個(gè)遞推升維公式就成功的將F(n)的求解變成了二維矩陣的求冪問題渊鞋。

這里解釋一下為什么要將左邊的格式改成2x2的寫法?原因很簡單瞧挤,是為了保證與右邊的2x2保持對應(yīng)锡宋,這樣的話比較直觀,很快就能確定F(n)對應(yīng)于矩陣的哪個(gè)元素了特恬。比如這里的F(n)=Martrix[0][1]元素执俩。

矩陣求解問題轉(zhuǎn)換

此時(shí)如果把矩陣內(nèi)容看成一個(gè)元素x,那么右邊的內(nèi)容就變成了求解x的n次冪癌刽,這樣的話役首,我們可以通過計(jì)算x的n次冪來求的x的值了

  • 關(guān)于求解x的n次冪問題
    這里有兩種方式去計(jì)算x的n次冪。分別是拆分分治的方法與按位運(yùn)算取值显拜。

    分治遞歸方法如下:

    /*
     使用拆分法衡奥,又叫分治歸并算法
     由于每次計(jì)算均為減半運(yùn)算 
     所以時(shí)間復(fù)雜度O(logn)
     */
    func powSplit(_ x: Int, _ n: Int) -> Int {
        if n == 0 {
            return 1
        }
        if n == 1 {
            return x
        }
        //偶數(shù)
        if n & 1 == 0 {
            return self.powSplit(x, n/2) * self.powSplit(x, n/2)
        }
        //奇數(shù)
        return self.powSplit(x, (n-1)/2) * self.powSplit(x, (n-1)/2) * x
    }


采用按位取值的方法如下:

func powByByte(_ x: Int, _ n: Int) -> Int {
        if x == 0 {
            return 0
        }
        if x == 1 {
            return x
        }
        var localN = n
        var localX = x
        var result = 1
        while localN != 0 {
            //當(dāng)前位有效,乘以權(quán)值
            if localN & 1 != 0 {
                result  = localX * result
            }
            //移位之前要按位加權(quán)
            localX = (localX * localX)
            localN = localN>>1
        }
        return result
    }

這里特別注意的一點(diǎn)是加權(quán)的時(shí)候远荠,需要當(dāng)前值的平方操作杰赛,如果按照二進(jìn)制進(jìn)行排列的話,當(dāng)前位置的平方是下一位權(quán)重的權(quán)值內(nèi)容矮台。例如:


image.png

所以這里要乘以當(dāng)前位的自身值乏屯,來確定下一個(gè)位的權(quán)重值內(nèi)容。將矩陣看成冪乘積之后瘦赫,會(huì)發(fā)現(xiàn)關(guān)于矩陣乘積的方法又涉及到矩陣相乘的問題辰晕。

矩陣乘法

矩陣乘法的概念這里就不多說了,直接看一下代碼實(shí)現(xiàn)确虱。

    /*
     矩陣乘法算法
     */
    func MartrixMutiply(_ leftMartrix: [[Int]], _ rightMartrix: [[Int]]) -> [[Int]] {
        var resMartrix = [[Int]]()
        var rowArr = [Int]()
        for row in 0..<leftMartrix.count{
            for col in 0..<rightMartrix[0].count{
                rowArr.append(self.countElementIndex(leftMartrix, row, rightMartrix, col))
            }
            resMartrix.append(rowArr)
            rowArr.removeAll()
        }
        return resMartrix
    }
    
    /*
     計(jì)算某行元素與某一列元素的乘積和
     */
    func countElementIndex(_ leftArray: [[Int]], _ rowIndex: Int, _ rightArray: [[Int]], _ colIndex: Int ) -> Int {
        
        //要符合兩個(gè)矩陣相乘的前提
        guard leftArray.count == rightArray[0].count else {
            return -1
        }
        
        var result = 0
        for index in 0..<leftArray.count {
            result = result + leftArray[rowIndex][index] * rightArray[index][colIndex]
        }
        
        return result
    }

最終我們通過將矩陣看成一個(gè)整體含友,對其內(nèi)容的求解轉(zhuǎn)變?yōu)榍蠼鈞的n次方的問題,所以可以實(shí)現(xiàn)如下的代碼:

    /*
     升冪運(yùn)算校辩,依據(jù)矩陣推倒公式窘问,相關(guān)的算法例如求解x的n次冪:x^n
     */
    func powMartrix(_ x: [[Int]], _ n: Int) -> [[Int]] {
        //先變成單位矩陣
        var result = [[1,0],[0,1]]
        var localN = n
        var localX = x
        while localN != 0 {
            if localN & 1 != 0{
                result = self.MartrixMutiply(localX, result)
            }
            localX = self.MartrixMutiply(localX, localX)
            localN = localN >> 1
        }
        
        return result
    }

依據(jù)公式F(n)對應(yīng)位置剛好在Martrix[0][1]的位置,所以直接返回當(dāng)前元素的值也即可得出F(n)的結(jié)果宜咒。如下:

    /*
     測試求值
     */
    func fibTestDemo(_ n: Int) -> Int {
        let res = self.fibMatrix(n)
        return res[0][1]
    }

利用矩陣來完成斐波那契的問題惠赫,是目前所能發(fā)現(xiàn)的算法的最優(yōu)解,時(shí)間復(fù)雜度位O(logn)故黑,這樣的優(yōu)質(zhì)解法我覺得還是有必要掌握一下的儿咱,畢竟屬于基本算法的范疇,值得深思與思考场晶,更能夠加強(qiáng)我們分析問題的能力混埠,這也是眾多公司要求開發(fā)者理解并且會(huì)適當(dāng)?shù)氖褂盟惴ǖ脑虬桑傊幔刻爝M(jìn)步一點(diǎn)點(diǎn)钳宪,何樂而不為呢~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扳炬,隨后出現(xiàn)的幾起案子吏颖,更是在濱河造成了極大的恐慌,老刑警劉巖鞠柄,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侦高,死亡現(xiàn)場離奇詭異,居然都是意外死亡厌杜,警方通過查閱死者的電腦和手機(jī)奉呛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夯尽,“玉大人瞧壮,你說我怎么就攤上這事〕孜眨” “怎么了咆槽?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長圈纺。 經(jīng)常有香客問我秦忿,道長麦射,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任灯谣,我火速辦了婚禮潜秋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胎许。我一直安慰自己峻呛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布辜窑。 她就那樣靜靜地躺著钩述,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穆碎。 梳的紋絲不亂的頭發(fā)上牙勘,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天赞季,我揣著相機(jī)與錄音忘伞,去河邊找鬼。 笑死疲酌,一個(gè)胖子當(dāng)著我的面吹牛北秽,可吹牛的內(nèi)容都是我干的葡幸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贺氓,長吁一口氣:“原來是場噩夢啊……” “哼蔚叨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辙培,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蔑水,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扬蕊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搀别,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年尾抑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歇父。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡再愈,死狀恐怖榜苫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翎冲,我是刑警寧澤垂睬,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響驹饺,放射性物質(zhì)發(fā)生泄漏钳枕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一逻淌、第九天 我趴在偏房一處隱蔽的房頂上張望么伯。 院中可真熱鬧,春花似錦卡儒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至欣舵,卻和暖如春擎鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缘圈。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工劣光, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糟把。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓绢涡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遣疯。 傳聞我的和親對象是個(gè)殘疾皇子雄可,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345