(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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值