背景:给出一组优惠券,并给出优惠券叠加关系,优惠券权重
业务规则:某个类型的券存在最多限制
需求:求出优惠券在业务规则下最优组合(权重最大的组合)
名词规定:
图的团=图的集=图的完全子图 二:业务问题转为数学问题
当存在一组优惠券时,得到最优优惠券组合。首先将业务数据抽离为算法数据可得到如下图
其中 0-5编号代表优惠券编码,优惠券之间连线代表两者是可以共同使用的。
从数据结构上考虑,可以得知:这是一个无向有环图,问题不难转化为:求出有向无环图的所有子图。
从此图来看:
所有完全子图包括:[[0,1,2],[3,4,5]]
点对点情况:[[0,1],[0,2] ,[0,3] ,[5,2] ,[3,5] ,[1,2] ,[1,4] ,[3,4] ,[5,4]]
单点情况:[0,1,2,3,4,5]
由于点对点和所有完全子图有重复对数据进行剪枝操作
剪枝后:
所有完全子图包括:[[0,1,2],[3,4,5]]
点对点情况:[ [0,3] ,[5,2] , [1,4]]
只需要求出所有子图的权重即可
综上所述,分为两步:
- 找到图的所有完全子图权重最大的那一组作为最佳搭配
总体思想:深度遍历这个图
计数方案:
四:数学问题再转为业务问题业务代码如下:
数据结构实体算法类测试类 4.1:数据结构实体
package xyz.fudongyang.demo3; import java.util.Objects; import java.util.Set; public class GraphStruct implements Comparable4.2: 算法处理类{ private Long couponId; private Double weight; private int afterWeightSum = 0; private String type; private transient Integer index; private transient int isSelect = 0; private Set mappingLists; public GraphStruct(Long couponId, Double weight, Integer index) { this(couponId, weight, index, null); } public GraphStruct(Long couponId, Double weight, Integer index, String type) { this.couponId = couponId; this.weight = weight; this.index = index; this.type = type; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GraphStruct that = (GraphStruct) o; return index.equals(that.index); } @Override public int hashCode() { return Objects.hash(index); } @Override public int compareTo(GraphStruct o) { return this.getIndex() - o.getIndex(); } public Long getCouponId() { return couponId; } public void setCouponId(Long couponId) { this.couponId = couponId; } public Double getWeight() { return weight; } public void setWeight(Double weight) { this.weight = weight; } public int getAfterWeightSum() { return afterWeightSum; } public void setAfterWeightSum(int afterWeightSum) { this.afterWeightSum = afterWeightSum; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } public int getIsSelect() { return isSelect; } public void setIsSelect(int isSelect) { this.isSelect = isSelect; } public Set getMappingLists() { return mappingLists; } public void setMappingLists(Set mappingLists) { this.mappingLists = mappingLists; } }
package xyz.fudongyang.demo3;
import org.springframework.lang.Nullable;
import java.util.*;
public class GraphUtil {
@Deprecated
static int exc_sum = 0;
// 选中图节点标志
private final static int SELECTED = 1;
// 未选中图节点标志
private final static int UN_SELECT = 0;
private final static String EC = "EC";
private final static String PC = "PC";
public static Set bestCombination(List metaData) {
return bestCombination(metaData, false, null);
}
public static void selectStatus(Map metaData, Set selectedList, Set notOptionals, Set optionals) {
// 存放所有数据
Map dataMap = new HashMap<>();
int selectedSize = selectedList.size();
// EC PC 业务规则判断 PC 最多三个 EC 最多两个
boolean banPC = isBan(selectedList, CouponTypeEnum.PC.getCode(), 3);
boolean banEC = isBan(selectedList, CouponTypeEnum.EC.getCode(), 2);
if (isEmpty(selectedList)) {
optionals.addAll(metaData.values());
} else {
selectedList.forEach(e -> {
// 存放元素本身
addElement(dataMap, e.getIndex());
// 存放元素关系网
Set mappingLists = e.getMappingLists();
if (!mappingLists.isEmpty()) {
mappingLists.forEach(g -> addElement(dataMap, g.getIndex()));
}
});
}
// 如果是可选状态 则元素需要两两需要连接 则需要判断元素的完全子图即可
for (Map.Entry entry : dataMap.entrySet()) {
GraphStruct graphStruct = metaData.get(entry.getKey());
if (selectedList.contains(graphStruct)) {
continue;
}
if ((banEC && isEC(graphStruct)) || (banPC && isPC(graphStruct))) {
notOptionals.add(graphStruct);
continue;
}
if (selectedSize == entry.getValue() && !selectedList.contains(graphStruct)) {
optionals.add(graphStruct);
} else {
notOptionals.add(graphStruct);
}
}
}
public static Set bestCombination(List metaData, boolean dynamic,
List selectedList) {
// 存放最佳组合的数据
Set ans = new HashSet<>();
int size = metaData.size();
// 存放图数据结构
int[][] maps = new int[size + 1][size + 1];
// 存放下标对应的业务数据
GraphStruct[] book = new GraphStruct[size + 1];
// 处理前置数据----剪枝用
preData(metaData, dynamic, selectedList);
// 构件图
buildGraph(metaData, maps, book);
// 解析图
dfs(1, Integer.MIN_VALUE, size, book, maps, ans, dynamic, selectedList);
System.out.println("exc_sum = " + exc_sum);
return ans;
}
private static void addElement(Map dataMap, Integer index) {
if (dataMap.containsKey(index)) {
dataMap.put(index, dataMap.get(index) + 1);
} else {
dataMap.put(index, 1);
}
}
private static boolean isEC(GraphStruct graphStruct) {
return CouponTypeEnum.EC.getCode().equalsIgnoreCase(graphStruct.getType());
}
private static boolean isPC(GraphStruct graphStruct) {
return CouponTypeEnum.PC.getCode().equalsIgnoreCase(graphStruct.getType());
}
private static boolean isBan(Set selectedList, String opt, int maxNum) {
int sum = 0;
for (GraphStruct graphStruct : selectedList) {
if (opt.equalsIgnoreCase(graphStruct.getType())) {
if (++sum >= maxNum) {
return true;
}
}
}
return false;
}
private static void preData(List metaData) {
preData(metaData, false, null);
}
private static void preData(List metaData, boolean dynamic,
List selectedList) {
// 计算每个节点 后续权重和 用于剪枝
setAfterWeightSum(metaData);
// 升序已选择的优惠券,得到数组第一个就是最小的下标
if (dynamic && !isEmpty(selectedList)) {
Collections.sort(selectedList);
}
}
private static void setAfterWeightSum(List metaData) {
Collections.sort(metaData);
int sum = 0;
ListIterator graphIterator = metaData.listIterator(metaData.size());
while (graphIterator.hasPrevious()) {
GraphStruct previous = graphIterator.previous();
previous.setAfterWeightSum(sum);
sum += previous.getWeight();
}
}
private static void buildGraph(List metaData, int[][] maps,
GraphStruct[] book) {
for (GraphStruct graphStruct : metaData) {
// 构建下标对应业务数据
Integer index = graphStruct.getIndex();
book[index] = graphStruct;
// 构建图
Set mappingLists = graphStruct.getMappingLists();
if (!isEmpty(mappingLists)) {
mappingLists.forEach(e -> maps[index][e.getIndex()] = SELECTED);
}
}
}
private static Integer dfs(int idx, Integer maxWeight, int size, GraphStruct[] book,
int[][] maps, Set ans) {
return dfs(idx, maxWeight, size, book, maps, ans, false, null);
}
private static Integer dfs(int idx, Integer maxWeight, int size, GraphStruct[] book,
int[][] maps, Set ans, boolean dynamic,
List selectedList) {
exc_sum++;
if (idx == size + 1) {
// 如果不包含已经选择的优惠券 则可以废弃该结果集
if (dynamic && !isEmpty(selectedList)
&& !resultContainsSelect(book, selectedList)) {
return maxWeight;
}
int bookSumWeight = getBookSumWeight(book);
if (maxWeight < bookSumWeight) {
// 获取到选中的数据存放到结果集种
ans.clear();
saveResult(book, ans);
}
return Math.max(maxWeight, bookSumWeight);
}
for (int i = 0; i < 2; i++) {//二叉树
if (i == 0) {
// 不选节点,构建二叉树
// idx后面所有的权重加起来都没这个大的话 就没必要执行了(剪枝1)
if (cutAfterData(maxWeight, idx, book)) {
maxWeight = dfs(idx + 1, maxWeight, size, book, maps, ans, dynamic, selectedList);
}
} else {
// 选中节点,构建二叉树
// 如果不满足完全子图,没必要构建(剪枝2)
if (check(idx, book, maps)) {
book[idx].setIsSelect(SELECTED);
// 如果不满足PC EC业务规则 则不允许执行(业务规则)
if (ruleCheck(book)) {
maxWeight = dfs(idx + 1, maxWeight, size, book, maps, ans, dynamic, selectedList);
}
book[idx].setIsSelect(UN_SELECT);
}
}
}
return maxWeight;
}
private static boolean check(int idx, GraphStruct[] book, int[][] maps) {
for (int i = 1; i < idx; i++) {
// 如果他们不是完全子图 就可以过滤掉了 前期我们已经选择了一些节点
if (book[i].getIsSelect() == SELECTED && maps[i][idx] != SELECTED) {
return false;
}
}
return true;
}
private static int getBookSumWeight(GraphStruct[] book) {
int sum = 0;
for (GraphStruct graphStruct : book) {
if (graphStruct != null && graphStruct.getIsSelect() == SELECTED) {
sum += graphStruct.getWeight();
}
}
return sum;
}
private static void saveResult(GraphStruct[] book, Set ans) {
for (GraphStruct graphStruct : book) {
if (graphStruct != null && graphStruct.getIsSelect() == SELECTED) {
ans.add(graphStruct);
}
}
}
private static boolean ruleCheck(GraphStruct[] book) {
int pcSum = 0;
int ecSum = 0;
for (GraphStruct graphStruct : book) {
if (graphStruct == null) {
continue;
}
if (pcSum > CouponTypeEnum.PC.getMaximum() || ecSum > CouponTypeEnum.EC.getMaximum()) {
return false;
}
if (graphStruct.getIsSelect() == SELECTED) {
if (CouponTypeEnum.PC.getCode().equalsIgnoreCase(graphStruct.getType())) {
pcSum++;
}
if (CouponTypeEnum.EC.getCode().equalsIgnoreCase(graphStruct.getType())) {
ecSum++;
}
}
}
return true;
}
private static boolean cutAfterData(int maxWeight, int idx, GraphStruct[] book) {
int before = 0;
int after = book[idx].getAfterWeightSum();
for (int i = 0; i <= idx; i++) {
if (book[i] != null && book[i].getIsSelect() == SELECTED) {
before += book[i].getWeight();
}
}
return maxWeight < before + after;
}
private static boolean resultContainsSelect(GraphStruct[] book,
List selectedList) {
for (GraphStruct graphStruct : selectedList) {
if (book[graphStruct.getIndex()].getIsSelect() == UN_SELECT) {
return false;
}
}
return true;
}
public static boolean isEmpty(@Nullable Collection> collection) {
return collection == null || collection.isEmpty();
}
}
4.3: 测试类
package xyz.fudongyang.demo3;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class BestCombinationTest {
public static void main(String[] args) {
GraphStruct graphStructA = new GraphStruct(1L, 5D, 1, "EC");
GraphStruct graphStructB = new GraphStruct(2L, 8D, 2, "EC");
GraphStruct graphStructC = new GraphStruct(3L, 20D, 3, "EC");
GraphStruct graphStructD = new GraphStruct(4L, 2D, 4, "EC");
GraphStruct graphStructE = new GraphStruct(5L, 7D, 5, "EC");
List reqList = new ArrayList<>();
Set graphA = new HashSet<>();
graphA.add(new GraphStruct(2L, 8D, 2, "EC"));
graphA.add(new GraphStruct(3L, 20D, 3, "EC"));
graphA.add(new GraphStruct(4L, 20D, 4, "EC"));
graphA.add(new GraphStruct(5L, 7D, 5, "EC"));
graphStructA.setMappingLists(graphA);
reqList.add(graphStructA);
Set graphB = new HashSet<>();
graphB.add(new GraphStruct(1L, 5D, 1, "EC"));
graphB.add(new GraphStruct(3L, 20D, 3, "EC"));
graphB.add(new GraphStruct(5L, 7D, 5, "EC"));
graphStructB.setMappingLists(graphB);
reqList.add(graphStructB);
Set graphC = new HashSet<>();
graphC.add(new GraphStruct(1L, 5D, 1, "EC"));
graphC.add(new GraphStruct(2L, 8D, 2, "EC"));
graphC.add(new GraphStruct(4L, 20D, 4, "EC"));
graphStructC.setMappingLists(graphC);
reqList.add(graphStructC);
Set graphD = new HashSet<>();
graphD.add(new GraphStruct(1L, 5D, 1, "EC"));
graphD.add(new GraphStruct(3L, 20D, 3, "EC"));
graphD.add(new GraphStruct(5L, 7D, 5, "EC"));
graphStructD.setMappingLists(graphD);
reqList.add(graphStructD);
Set graphE = new HashSet<>();
graphE.add(new GraphStruct(1L, 5D, 1, "EC"));
graphE.add(new GraphStruct(2L, 8D, 2, "EC"));
graphE.add(new GraphStruct(4L, 20D, 4, "EC"));
graphStructE.setMappingLists(graphE);
reqList.add(graphStructE);
Set graphStructs = GraphUtil.bestCombination(reqList);
System.out.println("======最佳优惠券组合==========");
graphStructs.forEach(e -> System.out.println(e.getCouponId()));
}
}
4.4冗余设计其他类
package xyz.fudongyang.demo3;
public enum CouponTypeEnum {
PC("PC", "优惠券PC码标识", 3),
EC("EC", "优惠券EC码标识", 2),
;
private final String code;
private final String desc;
private final Integer maximum;
CouponTypeEnum(String code) {
this(code, "");
}
CouponTypeEnum(String code, String desc) {
this(code, desc, null);
}
CouponTypeEnum(String code, String desc, Integer maximum) {
this.code = code;
this.desc = desc;
this.maximum = maximum;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public Integer getMaximum() {
return maximum;
}
public static CouponTypeEnum getByCode(String code) {
for (CouponTypeEnum value : CouponTypeEnum.values()) {
if (value.code.equalsIgnoreCase(code)) {
return value;
}
}
return null;
}
}



