斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)。
二、求解方法
1、时间和空间复杂度都为O(N)的递归算法
func Fib(n int) int {
if n == 1 || n == 2 {
return 1
}
return (Fib(n-2) + Fib(n-1))
}
这种方法是最简单的编程方法,但是由于重复计算的原因,当n>40的以后计算的就会很慢。为了提高计算效率,可以采用下面的递推方法。
2、时间复杂度为O(N),空间复杂度为O(1)的递推算法
var fibarry = [3]int{0, 1, 0}
func fibonacci(n int) int {
for i := 2; i <= n; i++ {
fibarry[2] = fibarry[0] + fibarry[1]
fibarry[0] = fibarry[1]
fibarry[1] = fibarry[2]
}
return fibarry[2]
}
这种算法避免了计算机的重复计算,而且占用内存始终为2个单位,相对上一个的递推算法简直是天壤之别,本人在2核
2.4 GHz Intel i5 处理器下计算第1000 000个斐波那契数的用时为3s。那现在有一种问题,还有没有更优的算法呢?如果从计算机的层面考虑是没有,但是数学的能力是无穷的,所以答案是肯定的!
3.时间复杂度为O(logN),空间复杂度为O(1)的矩阵递推算法
这个算法主要是根据矩阵和快速幂运算的实现的:
a、矩阵
在线性代数中把f(n)=af(n-1)+bf(n-2)这种递推式称为二阶递推式,所以的递推式都符合(f(n),f(n-1))=(f(n-1),f(n-2))*matrix,这里的matrix就是矩阵,几阶递推式就是几阶的矩阵;斐波那契数列的递推公式是f(n)=f(n-1)+f(n-2),所以斐波那契数列属于二阶。我们将n带入具体的值:
f(3)=f(2)+f(1)→(f(3),f(2))=(f(2),f(1))*(matrix)^1;
f(4)=f(3)+f(2)→(f(4),f(3))=(f(3),f(2))*(matrix)^1=(f(2),f(1))*(matrix)^2;
.....
f(n+1)=(f(2),f(1))*(matrix)^n-1→(f(n+1),f(n))=(f(2),f(1))*(matrix)^n-1=(f(1),f(0))*(matrix)^n;
根据这些公式可以算出二阶的matrix={{1,1},{1,0}};
这样就将问题转化为如何计算矩阵的n次幂了。由于golang的标准库中没有matrix库,所以自己简单的封装了一个,代码如下:
package matrix
import (
"math/big"
)
type Matrix struct {
rows, columns int // the number of rows and columns.
data []*big.Int // the contents of the matrix as one long slice.
}
// Set lets you define the value of a matrix at the given row and
// column.
func (A *Matrix) Set(r int, c int, val *big.Int) {
A.data[findIndex(r, c, A)] = val
}
// Get retrieves the contents of the matrix at the row and column.
func (A *Matrix) Get(r, c int) *big.Int {
return A.data[findIndex(r, c, A)]
}
// Column returns a slice that represents a column from the matrix.
// This works by examining each row, and adding the nth element of
// each to the column slice.
func (A *Matrix) Column(n int) []*big.Int {
col := make([]*big.Int, A.rows)
for i := 1; i <= A.rows; i++ {
col[i-1] = A.Row(i)[n-1]
}
return col
}
// Row returns a slice that represents a row from the matrix.
func (A *Matrix) Row(n int) []*big.Int {
return A.data[findIndex(n, 1, A):findIndex(n, A.columns+1, A)]
}
// Multiply multiplies two matrices together and return the resulting matrix.
// For each element of the result matrix, we get the dot product of the
// corresponding row from matrix A and column from matrix B.
func Multiply(A, B Matrix) *Matrix {
C := Zeros(A.rows, B.columns)
for r := 1; r <= C.rows; r++ {
A_row := A.Row(r)
for c := 1; c <= C.columns; c++ {
B_col := B.Column(c)
C.Set(r, c, dotProduct(A_row, B_col))
}
}
return &C
}
// Identity creates an identity matrix with n rows and n columns. When you
// multiply any matrix by its corresponding identity matrix, you get the
// original matrix. The identity matrix looks like a zero-filled matrix with
// a diagonal line of one's starting at the upper left.
func Identity(n int) Matrix {
A := Zeros(n, n)
for i := 0; i < len(A.data); i += (n + 1) {
A.data[i] = big.NewInt(1)
}
return A
}
// Zeros creates an r x c sized matrix that's filled with zeros. The initial
// state of an int is 0, so we don't have to do any initialization.
func Zeros(r, c int) Matrix {
return Matrix{r, c, make([]*big.Int, r*c)}
}
// New creates an r x c sized matrix that is filled with the provided data.
// The matrix data is represented as one long slice.
func New(r, c int, data []*big.Int) Matrix {
if len(data) != r*c {
panic("[]*big.Int data provided to matrix.New is great than the provided capacity of the matrix!'")
}
A := Zeros(r, c)
A.data = data
return A
}
// findIndex takes a row and column and returns the corresponding index
// from the underlying data slice.
func findIndex(r, c int, A *Matrix) int {
return (r-1)*A.columns + (c - 1)
}
// dotProduct calculates the algebraic dot product of two slices. This is just
// the sum of the products of corresponding elements in the slices. We use
// this when we multiply matrices together.
func dotProduct(a, b []*big.Int) *big.Int {
total := new(big.Int)
x := new(big.Int)
z := new(big.Int)
for i := 0; i < len(a); i++ {
y := x.Mul(a[i], b[i])
total = z.Add(total, y)
// total = total.Add(total, total.Mul(a[i], b[i]))
}
return total
}
b、快速幂运算
对于a^b,可将b转化为二进制,即b=b0+b1*2+b2*2^2+...+bn*2^n ,这里我们的b0对应的是b二进制的第一位,那么我们的a^b运算就可以拆解成a^b0*a^b1*2*...*a^(bn*2^n),对于b来说,二进制位不是0就是1,那么对于bx为0的项我们的计算结果是1就不用考虑了,我们真正想要的其实是b的非0二进制位,那么除去了b的0的二进制位之后我们得到的式子是a^(bx*2^x)*...*a(bn*2^n),通过这种方法,可以在O(lbn)的时间内计算出a的n次幂,这就是快速幂运算的本质所在。代码如下:
for b > 0 {
if b&1 == 1 {
s = *matrix.Multiply(s, a)
b = b >> 1
} else {
b = b >> 1
}
a = *matrix.Multiply(a, a)
}
return s
}
基于上面的矩阵和快速幂,完整代码如下:
package main
import (
"fmt"
"math/big"
"time"
"util/matrix"
)
//求矩阵的n次幂
func MatPow(a matrix.Matrix, b int) matrix.Matrix {
arr0 := [4]*big.Int{big.NewInt(1), big.NewInt(0), big.NewInt(0), big.NewInt(1)}
s := matrix.New(2, 2, arr0[0:4])
for b > 0 {
if b&1 == 1 {
s = *matrix.Multiply(s, a)
b = b >> 1
} else {
b = b >> 1
}
a = *matrix.Multiply(a, a)
}
return s
}
//矩阵的N次幂与fib(1)和Fib(0)组成的2行1列的矩阵相乘求fib(n+1)和Fib(n)组成的2行1列的矩阵
//从fib(n+1)和Fib(n)的2行1列的矩阵中取出fib(n)
func Fib(n int) *big.Int {
arr0 := [6]*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1), big.NewInt(0), big.NewInt(2), big.NewInt(1)}
k := matrix.New(2, 2, arr0[0:4])
s := MatPow(k, n)
d := matrix.New(2, 1, arr0[0:2])
s = *matrix.Multiply(s, d)
return s.Get(2, 1)
}
func main() {
start := time.Now()
n := 1000000
m := Fib(n)
fmt.Println("f(n)的结果是", m)
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
}
基于这个算法,本人在2核 2.4 GHz Intel i5 处理器下计算第1000 000个斐波那契数的用时为305ms,计算性能又提升了1000倍,所以数学的力量是无穷的!