(29)Go动态规划经典思想-01背包问题

方法1:暴力解法 //
每件物品可以放进包,也可以不放进包,时间复杂度O((2^n)*n)

方法2:动态规划
对于每件物品,都有2种选择,放进包和不放进包,分别计算这两种情况哪种包内的价值更大,取最大值
其状态如下图

根据上图状态可知,容积c和i构成数据对,可以定义一个二维数组来存储相应价值,如下图:


func max(v1, v2 int) int {
    if v1 > v2 {
        return v1
    }
    return v2
}

方法1:记忆化搜索
// 时间复杂度O(n*c)
// 空间复杂度O(n*c)
func knapsack2(w, v []int, c int) int {
    n := len(w)
    if n == 0 {
        return 0
    }

    // (n*(c+1))二维数组
    // memo[i][j]: 将[0...i]号物品,填充容量为j的背包的最大价值
    memo := make([][]int, n)
    for i := 0; i < n; i++ {
        memo[i] = make([]int, c+1)
    }
    return bestValue2(w, v, n-1, c, n, memo)
}

// 将[0...index]号物品,填充容积为C的背包的最大价值
func bestValue2(w, v []int, index, c, n int, memo [][]int) int {
    // 递归终止条件
    if index < 0 || c < 0 {
        return 0
    }

    // 如果计算过价值,直接返回该价值
    if memo[index][c] != 0 {
        return memo[index][c]
    }

    // 计算[0...index]物品放进容量c背包的最大价值

    // 2种情况
    // 情况1: index号物品不放进背包
    res := bestValue2(w, v, index-1, c, n)

    // 情况2: 如果背包有足够的空间,index号物品放进背包
    if c >= w[index] {
        res = max(res, v[index]+bestValue1(w, v, index-1, c-w[index], n))
    }

    // 存储结果
    memo[index][c] = res
    return res
}

根据状态方程得知,memo[i]行价值依赖memo[i-1]行结果,因此可以先求0行结果,
再求1行结果,一步步扩大,最终求出[0...n-1]行所有价值
方法2:动态规划
// 时间复杂度O(n*c)
// 空间复杂度O(n*c)
func knapsack3(w, v []int, c int) int {
    n := len(w)
    if n == 0 {
        return 0
    }

    // (n*(c+1))二维数组
    // memo[i][j]: 将[0...i]号物品,填充容量为j的背包的最大价值
    memo := make([][]int, n)
    for i := 0; i < n; i++ {
        memo[i] = make([]int, c+1)
    }

    // 第0行的情况: 将[0...0]物品存进容量为c的背包
    if w[0] <= c {
        for i := w[0]; i <= c; i++ {
            memo[0][i] = v[0]
        }
    }

    // 第[1...length-1]行的情况
    for i := 1; i < n; i++ {
        // [0...i]物品,j空间
        for j := 0; j <= c; j++ {
            // 2种情况
            // 情况1: index号物品不放进背包
            memo[i][j] = memo[i-1][j]

            // 情况2: 如果背包有足够的空间,index号物品放进背包
            if j >= w[i] {
                memo[i][j] = max(memo[i][j], v[i]+memo[i-1][j-w[i]])
            }
        }
    }
    return memo[n-1][c]
}

由方法2可知:第i行元素只依赖于i-1行元素,理论上,只需要保持两行元素即可求出最后一行价值
偶数行在memo[0],奇数行在memo[1]
方法3:动态规划优化1
// 时间复杂度O(n*c)
// 空间复杂度O(2*c)=O(c)
func knapsack4(w, v []int, c int) int {
    n := len(w)
    if n == 0 {
        return 0
    }

    memo := make([][]int, 2)
    for i := 0; i < 2; i++ {
        memo[i] = make([]int, c+1)
    }

    if w[0] <= c {
        for i := w[0]; i <= c; i++ {
            memo[0][i] = v[0]
        }
    }

    for i := 1; i < n; i++ {
        i1 := i % 2       // 本行
        i2 := (i - 1) % 2 // 上一行
        for j := 0; j <= c; j++ {
            memo[i1][j] = memo[i2][j]
            if j >= w[i] {
                memo[i1][j] = max(memo[i1][j], v[i]+memo[i2][j-w[i]])
            }
        }
    }
    return memo[(n-1)%2][c]
}

由方法3可知:i行素只参考i-1行上方和左边的元素,因此可以从右向左刷新 i 行内容
可以只用一行大小为c+1的数组完成动态规划
方法4:动态规划优化2
// 时间复杂度O(n*c)
// 空间复杂度O(c+1)
func knapsack5(w, v []int, c int) int {
    length := len(w)
    if length == 0 {
        return 0
    }

    // memo[c]: 区间[0...index]的物品,填充容积c的背包的最大价值
    // 空间(c+1)
    memo := make([]int, c+1)

    // 第0行的情况: 将[0...0]物品存进容量为c的背包
    if w[0] <= c {
        for i := w[0]; i <= c; i++ {
            memo[i] = v[0]
        }
    }

    for i := 1; i < length; i++ {
        // i个物品,j空间,当j<w[i]时,小于w[i]值均不变,可以提前结束判断
        for j := c; j >= w[i]; j-- {
            memo[j] = max(memo[j], v[i]+memo[j-w[i]])
        }
    }
    return memo[c]
}
测试4种实现方法的性能:
func main() {
    w := []int{1, 2, 3}
    v := []int{6, 10, 12}
    t := time.Now()
    fmt.Println(knapsack2(w, v, 5))
    fmt.Println("记忆化搜索花费时间", t.Sub(time.Now()))
    fmt.Println("========")

    t = time.Now()
    fmt.Println(knapsack3(w, v, 5))
    fmt.Println("动态规划1花费时间", t.Sub(time.Now()))
    fmt.Println("========")

    t = time.Now()
    fmt.Println(knapsack4(w, v, 5))
    fmt.Println("动态规划2花费时间", t.Sub(time.Now()))
    fmt.Println("========")

    t = time.Now()
    fmt.Println(knapsack5(w, v, 5))
    fmt.Println("动态规划2花费时间", t.Sub(time.Now()))
}

// 测试结果
22
记忆化搜索花费时间 -102.325µs
========
22
动态规划1花费时间 -13.581µs
========
22
动态规划2花费时间 -1.596µs
========
22
动态规划2花费时间 -1.346µs

继下一篇《(30)Go动态规划背包思想求解问题》https://www.jianshu.com/p/2f32ad93ee3e

有bug欢迎指出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 题目:给定一个n种物品和 一个能装m重量的背包, 物品重量w,价值是p。问:如何才能使背包m重量 ,装最多价值的物...
    梁同桌阅读 676评论 0 0
  • 背包问题(Knapsack problem) 是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品...
    NeoAndBob阅读 3,739评论 0 3
  • 尊敬的陈云贤主席、莫言教授、各位校董、各位嘉宾、老师们、同学们: 每年参加汕大的毕业典礼,我内心总是充满喜悦。在湖...
    溯子的树洞阅读 140评论 0 1
  • 临时接电话妈妈他们今晚回老家了 不需要我明天开车 于是下班直接回家换衣服直奔ktv 跟小弟弟玩儿小游戏等下转...
    桃花花_866d阅读 153评论 0 0
  • 昨夜的雨 氤氲的空气还未散尽 鸟儿在树上啾啾的叫着 树下草坪上的男孩 一个人呆呆地坐着 看着来来往往、 稀稀疏疏的...
    顾摇阅读 160评论 4 5

友情链接更多精彩内容