动态规划--01背包问题

229 阅读2分钟

动态规划的原理

  • 把大问题拆分成小问题, 通过寻找大问题与小问题的递推关系,解决一个个小问题, 最终达到解决原问题的效果。
  • 动态规划通过填写表的方式把已解决的子问题答案记录下来, 在父问题里需要用到子问题时可以直接提取子问题结果, 避免重复计算, 从而节约了时间。 用动态规划解决问题的核心在于填表, 填表完毕,最优解就找到了

背包问题的解决过程


capacitycapacity表示背包容量,
ViV_i表示第ii个物品的价值,
WiW_i为第ii个物品的占据背包的容量,
CV(i,j)CV(i,j)为当前背包容量为j时,前i个物品的最佳组合的价值,
XiX_i ::Xi=0X_i = 0时,表示第i个物品不装入背包, 当Xi=1X_i = 1时表示第i个物品装入背包
则问题的数据模型为

  1. max(i=0nViXi)max(\sum_{i=0}^n V_iX_i)
  2. 约束条件i=0nWiXi<capacity\sum_{i=0}^n W_iX_i < capacity
  3. 递推关系, 面对物品有两种可能性:
    (1) 背包剩余容量装不下当前物品, 此时背包中的物品的价值没变即CV(i,j)=CV(i1,j)CV(i,j) = CV(i-1,j)
    (2) 背包剩余容量能装下当前物品, 此时会遇到一个问题, 背包装下当前物品能否达到最优价值, 所以在装与不装之间选择最优答案,即CV(i,j)=max(CV(i1,j),CV(i1,jWi)+Vi)CV(i,j) = max(CV(i-1,j),CV(i-1,j-W_i)+V_i)

其中
CV(i1,j)CV(i-1,j)表示不装当前物品,
CV(i1,jWi)+ViCV(i-1,j-W_i)+V_i表示装下当前物品,使用了背包WiW_i的容量,同时背包中的价值增加了ViV_i

例题

有3个物品, 物品的价值、重量和标号相同,有一个背包最大装载重量为4,如何让背包里装入的物品具有最大的价值总和

ii (物品编号)123
WW(重量)123
VV(价值)123

第一步: 画表

顺便初始化边界

i/ji/j01234
000000
10
20
30

第二步: 填表

i/ji/j01234
000000
101111
201233
301234

步骤:
从左到右,从上到下

  1. 当前物品编号为ii,重量为WiW_i,价值为ViV_i, 当前背包容量为jj
  2. j<Wij<W_i,则 CV(i,j)=CV(i1,j)CV(i,j) = CV(i-1,j) (装不下)
    否则CV(i,j)=max(CV(i1,j),CV(i1,jWi)+Vi)CV(i,j) = max(CV(i-1,j),CV(i-1,j-W_i)+V_i)

第三步: 得出结论

CV(3,4)=4CV(3,4) = 4

go语言实现

package main

import (
	"fmt"
)

func max(a int, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func CV() int {
	// 物品占用背包容量
	w := []int{0, 1, 2, 3}
	// 物品价值
	v := []int{0, 1, 2, 3}
	// 背包空间
	capacity := 4
	// 动态规划矩阵(表格)
	dp := [4][5]int{{0}}
	for i := 1; i < len(w); i++ {
		for j := 1; j <= capacity; j++ {
			if j < w[i] {
				// 当前背包空间小于物品占用背包容量
				// 则无法装入背包, 当前背包内物品价值等于前i-1个物品
				dp[i][j] = dp[i-1][j]
			} else {
				// 如果能装进背包
				// 则取最优解
				dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
			}
		}
	}
	return dp[3][4]
}

func main() {
	fmt.Print(CV())
}

LeetCode

322. 零钱兑换

状态转移方程 设ii为总金额, F(i)F(i)为金额为ii时需要的硬币数
F(i)=mini=1...n(F(iCi))+1F(i) = min_{i=1...n}(F(i - C_i)) + 1

func min(arr ...int) int {
	minInt := 0x0f0f0f
	for i := 0; i < len(arr); i++ {
		if minInt > arr[i] {
			minInt = arr[i]
		}
	}
	return minInt
}

func coinChange(coins []int, amount int) int {
	// dp[i] 表示 面额为i是使用的硬币数
	dp := make([]int, amount+1)
	for i := 0; i < len(dp); i++ {
		dp[i] = amount + 1
	}

	dp[0] = 0
	for i := 1; i <= amount; i++ {
		for j := 0; j < len(coins); j++ {
			if coins[j] <= i {
				// 如果硬币面额小于当前总金额
				// 则在取或不取中得到最优解
				dp[i] = min(dp[i], dp[i-coins[j]]+1)
			}
		}

	}
	if dp[amount] > amount {
		return -1
	}
	return dp[amount]
}