feat(Go-Tool): 添加求背包问题最优解三种方式(KnapsackOptimizationUtil.go)及其单测类,添加求背包问题最优解过程路径(KnapsackSearchAnswerUtil.go)及其单测类.添加背包问题的readme相关说明

This commit is contained in:
huangzj
2020-08-06 11:37:49 +08:00
parent 223baa60ba
commit c1f291ee79
11 changed files with 388 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
/*
* @Author : huangzj
* @Time : 2020/7/31 15:43
* @Description:背包道具
*/
package KnapsackOptimization
type BagItem struct {
Value int //道具价值
Weight int //道具重量
}
@@ -0,0 +1,101 @@
/*
* @Author : huangzj
* @Time : 2020/7/31 15:35
* @Description:背包最优问题工具类
* 基本场景:给定n个重量为w1w 1w2w 2 ,w3w 3 ,…,wnw n ,价值为v1v 1 ,v2v 2 ,v3v 3 ,…,vnv n 的物品和容量为CC的背包,求这个物品中一个最有价值的子集,使得在满足背包的容量的前提下,包内的总价值最大
* 参考地址:https://blog.csdn.net/chanmufeng/article/details/82955730
*/
package KnapsackOptimization
type KnapsackOptimization struct {
BagCapacity int //背包的总容量上限
BagItemList []*BagItem //背包道具列表
ItemNum int //道具的数量
}
func NewKnapsackOptimization(bagItemList []*BagItem, bagCapacity int) *KnapsackOptimization {
if bagCapacity <= 0 {
panic("背包总容量不能小于等于0")
}
return &KnapsackOptimization{
BagCapacity: bagCapacity,
BagItemList: bagItemList,
ItemNum: len(bagItemList),
}
}
//通过递归的方式解决上述基本场景,这种方式效率比较低下
func (cmd *KnapsackOptimization) OptimizePackageByRecursion() int {
return cmd.knapsackRecursion(cmd.BagItemList, len(cmd.BagItemList)-1, cmd.BagCapacity)
}
func (cmd *KnapsackOptimization) knapsackRecursion(bagItemList []*BagItem, index, capacity int) int {
if index < 0 || capacity <= 0 {
return 0
}
res := cmd.knapsackRecursion(bagItemList, index-1, capacity) //不放第index个物品所得价值
//放第index个物品所得价值(前提是:第index个物品可以放得下)
if bagItemList[index].Weight <= capacity {
res = max(res, bagItemList[index].Value+cmd.knapsackRecursion(bagItemList, index-1, capacity-bagItemList[index].Weight))
}
return res
}
func (cmd *KnapsackOptimization) KnapsackCycle() int {
dynamicPlan := cmd.initKnapsackParam() //初始化参数,这边通过动态规划的方式处理,需要一个辅助行和辅助列
//循环所有的道具,进行动态规划二维数组的组装.
for i := 1; i <= cmd.ItemNum; i++ {
for j := 1; j <= cmd.BagCapacity; j++ {
//如果背包重量不够,则不能装下这个道具
if j < cmd.BagItemList[i].Weight {
dynamicPlan[i][j] = dynamicPlan[i-1][j]
} else {
//如果背包重量足够的话,判断装入这个道具价值更高,还是不装这个道具价值更高
dynamicPlan[i][j] = max(dynamicPlan[i-1][j], dynamicPlan[i-1][j-cmd.BagItemList[i].Weight]+cmd.BagItemList[i].Value)
}
}
}
return dynamicPlan[len(cmd.BagItemList)-1][cmd.BagCapacity-1]
}
//通过一维数组实现背包求解
func (cmd *KnapsackOptimization) KnapsackCycleSimple() int {
dynamicPlan := cmd.initKnapsackParamSimple()
for i := 1; i <= cmd.ItemNum; i++ {
for j := cmd.BagCapacity; j >= cmd.BagItemList[i].Weight; j-- {
dynamicPlan[j] = max(dynamicPlan[j], dynamicPlan[j-cmd.BagItemList[i].Weight]+cmd.BagItemList[i].Value)
}
}
return dynamicPlan[cmd.BagCapacity]
}
func (cmd *KnapsackOptimization) initKnapsackParamSimple() []int {
itemList := make([]*BagItem, 0)
itemList = append(itemList, &BagItem{})
itemList = append(itemList, cmd.BagItemList...)
cmd.BagItemList = itemList
return make([]int, cmd.BagCapacity+1)
}
//这里需要组成一个辅助数组去判断动态规划的实现
func (cmd *KnapsackOptimization) initKnapsackParam() [][]int {
itemList := make([]*BagItem, 0)
itemList = append(itemList, &BagItem{})
itemList = append(itemList, cmd.BagItemList...)
cmd.BagItemList = itemList
//初始化数组
dynamicPlan := make([][]int, len(cmd.BagItemList))
for i := 0; i <= cmd.ItemNum; i++ {
dynamicPlan[i] = make([]int, cmd.BagCapacity+1)
}
return dynamicPlan
}
func max(compareNum, otherCompareNum int) int {
if compareNum > otherCompareNum {
return compareNum
}
return otherCompareNum
}
@@ -0,0 +1,111 @@
/*
* @Author : huangzj
* @Time : 2020/8/5 11:50
* @Description
*/
package KnapsackOptimization
import "fmt"
type KnapsackSearchAnswer struct {
BagCapacity int //背包的总容量上限
BagItemList []*BagItem //背包道具列表
ItemNum int //道具的数量
bestPath []int //最佳路径(怎么存放价值最高)
currentBestPath []int //最佳路径(当前解)
bestValue []int //每个容量最大价值
currentValue int //当前价值
currentWeight int //当前重量
lastBestValue int //目前装载的最优价值
surplusValue int //剩余道具价值
}
func NewKnapsackSearchAnswer(bagItemList []*BagItem, bagCapacity int) *KnapsackSearchAnswer {
if bagCapacity <= 0 {
panic("背包总容量不能小于等于0")
}
answer := &KnapsackSearchAnswer{
BagCapacity: bagCapacity,
BagItemList: bagItemList,
ItemNum: len(bagItemList),
bestPath: make([]int, len(bagItemList)+1),
currentBestPath: make([]int, len(bagItemList)+1),
bestValue: make([]int, len(bagItemList)+1),
}
//初始化剩余道具价值等于所有道具总价值
for _, r := range bagItemList {
answer.surplusValue = answer.surplusValue + r.Value
}
return answer
}
//回溯法计算最佳路径和最大达到价值
func (cmd *KnapsackSearchAnswer) DynamicPlanForBestValue() {
cmd.initParam() //初始化第一个数组为空道具
cmd.BackTrack(1) //从第一个道具开始进行路径搜索
}
func (cmd *KnapsackSearchAnswer) BackTrack(step int) {
//遍历到最后一个道具
if step > cmd.ItemNum {
//如果当前总价值大于上一次装载的最佳价值
if cmd.currentValue > cmd.lastBestValue {
//最终的最佳路径用当前最佳路径来替换
for i := 1; i <= cmd.ItemNum; i++ {
cmd.bestPath[i] = cmd.currentBestPath[i]
}
cmd.lastBestValue = cmd.currentValue
return
}
}
//装入该道具之后,剩余的价值要减去当前道具的价值
cmd.surplusValue = cmd.surplusValue - cmd.BagItemList[step].Value
//当前道具的总重量没有超重
if cmd.currentWeight+cmd.BagItemList[step].Weight <= cmd.BagCapacity {
cmd.currentBestPath[step] = 1 //当前道具选中了,设置为1
cmd.currentValue = cmd.currentValue + cmd.BagItemList[step].Value //计算当前价值
cmd.currentWeight = cmd.currentWeight + cmd.BagItemList[step].Weight //计算当前价值
cmd.BackTrack(step + 1) //继续遍历下一个道具
cmd.currentValue = cmd.currentValue - cmd.BagItemList[step].Value //遍历完成要恢复成原来的价值
cmd.currentWeight = cmd.currentWeight - cmd.BagItemList[step].Weight //遍历完成要恢复成原来的重量
}
//这边是剪枝的操作,如果剩余价值加上当前价值会超过上一次的最佳价值,那么就要往右子树遍历,否则不操作.
if cmd.surplusValue+cmd.currentValue > cmd.lastBestValue {
cmd.currentBestPath[step] = 0 //当前路径置为0,不选择该位置
cmd.BackTrack(step + 1)
}
cmd.surplusValue = cmd.surplusValue + cmd.BagItemList[step].Value
}
//输出最佳路径解
func (cmd *KnapsackSearchAnswer) PrintBestSearchPath() {
for index, r := range cmd.bestPath {
if r == 1 {
fmt.Print(" ")
fmt.Println(fmt.Sprintf("道具的价值是:%d,道具的重量是%d", cmd.BagItemList[index].Value, cmd.BagItemList[index].Weight))
}
}
}
func (cmd *KnapsackSearchAnswer) initParam() {
itemList := make([]*BagItem, 0)
itemList = append(itemList, &BagItem{})
itemList = append(itemList, cmd.BagItemList...)
cmd.BagItemList = itemList
}
func (cmd *KnapsackSearchAnswer) BestValue() int {
return cmd.lastBestValue
}
+62
View File
@@ -0,0 +1,62 @@
参考地址:
[0-1背包问题的动态规划算法](https://zhuanlan.zhihu.com/p/30959069)
[01背包问题 图解+详细解析 (转载)](https://blog.csdn.net/qq_37767455/article/details/99086678)
[背包九讲——全篇详细理解与代码实现](https://blog.csdn.net/yandaoqiusheng/article/details/84782655/)
### 1、通过递归的方式实现
递归的理解:
理解这边的参数 bagItemList代表所有的物品(包含重量和价值) 、 index表示当前要装入背包的物品的位置 -- 第一二个参数组成了当前要装入背包的物品信息
第三个参数表示 当前剩余的背包容量,也就是说对于当前index位置的物品来说,此时还有多少容量剩余.
结束条件是 所有物品都尝试装入完毕 或者 当前容量小于0,没办法再装入其他道具.
算法的整个过程表现为:尝试装入第index位置的道具,当前容量是减去之前装入道具重量后剩余的容量
假设我们这么理解,从最后一个道具去装(本质上没有顺序),这个时候背包容量是原始容量.如果道具重量非常大,不满足背包容量,则这个道具压根没办法装入,不管他.
这边有两种情况,一个是装入index位置的道具,一个是不装入index位置的道具.
不装index位置的道具,则这个时候容量是背包最大的容量,如果装入index位置的道具,则背包容量变小,留给index-1位置的容量就是 (背包容量 - index位置重量)
对于不装index位置的道具,最大价值是前index-1个道具的最优解,而装入index位置的道具,拿到的就是前index个道具的最优解
以此类推。我们取这两个值的max,就可以保证递归回来的时候拿到的永远是最优解.
![](/.readme_images/递归解背包问题.jpg)
### 2、通过动态规划的方式实现
通过动态规划的方式来解答背包问题,这边需要在背包道具初始化的时候去创建一个辅助数组
也就是说数组的展示方式是按照下面的方式进行处理的。
也就是说这边的横坐标是背包的容量。
纵坐标是当前拥有的道具的数量,道具按照顺序去增加,也就是说当道具数量为1的时候,
这个时候拥有第一个道具,道具数量为2的时候,这个时候拥有第二个道具,
这么处理是为了实现当容量在一定数量的情况下,拥有n个道具能达到的最优解是多少。
也就是我们在递归里面看到的,index位置的道具比较的是
max(前index-1个道具的最优解,index位置的道具 + 减去index位置重量的前index-1个道具的最优解)
![](/.readme_images/动态规划解背包问题.png)
### 3、动态规划(一维数组简化空间复杂度)
一维数组实现背包求解同样需要辅助列,也就是当背包容量为0,作为辅助列,数组的长度是背包容量+1,分表表示容量为0、1、2、3...最大容量
从第一个道具开始到最后一个道具计算,每次分成两种情况,一个是装入该道具,一个是不装入该道具.
装入该道具的时候:计算的总价值是 当前道具价值 + List[当前容量 - 道具重量]
不装入该道具的时候:计算的总价值就是原来数组的价值,即 List[当前容量]
从第一个道具开始,一旦小于该道具价值,表示道具没办法装入该容量的背包的时候,停止计算。第二个道具在计算的时候同样是分成两种情况,
计算得到的值就是第一个道具放入与不放入两种情况中的最优解,同理一直到最后一个道具为止
计算结束,返回的List[背包总容量]表示的就是在背包总容量的情况下的最优解
这种方式虽然简化了存储空间,但是也因为覆盖对应的数据,导致如果要查找对应的道具最优解路径没办法完整还原。
![](/.readme_images/动态规划解背包问题(优化数组).jpg)
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB