關於斐波那契數列求值問題

斐波那契數列

斐波那契數列是一個經典的數學問題,同時也是算法中的經典案例,並且衍生出了很多類似的問題,這個問題簡單來說就是當前數列的元素是由前兩個數的和構成。不如舉個栗子:

比如 F(0) = 0, F(1) = 1, 那麼 F(2) = F(0) + F(1),也就是說F(2) = 0 + 1 = 2,依次循環得出相關的數列內容。

所以依據這樣的規律特點,我們可以寫出下面的遞推內容:

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

遞歸解法

這就明顯感覺是迭代求值的關係,所以依據這樣的特點,可以採用最基本的遞歸的方法去完成求值。實現內容如下:

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

但是仔細看一下當前的時間複雜度,會發現是O(2^n)的,效率略微有點差。

正推法求值

遞歸的方法求值實際上是從n往0,反向遞推求其值,但是這樣有一個不好的地方在於重複計算了已經算過的值,例如在求解F(5)的時候內容如下:

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

再去求解F(4)的時候,你會發現F(3)重複計算了兩次,如下:

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

按照此內容推下去,計算就會增倍了。如果按照正向遞推的方式的話,就剛好解決了這樣的問題,在求解F(5)的時候已經正向求解F(4)與F(3)的結果了,所以直接累加即可得其結果。所以按照這樣的思路,我們可以正向遞推求值:

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
    }

這個時間複雜度是O(n)的。

矩陣乘法

真正的O(n)就是最優解了嘛?答案是否定的,因爲有個神級的解法,叫做升維跨越,可以將其時間複雜度變成O(logn)的,具體做法就是利用矩陣乘法。

矩陣推理

所以經過一系列的推倒,矩陣變換成了如下的內容:


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

這裏解釋一下爲什麼要將左邊的格式改成2x2的寫法?原因很簡單,是爲了保證與右邊的2x2保持對應,這樣的話比較直觀,很快就能確定F(n)對應於矩陣的哪個元素了。比如這裏的F(n)=Martrix[0][1]元素。

矩陣求解問題轉換

此時如果把矩陣內容看成一個元素x,那麼右邊的內容就變成了求解x的n次冪,這樣的話,我們可以通過計算x的n次冪來求的x的值了

  • 關於求解x的n次冪問題
    這裏有兩種方式去計算x的n次冪。分別是拆分分治的方法與按位運算取值。

    分治遞歸方法如下:

    /*
     使用拆分法,又叫分治歸併算法
     由於每次計算均爲減半運算 
     所以時間複雜度O(logn)
     */
    func powSplit(_ x: Int, _ n: Int) -> Int {
        if n == 0 {
            return 1
        }
        if n == 1 {
            return x
        }
        //偶數
        if n & 1 == 0 {
            return self.powSplit(x, n/2) * self.powSplit(x, n/2)
        }
        //奇數
        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 {
            //當前位有效,乘以權值
            if localN & 1 != 0 {
                result  = localX * result
            }
            //移位之前要按位加權
            localX = (localX * localX)
            localN = localN>>1
        }
        return result
    }

這裏特別注意的一點是加權的時候,需要當前值的平方操作,如果按照二進制進行排列的話,當前位置的平方是下一位權重的權值內容。例如:



所以這裏要乘以當前位的自身值,來確定下一個位的權重值內容。將矩陣看成冪乘積之後,會發現關於矩陣乘積的方法又涉及到矩陣相乘的問題。

矩陣乘法

矩陣乘法的概念這裏就不多說了,直接看一下代碼實現。

    /*
     矩陣乘法算法
     */
    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
    }
    
    /*
     計算某行元素與某一列元素的乘積和
     */
    func countElementIndex(_ leftArray: [[Int]], _ rowIndex: Int, _ rightArray: [[Int]], _ colIndex: Int ) -> Int {
        
        //要符合兩個矩陣相乘的前提
        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
    }

最終我們通過將矩陣看成一個整體,對其內容的求解轉變爲求解x的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
    }

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

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

利用矩陣來完成斐波那契的問題,是目前所能發現的算法的最優解,時間複雜度位O(logn),這樣的優質解法我覺得還是有必要掌握一下的,畢竟屬於基本算法的範疇,值得深思與思考,更能夠加強我們分析問題的能力,這也是衆多公司要求開發者理解並且會適當的使用算法的原因吧,總之,每天進步一點點,何樂而不爲呢~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章