diff --git a/readme.md b/readme.md index f6539ed..144f3a9 100644 --- a/readme.md +++ b/readme.md @@ -30,6 +30,15 @@ 2020/7/13: 修改FileUtil的报错 +2020/7/20: 添加深度优先搜索算法工具类以及变种,分别对应以下场景 + +- 在N个道具中,提取所有M个道具的组合方案,这种方式可以用在 权重一样的 N个道具中选择一定的数量的道具组合(这种方案就是权重一样) +- 在N个道具中,提取超过一定价值V的所有组合方案,道具的数量没有限制,但是必须超过最低价值,也就是我们说的保底方案 +- 在N个道具中,提取超过一定价值VMin,但是不超过一定价值VMax的组合方案,道具的数量没有限制,必须在最低价值和最高价值之间. +- 在N个道具中,提取超过一定价值V并且个数大小为M的所有组合方案,这个就是保证 保底的一定数量的道具 + +类似上面的方案,在游戏中可以在活动创建的时候,初始化所有的方案放到redis,然后每一次抽取都筛选下标来确认方案.这样就可以保证在一定的范围或者数量内确认对应的方案。 + # mod vendor模式加载包 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到 diff --git a/test/deepSearchTest/DeepSearch_test.go b/test/deepSearchTest/DeepSearch_test.go new file mode 100644 index 0000000..bdbb213 --- /dev/null +++ b/test/deepSearchTest/DeepSearch_test.go @@ -0,0 +1,128 @@ +/* + * @Author : huangzj + * @Time : 2020/7/20 10:27 + * @Description: + */ + +package deepSearchTest + +import ( + "Go-Tool/util/deepSearch" + "fmt" + "testing" +) + +func TestOriginDeepSearch(t *testing.T) { + srv := deepSearch.NewDeepSearch() + itemGroup := srv.GetItemMatchNum([]deepSearch.ItemValue{ + { + ItemId: 1, + ItemValue: 1, + }, { + ItemId: 2, + ItemValue: 2, + }, { + ItemId: 3, + ItemValue: 3, + }, { + ItemId: 4, + ItemValue: 4, + }, { + ItemId: 5, + ItemValue: 5, + }, + }, 6) + + for _, r := range itemGroup { + fmt.Println() + for _, row := range r { + fmt.Println(fmt.Sprintf("{%d %d}", row.ItemId, row.ItemValue)) + } + } +} + +func TestDeepSearch(t *testing.T) { + srv := deepSearch.NewDeepSearch() + itemGroup := srv.GetItemMatchValue([]deepSearch.ItemValue{ + { + ItemId: 1, + ItemValue: 1, + }, { + ItemId: 2, + ItemValue: 2, + }, { + ItemId: 3, + ItemValue: 3, + }, { + ItemId: 4, + ItemValue: 4, + }, { + ItemId: 5, + ItemValue: 5, + }, + }, 5) + + for _, r := range itemGroup { + fmt.Println() + for _, row := range r { + fmt.Println(fmt.Sprintf("{%d %d}", row.ItemId, row.ItemValue)) + } + } +} + +func TestDeepSearchLimitNum(t *testing.T) { + srv := deepSearch.NewDeepSearch() + itemGroup := srv.GetItemMatchValueLimitNum([]deepSearch.ItemValue{ + { + ItemId: 1, + ItemValue: 1, + }, { + ItemId: 2, + ItemValue: 2, + }, { + ItemId: 3, + ItemValue: 3, + }, { + ItemId: 4, + ItemValue: 4, + }, { + ItemId: 5, + ItemValue: 5, + }, + }, 5, 4) + + for _, r := range itemGroup { + fmt.Println() + for _, row := range r { + fmt.Println(fmt.Sprintf("{%d %d}", row.ItemId, row.ItemValue)) + } + } +} +func TestDeepSearchInSection(t *testing.T) { + srv := deepSearch.NewDeepSearch() + itemGroup := srv.GetItemMatchValueInSection([]deepSearch.ItemValue{ + { + ItemId: 1, + ItemValue: 1, + }, { + ItemId: 2, + ItemValue: 2, + }, { + ItemId: 3, + ItemValue: 3, + }, { + ItemId: 4, + ItemValue: 4, + }, { + ItemId: 5, + ItemValue: 5, + }, + }, 5, 6) + + for _, r := range itemGroup { + fmt.Println() + for _, row := range r { + fmt.Println(fmt.Sprintf("{%d %d}", row.ItemId, row.ItemValue)) + } + } +} diff --git a/util/deepSearch/DeepSearchUtil.go b/util/deepSearch/DeepSearchUtil.go new file mode 100644 index 0000000..0867470 --- /dev/null +++ b/util/deepSearch/DeepSearchUtil.go @@ -0,0 +1,220 @@ +/* + * @Author : huangzj + * @Time : 2020/7/19 20:46 + * @Description:深度搜索工具类,在现实问题中,用于比如一组道具,抽取一定数量,必须超过总价值多少,有几种组合方式 + * 剪枝策略,参考:https://blog.csdn.net/qq_38790716/article/details/88051412 + */ + +package deepSearch + +type DeepFirstSearch struct { + length int //道具数组长度 + vis []bool //标识 --true表示这个节点走过了,false表示这个节点没走过 + limitValue int //最小限制值(即抽中的道具需要至少最低保底多少价值) + limitMaxValue int //最大限制值(最大值区间,只用于计算在保底和区间之间的方法中,不超过) + LimitNum int //限制数字的数量(最多只能有几个数字) + sum int //总数值 + valueList []ItemValue //道具价值数组 + tempList []int //下标数组(用来存储当前对应的道具下标) + itemGroup [][]ItemValue //最终的组合数组 +} + +func NewDeepSearch() *DeepFirstSearch { + return &DeepFirstSearch{ + sum: 0, + } +} + +//计算n个道具的所有组合,这个是最原始的深度优先搜索算法 +func (srv *DeepFirstSearch) GetItemMatchNum(list []ItemValue, num int) [][]ItemValue { + //不校验数量,如果超过数组的最大数量,就是失败了.返回空数组 + + srv.length = len(list) + srv.vis = make([]bool, srv.length) + srv.valueList = list + srv.tempList = make([]int, srv.length) + srv.itemGroup = make([][]ItemValue, 0) + srv.LimitNum = num + + srv.dfs(0, 0) + + return srv.itemGroup +} + +//valueList:物品价值数组 一维数组的组成是:[编号,价值] +//limitValue:物品价值限制 +//题目可以理解为:有一个长度为n的数组valueList,从其中选择出1~n个元素,保证这些元素相加的值能够至少超过limitValue. +//找到所有对应的组合即可 +func (srv *DeepFirstSearch) GetItemMatchValue(list []ItemValue, limit int) [][]ItemValue { + //这边不校验所有的价值总和是否要高于limit,如果没有超过数组就是0,也是符合条件的. + + srv.length = len(list) + srv.vis = make([]bool, srv.length) + srv.limitValue = limit + srv.valueList = list + srv.tempList = make([]int, srv.length) + srv.itemGroup = make([][]ItemValue, 0) + + srv.dfsBeyondValue(0, 0) + + return srv.itemGroup +} + +//获取限制N个数量的保底计算方法,全面两个参数合和 #GetItemMatchValue# 一致 +func (srv *DeepFirstSearch) GetItemMatchValueLimitNum(list []ItemValue, limit int, num int) [][]ItemValue { + //不校验,没有结果返回空数组 + srv.length = len(list) + srv.vis = make([]bool, srv.length) + srv.limitValue = limit + srv.valueList = list + srv.tempList = make([]int, srv.length) + srv.itemGroup = make([][]ItemValue, 0) + srv.LimitNum = num + + srv.dfsLimitNum(0, 0) + + return srv.itemGroup +} + +func (srv *DeepFirstSearch) GetItemMatchValueInSection(list []ItemValue, min int, max int) [][]ItemValue { + //不校验,查不到就返回空数组 + srv.length = len(list) + srv.vis = make([]bool, srv.length) + srv.limitValue = min + srv.valueList = list + srv.tempList = make([]int, srv.length) + srv.itemGroup = make([][]ItemValue, 0) + srv.limitMaxValue = max + + srv.dfsInSection(0, 0) + + return srv.itemGroup +} + +func (srv *DeepFirstSearch) dfs(step int, pos int) { + //满足条件,添加组合方案 + srv.generateGroupDfs(step) + + for i := pos; i < srv.length; i++ { + //vis为false表示该位置没有被进入过 && + if srv.vis[i] == false { + srv.tempList[step] = i + srv.sum = srv.sum + srv.valueList[i].ItemValue //计算总数值,这个是需要到下一个step的dfs中进行判断的 + srv.vis[i] = true //该位置已经被进入了,设置为true + + srv.dfs(step+1, i+1) //进行下一步的深度搜索 + + srv.vis[i] = false //搜索结束后返回,标识置为false,表示可以再进入 + srv.sum = srv.sum - srv.valueList[i].ItemValue //总数要减掉之前的值,才是现在的值 + } + } + return +} + +func (srv *DeepFirstSearch) dfsBeyondValue(step int, pos int) { + //满足条件,添加组合方案 + srv.generateGroup(step) + + for i := pos; i < srv.length; i++ { + //vis为false表示该位置没有被进入过 && + if srv.vis[i] == false { + srv.tempList[step] = i + srv.sum = srv.sum + srv.valueList[i].ItemValue //计算总数值,这个是需要到下一个step的dfs中进行判断的 + srv.vis[i] = true //该位置已经被进入了,设置为true + + srv.dfsBeyondValue(step+1, i+1) //进行下一步的深度搜索 + + srv.vis[i] = false //搜索结束后返回,标识置为false,表示可以再进入 + srv.sum = srv.sum - srv.valueList[i].ItemValue //总数要减掉之前的值,才是现在的值 + } + } + return +} + +func (srv *DeepFirstSearch) dfsInSection(step int, pos int) { + //满足条件,添加组合方案 + srv.generateGroupInSection(step) + + for i := pos; i < srv.length; i++ { + //vis为false表示该位置没有被进入过 && + if srv.vis[i] == false { + srv.tempList[step] = i + srv.sum = srv.sum + srv.valueList[i].ItemValue //计算总数值,这个是需要到下一个step的dfs中进行判断的 + srv.vis[i] = true //该位置已经被进入了,设置为true + + srv.dfsInSection(step+1, i+1) //进行下一步的深度搜索 + + srv.vis[i] = false //搜索结束后返回,标识置为false,表示可以再进入 + srv.sum = srv.sum - srv.valueList[i].ItemValue //总数要减掉之前的值,才是现在的值 + } + } + return +} + +func (srv *DeepFirstSearch) dfsLimitNum(step int, pos int) { + //满足条件,添加组合方案 + srv.generateGroupLimitNum(step) + + for i := pos; i < srv.length; i++ { + //vis为false表示该位置没有被进入过 && + if srv.vis[i] == false { + srv.tempList[step] = i + srv.sum = srv.sum + srv.valueList[i].ItemValue //计算总数值,这个是需要到下一个step的dfs中进行判断的 + srv.vis[i] = true //该位置已经被进入了,设置为true + + srv.dfsLimitNum(step+1, i+1) //进行下一步的深度搜索 + + srv.vis[i] = false //搜索结束后返回,标识置为false,表示可以再进入 + srv.sum = srv.sum - srv.valueList[i].ItemValue //总数要减掉之前的值,才是现在的值 + } + } + return +} + +//满足条件,添加组合方案 +func (srv *DeepFirstSearch) generateGroupDfs(step int) { + //这边不直接return是因为可能后面还有元素没进组合,这个也是允许的,所有的组合,如果直接return的话,我搜索到[1,2,3]满足就不会再去搜索[1,2,3,4]. + if step == srv.LimitNum { + itemList := make([]ItemValue, 0) + for i := 0; i < step; i++ { + itemList = append(itemList, srv.valueList[srv.tempList[i]]) + } + srv.itemGroup = append(srv.itemGroup, itemList) + } +} + +//满足条件,添加组合方案 +func (srv *DeepFirstSearch) generateGroup(step int) { + //这边不直接return是因为可能后面还有元素没进组合,这个也是允许的,所有的组合,如果直接return的话,我搜索到[1,2,3]满足就不会再去搜索[1,2,3,4]. + if srv.sum >= srv.limitValue { + itemList := make([]ItemValue, 0) + for i := 0; i < step; i++ { + itemList = append(itemList, srv.valueList[srv.tempList[i]]) + } + srv.itemGroup = append(srv.itemGroup, itemList) + } +} + +//满足条件,添加组合方案 +func (srv *DeepFirstSearch) generateGroupLimitNum(step int) { + //这边不直接return是因为可能后面还有元素没进组合,这个也是允许的,所有的组合,如果直接return的话,我搜索到[1,2,3]满足就不会再去搜索[1,2,3,4]. + if srv.sum >= srv.limitValue && step == srv.LimitNum { + itemList := make([]ItemValue, 0) + for i := 0; i < step; i++ { + itemList = append(itemList, srv.valueList[srv.tempList[i]]) + } + srv.itemGroup = append(srv.itemGroup, itemList) + } +} + +//满足条件,添加组合方案 +func (srv *DeepFirstSearch) generateGroupInSection(step int) { + //这边不直接return是因为可能后面还有元素没进组合,这个也是允许的,所有的组合,如果直接return的话,我搜索到[1,2,3]满足就不会再去搜索[1,2,3,4]. + if srv.sum >= srv.limitValue && srv.sum <= srv.limitMaxValue { + itemList := make([]ItemValue, 0) + for i := 0; i < step; i++ { + itemList = append(itemList, srv.valueList[srv.tempList[i]]) + } + srv.itemGroup = append(srv.itemGroup, itemList) + } +} diff --git a/util/deepSearch/ItemValue.go b/util/deepSearch/ItemValue.go new file mode 100644 index 0000000..1f41a8e --- /dev/null +++ b/util/deepSearch/ItemValue.go @@ -0,0 +1,12 @@ +/* + * @Author : huangzj + * @Time : 2020/7/20 9:42 + * @Description: + */ + +package deepSearch + +type ItemValue struct { + ItemId int //物品的Id + ItemValue int //物品的价值 +}