github.com/qiuhoude/go-web@v0.0.0-20220223060959-ab545e78f20d/algorithm/datastructures/dp/knapsack_problem_test.go (about) 1 package dp 2 3 import "testing" 4 5 //背包问题9讲 https://www.kancloud.cn/kancloud/pack/70125 6 7 /* 8 0-1 背包问题 求总总量最大 9 现在我们有 n 个物品,每个物品的重量不等,并且不可分割。我们现在期望选择几件物 10 品,装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大 11 12 我们可以把物品依次排列,整个问题就分解为了 n 个阶段,每个阶段 13 对应一个物品怎么选择。先对第一个物品进行处理,选择装进去或者不装进去,然后再递归地处 14 理剩下的物品 15 16 使用回溯方式 17 i 表示考察到哪个物品了 18 curW 当前背包的重量 19 limitW 重量限制 20 weight 物品列表 21 */ 22 func bag01Problem(i, cw, limitW int, weight []int, ret *int) { 23 al1Cnt++ 24 if cw == limitW || // 表示装满了 25 i == len(weight) { // 表示已经考察完所有的物品 26 if cw > *ret { //找到了 27 *ret = cw 28 } 29 return 30 } 31 32 bag01Problem(i+1, cw, limitW, weight, ret) // 选择不装第 i 个物品 33 if cw+weight[i] <= limitW { // 已经超过可以背包承受的重量的时候,就不要再装了 34 bag01Problem(i+1, cw+weight[i], limitW, weight, ret) //选择 第 i 个物品 35 } 36 } 37 38 /* 39 使用回溯方式记忆化搜索的方式,将f(i,cw) 重复的递归调用去除掉 40 f(i,cw) 建立 i,cw 的状态数组 41 */ 42 func bag01ProblemRS(limitW int, weight []int, ret *int) { 43 // 初始化state 44 state := make([][]bool, len(weight)) 45 for i := range state { 46 state[i] = make([]bool, limitW+1) 47 for j := range state[i] { 48 state[i][j] = false 49 } 50 } 51 bag01RememberSearch(0, 0, limitW, weight, ret, state) 52 } 53 54 func bag01RememberSearch(i, cw, limitW int, weight []int, ret *int, state [][]bool) { 55 al2Cnt++ 56 if cw == limitW || // 表示装满了 57 i == len(weight) { // 表示已经考察完所有的物品 58 if cw > *ret { //找到了 59 *ret = cw 60 } 61 return 62 } 63 if state[i][cw] { //之前已经访问过 64 return 65 } 66 state[i][cw] = true // 已经访问过 67 bag01RememberSearch(i+1, cw, limitW, weight, ret, state) // 不选择i 68 if cw+weight[i] <= limitW { 69 bag01RememberSearch(i+1, cw+weight[i], limitW, weight, ret, state) 70 } 71 } 72 73 /* 74 动态规划的方式 75 76 把整个求解过程分为 n 个阶段,每个阶段会决策一个物品是否放到背包中。每个物品决策 77 (放入或者不放入背包)完之后,背包中的物品的重量会有多种情况,也就是说,会达到多种不 78 同的状态,对应到递归树中,就是有很多不同的节点 79 80 把每一层重复的状态(节点)合并,只记录不同的状态,然后基于上一层的状态集合,来推 81 导下一层的状态集合。我们可以通过合并每一层重复的状态,这样就保证每一层不同状态的个数 82 都不会超过 w 个(w 表示背包的承载重量) 83 84 用一个二维数组 states[n][w+1],来记录每层可以达到的不同状态 85 86 第 0 个(下标从 0 开始编号)物品的重量是 2,要么装入背包,要么不装入背包,决策完之 87 后,会对应背包的两种状态,背包中物品的总重量是 0 或者 2。我们用 states[0][0]=true 和 88 states[0][2]=true 来表示这两种状态 89 90 第 1 个物品的重量也是 2,基于之前的背包状态,在这个物品决策完之后,不同的状态有 3 91 个,背包中物品总重量分别是 0(0+0),2(0+2 or 2+0),4(2+2)。我们用 states[1][0]=true, 92 states[1][2]=true,states[1][4]=true 来表示这三种状态 93 94 以此类推,直到考察完所有的物品后,整个 states 状态数组就都计算好了。我把整个计算的过 95 程画了出来,你可以看看。图中 0 表示 false,1 表示 true。我们只需要在最后一层,找一个值 96 为 true 的最接近 w(这里是 9)的值,就是背包中物品总重量的最大值。 97 98 然后通过当前阶段的状态集合,来推导下一个阶段的状态集合,动态地往前推进 99 100 */ 101 func knapsack01ProblemDP1(w int, weight []int) int { 102 n := len(weight) 103 // 初始化状态 104 states := make([][]bool, n) 105 for i := range states { 106 states[i] = make([]bool, w+1) 107 for j := range states[i] { 108 states[i][j] = false 109 } 110 } 111 // 第0个weight 的两种状态 112 states[0][0] = true //不放0个物品, 第一行的数据要特殊处理,可以利用哨兵优化 113 states[0][weight[0]] = true // 放第0个物品 114 115 // 动态规划状态转移 116 for i := 1; i < n; i++ { 117 for j := 0; j <= w; j++ { // 不把第 i 个物品放入背包 118 if states[i-1][j] { // 前一个物品已经放入 119 states[i][j] = states[i-1][j] 120 } 121 } 122 for j := 0; j <= w-weight[i]; j++ { // 第 i 个物品放入背包 123 if states[i-1][j] { 124 states[i][j+weight[i]] = true 125 } 126 } 127 } 128 // 结果 129 for i := w; i >= 0; i-- { 130 if states[n-1][i] { 131 return i 132 } 133 } 134 return 0 135 } 136 137 /* 138 使用 1维数组来做 139 */ 140 func knapsack01ProblemDP2(w int, weight []int) int { 141 n := len(weight) 142 // 初始化状态 143 states := make([]bool, w+1) 144 for i := range states { 145 states[i] = false 146 } 147 states[0] = true 148 states[weight[0]] = true 149 // 动态规划状态转移, 150 for i := 1; i < n; i++ { 151 //j 需要从大到小来处理。如果我们按照 j 从小到大处理的话,会出现 for 循环重复计算的问题 152 for j := w - weight[i]; j >= 0; j-- { // 第 i 个物品放入背包 153 if states[j] { 154 states[j+weight[i]] = true 155 } 156 } 157 } 158 // 结果 159 for i := w; i >= 0; i-- { 160 if states[i] { 161 return i 162 } 163 } 164 return 0 165 } 166 167 // 带价值的背包问,不能使用记忆化搜索的方式解答 168 169 // 使用回溯的方式 170 func knapsack02(i, cw, cv, limitW int, weight, value []int, ret *int) { 171 al1Cnt++ 172 if cw == limitW || // 表示装满了 173 i == len(weight) { // 表示已经考察完所有的物品 174 if cv > *ret { //找到了 175 *ret = cv 176 } 177 return 178 } 179 180 knapsack02(i+1, cw, cv, limitW, weight, value, ret) // 选择不装第 i 个物品 181 if cw+weight[i] <= limitW { // 已经超过可以背包承受的重量的时候,就不要再装了 182 knapsack02(i+1, cw+weight[i], cv+value[i], limitW, weight, value, ret) //选择 第 i 个物品 183 } 184 } 185 186 // 使用动态规划的方式 187 func knapsack02Dp(w int, weight, value []int) int { 188 n := len(weight) 189 // f(i,cw,cv) cv 就是存储的值 190 // 初始化states 191 states := make([][]int, n) 192 for i := range states { 193 states[i] = make([]int, w+1) 194 for j := range states[i] { 195 states[i][j] = -1 196 } 197 } 198 states[0][0] = 0 // 第物品放不放进背包 199 states[0][weight[0]] = value[0] // 第一放 200 for i := 1; i < n; i++ { 201 for j := 0; j <= w; j++ { // 不把第 i 个物品放入背包 202 if states[i-1][j] >= 0 { // 前一个物品已经放入 203 states[i][j] = states[i-1][j] 204 } 205 } 206 for j := 0; j <= w-weight[i]; j++ { // 第 i 个物品放入背包 207 if states[i-1][j] >= 0 { 208 // 上一val放入背包的值 + 本次背包的值 209 v := states[i-1][j] + value[i] 210 if v > states[i][j+weight[i]] { // 相同总量下选取 value值大的 211 states[i][j+weight[i]] = v 212 } 213 } 214 } 215 } 216 // 在最后找出最大值 217 maxV := 0 218 for _, v := range states[n-1] { 219 if v > maxV { 220 maxV = v 221 } 222 } 223 return maxV 224 } 225 226 var al1Cnt int = 0 227 var al2Cnt int = 0 228 229 func TestBagProblem(t *testing.T) { 230 weight := []int{2, 2, 4, 6, 3} 231 limit := 9 232 233 ret1 := 0 234 bag01Problem(0, 0, limit, weight, &ret1) 235 t.Log("maxW ret1:", ret1, "al1Cnt:", al1Cnt) 236 237 ret2 := 0 238 bag01ProblemRS(limit, weight, &ret2) 239 t.Log("maxW ret2:", ret2, "al2Cnt:", al2Cnt) 240 241 ret3 := knapsack01ProblemDP1(limit, weight) 242 t.Log("maxW ret3:", ret3) 243 244 ret4 := knapsack01ProblemDP2(limit, weight) 245 t.Log("maxW ret4:", ret4) 246 247 // 带价值的背包问题 248 value := []int{3, 4, 8, 9, 6} 249 ret5 := 0 250 knapsack02(0, 0, 0, limit, weight, value, &ret5) 251 t.Log("maxV ret5:", ret5) 252 253 ret6 := knapsack02Dp(limit, weight, value) 254 t.Log("maxV ret6:", ret6) 255 256 }