diff --git a/.readme_images/动态规划解背包问题(优化数组).jpg b/.readme_images/动态规划解背包问题(优化数组).jpg new file mode 100644 index 0000000..7ba7f5c Binary files /dev/null and b/.readme_images/动态规划解背包问题(优化数组).jpg differ diff --git a/.readme_images/动态规划解背包问题.png b/.readme_images/动态规划解背包问题.png new file mode 100644 index 0000000..35d4424 Binary files /dev/null and b/.readme_images/动态规划解背包问题.png differ diff --git a/.readme_images/递归解背包问题.jpg b/.readme_images/递归解背包问题.jpg new file mode 100644 index 0000000..fe515f4 Binary files /dev/null and b/.readme_images/递归解背包问题.jpg differ diff --git a/readme.md b/readme.md index 144f3a9..5559296 100644 --- a/readme.md +++ b/readme.md @@ -39,6 +39,11 @@ 类似上面的方案,在游戏中可以在活动创建的时候,初始化所有的方案放到redis,然后每一次抽取都筛选下标来确认方案.这样就可以保证在一定的范围或者数量内确认对应的方案。 + +2020/8/6: 添加求背包问题最优解三种方式(KnapsackOptimizationUtil.go)及其单测类,添加求背包问题最优解过程路径(KnapsackSearchAnswerUtil.go)及其单测类.添加背包问题的readme相关说明 + + + # mod vendor模式加载包 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到 diff --git a/test/KnapsackOptimizationTest/KnapsackOptimizationUtil_test.go b/test/KnapsackOptimizationTest/KnapsackOptimizationUtil_test.go new file mode 100644 index 0000000..0ee4755 --- /dev/null +++ b/test/KnapsackOptimizationTest/KnapsackOptimizationUtil_test.go @@ -0,0 +1,51 @@ +/* + * @Author : huangzj + * @Time : 2020/7/31 15:35 + * @Description:背包最优问题工具类 + * 基本场景:给定n个重量为w1w 1,w2w 2 ,w3w 3 ,…,wnw n ,价值为v1v 1 ,v2v 2 ,v3v 3 ,…,vnv n 的物品和容量为CC的背包,求这个物品中一个最有价值的子集,使得在满足背包的容量的前提下,包内的总价值最大 + * 参考地址:https://blog.csdn.net/chanmufeng/article/details/82955730 + */ + +package KnapsackOptimizationTest + +import ( + "Go-Tool/util/KnapsackOptimization" + "fmt" + "testing" +) + +func TestKnapsackOptimizationUtil(t *testing.T) { + bagItem := make([]*KnapsackOptimization.BagItem, 0) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 3, + Weight: 2, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 3, + Weight: 3, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 4, + Weight: 4, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 5, + Weight: 5, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 6, + Weight: 2, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 7, + Weight: 2, + }) + cmd := KnapsackOptimization.NewKnapsackOptimization(bagItem, 12) + cmd1 := KnapsackOptimization.NewKnapsackOptimization(bagItem, 12) + cmd2 := KnapsackOptimization.NewKnapsackOptimization(bagItem, 12) + fmt.Print(cmd.OptimizePackageByRecursion()) //通过递归解背包问题 + + fmt.Println(cmd1.KnapsackCycle()) //通过逆序解背包问题. + + fmt.Println(cmd2.KnapsackCycleSimple()) //通过一维数组来解决背包问题 +} diff --git a/test/KnapsackOptimizationTest/KnapsackSearchAnswerUtil_test.go b/test/KnapsackOptimizationTest/KnapsackSearchAnswerUtil_test.go new file mode 100644 index 0000000..6a9d64c --- /dev/null +++ b/test/KnapsackOptimizationTest/KnapsackSearchAnswerUtil_test.go @@ -0,0 +1,46 @@ +/* + * @Author : huangzj + * @Time : 2020/8/5 15:40 + * @Description: + */ + +package KnapsackOptimizationTest + +import ( + "Go-Tool/util/KnapsackOptimization" + "fmt" + "testing" +) + +func TestKnapsackSearchAnswerUtil(t *testing.T) { + bagItem := make([]*KnapsackOptimization.BagItem, 0) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 3, + Weight: 2, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 3, + Weight: 3, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 4, + Weight: 4, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 5, + Weight: 5, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 6, + Weight: 2, + }) + bagItem = append(bagItem, &KnapsackOptimization.BagItem{ + Value: 7, + Weight: 2, + }) + //通过回溯解背包问题 + cmd := KnapsackOptimization.NewKnapsackSearchAnswer(bagItem, 12) + cmd.DynamicPlanForBestValue() + cmd.PrintBestSearchPath() + fmt.Println(fmt.Sprintf("最佳价值为:%d", cmd.BestValue())) +} diff --git a/util/KnapsackOptimization/BagItem.go b/util/KnapsackOptimization/BagItem.go new file mode 100644 index 0000000..8bd4d9c --- /dev/null +++ b/util/KnapsackOptimization/BagItem.go @@ -0,0 +1,12 @@ +/* + * @Author : huangzj + * @Time : 2020/7/31 15:43 + * @Description:背包道具 + */ + +package KnapsackOptimization + +type BagItem struct { + Value int //道具价值 + Weight int //道具重量 +} diff --git a/util/KnapsackOptimization/KnapsackOptimizationUtil.go b/util/KnapsackOptimization/KnapsackOptimizationUtil.go new file mode 100644 index 0000000..cfbfd56 --- /dev/null +++ b/util/KnapsackOptimization/KnapsackOptimizationUtil.go @@ -0,0 +1,101 @@ +/* + * @Author : huangzj + * @Time : 2020/7/31 15:35 + * @Description:背包最优问题工具类 + * 基本场景:给定n个重量为w1w 1,w2w 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 +} diff --git a/util/KnapsackOptimization/KnapsackSearchAnswerUtil.go b/util/KnapsackOptimization/KnapsackSearchAnswerUtil.go new file mode 100644 index 0000000..749a00f --- /dev/null +++ b/util/KnapsackOptimization/KnapsackSearchAnswerUtil.go @@ -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 +} diff --git a/util/KnapsackOptimization/readme.md b/util/KnapsackOptimization/readme.md new file mode 100644 index 0000000..6ad7930 --- /dev/null +++ b/util/KnapsackOptimization/readme.md @@ -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) diff --git a/util/KnapsackOptimization/动态规划解背包问题.png b/util/KnapsackOptimization/动态规划解背包问题.png new file mode 100644 index 0000000..35d4424 Binary files /dev/null and b/util/KnapsackOptimization/动态规划解背包问题.png differ