斐波那契数列

Posted on By ᵇᵒ

在数学中,斐波那契数列是其中每个元素都是其前面的两个元素之和的序列。斐波那契数列中的数字称为斐波那契数,通常表示为 Fₙ。许多作者从 0 和 1 开始序列,也有些作者从 1 和 1 开始,还有些作者从 1 和 2 开始。这里我们也从 0 和 1 开始。

递归

fun fib(n: Int): Int = if (n < 2) n else fib(n - 1) + fib(n - 2)

递归的代码简洁明了,但是存在大量重复计算,复杂度指数级别,运行效率极其低下。

尾递归

fun fib(n: Int, a: Int = 0, b: Int = 1): Int = if (n == 0) a else fib(n - 1, b, a + b)

尾递归一般比较难想到,主要是平时能见到的应用不多,更多的是为后续转化为循环迭代做准备,比如 kotlin 里使用 tailrec 标记尾递归可以被编译器自动优化成循环。

迭代-数组

fun fib(n: Int): Int {
    val dp = IntArray(n + 1) { it }
    for (i in 2..n) dp[i] = dp[i - 1] + dp[i - 2]
    return dp.last()
}

这大概是大多数人能想到的优化版本。

迭代-临时变量

fun fib(n: Int): Int {
    var a = 0
    var b = 1
    repeat(n) { b += a.apply { a = b } }
    return a
}

使用临时变量替代数组,降低空间复杂度。

通项公式

\[F_n = \tfrac{1}{\sqrt{5}}\Big [(\tfrac{1 + \sqrt{5}}{2})^n - (\tfrac{1 - \sqrt{5}}{2})^n\Big ]\]

数学理论上通项公式最快,复杂度 O(1)。
但在实际计算机工程应用中,浮点数计算的复杂度为 O(logn),且浮点数存在运算精度问题。

矩阵快速幂

import kotlin.system.measureTimeMillis

class Matrix(vararg elements: IntArray) {
    private val arr: Array<out IntArray> = elements.map { it.copyOf() }.toTypedArray()

    val rows = arr.size
    val cols = if (arr.isNotEmpty()) arr[0].size else 0

    fun set(row: Int, col: Int, value: Int) {
        require(row in 0..<rows && col in 0..<cols) { "Index out of bounds: ($row, $col)" }
        arr[row][col] = value
    }

    fun get(row: Int, col: Int): Int {
        require(row in 0..<rows && col in 0..<cols) { "Index out of bounds: ($row, $col)" }
        return arr[row][col]
    }

    operator fun times(m: Matrix): Matrix {
        require(cols == m.rows) { "Matrix dimensions do not match for multiplication" }
        val product = Matrix(*Array(rows) { IntArray(m.cols) })
        repeat(rows) { r ->
            repeat(m.cols) { c ->
                product.set(r, c, (0..<cols).fold(0) { acc, i -> acc + this.get(r, i) * m.get(i, c) })
            }
        }
        return product
    }
}

fun power(matrix: Matrix, exp: Int): Matrix {
    if (exp == 1) return matrix
    val half = power(matrix, exp / 2)
    return if (exp % 2 == 0) half * half else half * half * matrix
}

fun fib(n: Int): Int {
    if (n < 2) return n
    val init = Matrix(intArrayOf(1), intArrayOf(0))
    val eigen = Matrix(intArrayOf(1, 1), intArrayOf(1, 0))
    // return ((2..<n).fold(eigen) { matrix, _ -> matrix * eigen } * init).get(0, 0)
    return (power(eigen, n - 1) * init).get(0, 0)
}

fun main() {
    val n = 1_000_000
    val time = measureTimeMillis {
        val result = fib(n)
        println("Fibonacci($n) = $result")
    }
    println("Computation time: $time ms")
}

欢迎来到线性代数的世界,显然:

\[\left\{ \begin{array}{l} F_2 = F_1 + F_0 \\ F_1 = F_1 \end{array} \right.\]

使用矩阵表示上述等式关系:

\[\bigg [ \begin{matrix} F_2 \\ F_1 \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ] \times \bigg [ \begin{matrix} F_1 \\ F_0 \end{matrix} \bigg ]\]

那么:

\[\bigg [ \begin{matrix} F_3 \\ F_2 \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ] \times \bigg [ \begin{matrix} F_2 \\ F_1 \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ]^2 \times \bigg [ \begin{matrix} F_1 \\ F_0 \end{matrix} \bigg ]\]

一般地:

\[\bigg [ \begin{matrix} F_n \\ F_{n-1} \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ] \times \bigg [ \begin{matrix} F_{n-1} \\ F_{n-2} \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ]^{n-1} \times \bigg [ \begin{matrix} F_1 \\ F_0 \end{matrix} \bigg ] = \bigg [ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \bigg ]^{n-1} \times \bigg [ \begin{matrix} 1 \\ 0 \end{matrix} \bigg ]\]

斐波那契数列增长很快,上面的 Int 版本很容易整数溢出,即便是 Long 类型也在 n=562 时就溢出了,有需要的可以使用 BigInteger 来实现。

卢卡斯数列

占坑

齐肯多夫定理

坑 2

斐波那契编码

坑 3