斐波那契数列的最优算法(golang代码)

一、定义
       斐波那契数列(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倍,所以数学的力量是无穷的!
  


   

     

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值