代码以及注释:
- Sub test() 'by kagawa 2017/7/24 背包算法解决分组凑数问题
- Dim ar, br, cr(), h&, k&, l&, m&, n&, r&, tms#
- tms = Timer
-
- m = [b2].End(4).Row - 1 '数据个数m
- ar = [b2].Resize(m, 3) '读取B:D 3列 分组原始数据、权重、备注信息
- br = [g2].Resize(m) '读取G列 分组目标限额数据
- ReDim cr(1 To m) '定义记录分组序号的数组cr
-
- [g1].CurrentRegion.Offset(1, 1) = ""
- r = m
- If WorksheetFunction.Count([c2].Resize(m)) = m Then l = 2 Else l = 1 '检查C列是否为权重数据
- For k = 1 To m '依次进行分组计算
-
- h = br(k, 1): If h = 0 Then h = br(k - 1, 1): br(k, 1) = h: Cells(k + 1, 7) = h
- '读取分组目标限额、如本行为空则继承上一行数值h
-
- n = GetDP(ar, cr, h, k, l) '调用背包算法 进行<=h的凑数 并返回有效组的元素个数
-
- If n = 0 Then Exit For '如找不到解则退出
-
- r = r - n '计算数据剩余个数r
- If r = 0 Then Exit For '如r=0即全部数据都已经分组完毕也退出
-
- ' [e2].Resize(m) = WorksheetFunction.Transpose(cr)'调试时输出每一步的分组结果
- Next
-
- [e2].Resize(m) = WorksheetFunction.Transpose(cr) '输出最终分组记录结果
- [a2].Resize(m, 5).Sort [e2], 1, [b2], , 1, [a2], 1, 2 '分组结果排序
- MsgBox Format(Timer - tms, "0.000s") & vbCr & "已经完成 " & k & " 个分组"
- End Sub
- Function GetDP(ar, cr, h&, k&, l&) '老窖提供生成DP_Knapsack表的算法、香川2017/07/23 修改
- Dim sj&(), DP&(), i&, j&, m&, n&, v&, v1&, r&, s1$, s2$
-
- m = 0: ReDim sj&(1 To UBound(ar), 2)
- For i = 1 To UBound(ar)
- If cr(i) = 0 Then m = m + 1: sj(m, 0) = i: sj(m, 1) = ar(i, 1): sj(m, 2) = ar(i, l)
- Next
- '以上对照扣除已分组数据,把剩余有效数据重新整理、写入sj数组
-
- ReDim DP&(m, h) '定义DP表数组
- For i = 1 To m '填写DP表
- For j = 1 To h
- If sj(i, 1) > j Then
- DP(i, j) = DP(i - 1, j) '加入后超过当前值则不可加入,直接继承上一行结果
- Else
- v1 = DP(i - 1, j - sj(i, 1)) + sj(i, l) '计算加入背包后的结果v1
- v = DP(i - 1, j) '不加入背包时的结果v
- If v1 > v Then DP(i, j) = v1 Else DP(i, j) = v '取上述两种结果中的最大值
- End If
- Next
- Next
-
- '按上述动态规划的背包算法遍历所有可能的组合得到最大值的DP数据表,据此可以得到所有解
- i = m: j = h '下面代码只选取第1种低位有效组合
- Do
- Do
- If DP(i, j) = DP(i, j - 1) Then j = j - 1 Else Exit Do '背包值相同时左移1列
- Loop Until j = 0
- Do
- If DP(i, j) = DP(i - 1, j) Then i = i - 1 Else Exit Do '背包值相同时上移1行
- Loop Until i = 1
- If DP(i, j) = 0 Then Exit Do '无解时退出
-
- '背包值不同时即可提取本组的对应元素
- n = n + 1 '记录本组使用元素个数
- cr(sj(i, 0)) = k '数组cr中记录分组序号k
- r = r + sj(i, 1) '统计本组凑数总和r
- s1 = s1 & "+" & sj(i, 1) '合并本组凑数结果字符串
- s2 = s2 & "+" & ar(sj(i, 0), 3) '合并本组信息字符串
- j = j - sj(i, 1): i = i - 1 '
- Loop Until DP(i, j) = 0 '提取完毕退出
-
- If n Then '本组有效时 输出结果到工作表
- Cells(k + 1, 8) = "=" & Mid(s1, 2) '本组凑数总和
- Cells(k + 1, 9) = h - r '本组和目标值的差额
- Cells(k + 1, 10) = Mid(s2, 2) '本组信息合并字符串
- End If
-
- GetDP = n '函数返回本组含元素个数n
- End Function
复制代码 |