Go算法

(27)Go记忆化搜索和动态规划

2019-05-15  本文已影响0人  哥斯拉啊啊啊哦

了解记忆化搜索和动态规划前,先看一个普通的递归函数实现斐波那契数列的例子


上图,斐波那契数列的定义

// 普通递归函数的实现方法
func fib(n int) int {
    if n == 0 {
        return 0
    }
    if n == 1 {
        return 1
    }
    return fib(n-1) + fib(n-2)
}
该实现方法不好的地方是存在大量重复运算,用求第5个斐波那契数的图示说明,如下图:
如上图示,浪费太多资源做重复的事情,很自然就会想到能不能加个缓存,将结果存储在缓存中,
下次求该值,先去缓存中寻找,找到直接返回,找不到再去计算,这种思想就是记忆化搜索
实现如下:
// 记忆化搜索优化
// 算法复杂度O(n) 自上而下解决问题
func fib2(n int) int {
    // n从零到n,共n+1个位置
    memo := make([]int, n+1)

    // 递归函数
    var f func(n int) int
    f = func(n int) int {
        sum++
        if n == 0 {
            return 0
        }

        // 缓存中能找到值,直接返回该值
        if memo[n] != 0 {
            return memo[n]
        }

        // 计算该值并将该值存储在memo[n]中,下次不用重复计算
        if n == 1 {
            memo[n] = 1
        } else {
            memo[n] = f(n-1) + f(n-2)
        }
        return memo[n]
    }

    // f(n)为第n个斐波那契数
    return f(n)
}

动态规划和记忆化搜索相反,是自下而上解决问题,定义如下:
动态规划: 将原问题拆解成若干个子问题,同时保存子问题的答案,使得每个子问题只求解一次, 
最终获得原问题的答案
具体实现:
// 动态规划:时间复杂度O(n),自下而上解决问题
func fib3(n int) int {
    if n == 0 {
        return 0
    }

    memo := make([]int, n+1) //从0到n,共n+1个元素
    memo[0] = 0
    memo[1] = 1

    for i := 2; i <= n; i++ {
        sum++
        memo[i] = memo[i-1] + memo[i-2]
    }
    return memo[n]
}
测试这3种方法性能 //
func main() {
    t := time.Now()
    fmt.Println(fib(40)) 
    fmt.Println("普通递归函数花费时间", t.Sub(time.Now()))
    fmt.Println("========")

    t2 := time.Now()
    fmt.Println(fib2(40)) 
    fmt.Println("记忆化搜索花费时间", t2.Sub(time.Now()))
    fmt.Println("========")

    t3 := time.Now()
    fmt.Println(fib3(40))
    fmt.Println("动态规划花费时间", t3.Sub(time.Now()))
}

测试结果
102334155
普通递归函数花费时间 -1.667709146s
========
102334155
记忆化搜索花费时间 -40.555µs
========
102334155
动态规划花费时间 -21.188µs

总结:普通递归函数性能很差,动态规划性能最好,记忆化搜索次之,
因为记忆化搜索在递归调用中花费更多时间

有bug欢迎指出

上一篇 下一篇

猜你喜欢

热点阅读