关于斐波那契数列求值问题

斐波那契数列

斐波那契数列是一个经典的数学问题,同时也是算法中的经典案例,并且衍生出了很多类似的问题,这个问题简单来说就是当前数列的元素是由前两个数的和构成。不如举个栗子:

比如 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
    }

这里特别注意的一点是加权的时候,需要当前值的平方操作,如果按照二进制进行排列的话,当前位置的平方是下一位权重的权值内容。例如:


image.png

所以这里要乘以当前位的自身值,来确定下一个位的权重值内容。将矩阵看成幂乘积之后,会发现关于矩阵乘积的方法又涉及到矩阵相乘的问题。

矩阵乘法

矩阵乘法的概念这里就不多说了,直接看一下代码实现。

    /*
     矩阵乘法算法
     */
    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),这样的优质解法我觉得还是有必要掌握一下的,毕竟属于基本算法的范畴,值得深思与思考,更能够加强我们分析问题的能力,这也是众多公司要求开发者理解并且会适当的使用算法的原因吧,总之,每天进步一点点,何乐而不为呢~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353