编程语言
首页 > 编程语言> > 优惠券最佳叠加算法(图深度搜索)

优惠券最佳叠加算法(图深度搜索)

作者:互联网

可叠加的优惠券最佳组合算法(Java)

一:前提知识

背景:给出一组优惠券,并给出优惠券叠加关系,优惠券权重

业务规则:某个类型的券存在最多限制

需求:求出优惠券在业务规则下最优组合(权重最大的组合)

名词规定:

二:业务问题转为数学问题

当存在一组优惠券时,得到最优优惠券组合。首先将业务数据抽离为算法数据可得到如下图

在这里插入图片描述

其中 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]]

只需要求出所有子图的权重即可

综上所述,分为两步:

  1. 找到图的所有完全子图
  2. 权重最大的那一组作为最佳搭配

三:求图的完全子图(团、集)

总体思想:深度遍历这个图

计数方案:

在这里插入图片描述

四:数学问题再转为业务问题

业务代码如下:

4.1:数据结构实体

package xyz.fudongyang.demo3;

import java.util.Objects;
import java.util.Set;

/**
 * @author: vfudongyang
 * @createTime: 2022年03月16日 18:08:00
 * @Description: 图节点通用结构体
 */

public class GraphStruct implements Comparable<GraphStruct> {

    /**
     * 业务字段:优惠券id
     */
    private Long couponId;

    /**
     * 业务字段:权重
     */
    private Double weight;

    /**
     * 业务字段:后序所有权重和(客户端不需要提供)
     */
    private int afterWeightSum = 0;

    /**
     * 业务字段:优惠券类型:PC EC
     *
     * @see CouponTypeEnum
     */
    private String type;

    /**
     * 图字段:下标
     */
    private transient Integer index;

    /**
     * 图字段:1 代表图节点被选中 0 代表图节点未被选中
     */
    private transient int isSelect = 0;

    /**
     * 图映射关系
     */
    private Set<GraphStruct> 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<GraphStruct> getMappingLists() {
        return mappingLists;
    }

    public void setMappingLists(Set<GraphStruct> mappingLists) {
        this.mappingLists = mappingLists;
    }
}

4.2: 算法处理类

package xyz.fudongyang.demo3;

import org.springframework.lang.Nullable;

import java.util.*;

/**
 * @author: vfudongyang
 * @createTime: 2022年03月17日 14:30:00
 * @Description:
 */
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<GraphStruct> bestCombination(List<GraphStruct> metaData) {
        return bestCombination(metaData, false, null);
    }

    /**
     * 优惠券选择状态
     *
     * @param metaData     源数据信息
     * @param selectedList 已选数据
     * @param notOptionals 不可选数据
     * @param optionals    可选数据
     */
    public static void selectStatus(Map<Integer, GraphStruct> metaData, Set<GraphStruct> selectedList, Set<GraphStruct> notOptionals, Set<GraphStruct> optionals) {
        // 存放所有数据
        Map<Integer, Integer> 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<GraphStruct> mappingLists = e.getMappingLists();
                if (!mappingLists.isEmpty()) {
                    mappingLists.forEach(g -> addElement(dataMap, g.getIndex()));
                }
            });
        }

        // 如果是可选状态 则元素需要两两需要连接 则需要判断元素的完全子图即可
        for (Map.Entry<Integer, Integer> 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);
            }
        }
    }

    /**
     * 最佳优惠券组合
     *
     * @param metaData     源数据信息
     * @param dynamic      是否是动态选择
     * @param selectedList 已选数据
     * @return Set<GraphStruct> 优惠券最佳组合
     */
    public static Set<GraphStruct> bestCombination(List<GraphStruct> metaData, boolean dynamic,
                                                   List<GraphStruct> selectedList) {
        // 存放最佳组合的数据
        Set<GraphStruct> 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<Integer, Integer> 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<GraphStruct> 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<GraphStruct> metaData) {
        preData(metaData, false, null);
    }

    private static void preData(List<GraphStruct> metaData, boolean dynamic,
                                List<GraphStruct> selectedList) {
        // 计算每个节点 后续权重和 用于剪枝
        setAfterWeightSum(metaData);

        // 升序已选择的优惠券,得到数组第一个就是最小的下标
        if (dynamic && !isEmpty(selectedList)) {
            Collections.sort(selectedList);
        }
    }

    private static void setAfterWeightSum(List<GraphStruct> metaData) {
        Collections.sort(metaData);
        int sum = 0;
        ListIterator<GraphStruct> graphIterator = metaData.listIterator(metaData.size());
        while (graphIterator.hasPrevious()) {
            GraphStruct previous = graphIterator.previous();
            previous.setAfterWeightSum(sum);
            sum += previous.getWeight();
        }
    }

    private static void buildGraph(List<GraphStruct> metaData, int[][] maps,
                                   GraphStruct[] book) {

        for (GraphStruct graphStruct : metaData) {

            // 构建下标对应业务数据
            Integer index = graphStruct.getIndex();
            book[index] = graphStruct;

            // 构建图
            Set<GraphStruct> 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<GraphStruct> ans) {
        return dfs(idx, maxWeight, size, book, maps, ans, false, null);
    }

    /**
     * @param idx          第idx个点 从 下标1开始
     * @param maxWeight    全中国
     * @param size         数据集合大小
     * @param book         元数据集合
     * @param maps         数据间图形数据结构
     * @param ans          最佳组合优惠券结果集合
     * @param dynamic      是否是动态选择
     * @param selectedList 已选择的优惠券
     * @return Integer     当前权重
     */
    private static Integer dfs(int idx, Integer maxWeight, int size, GraphStruct[] book,
                               int[][] maps, Set<GraphStruct> ans, boolean dynamic,
                               List<GraphStruct> 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<GraphStruct> 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<GraphStruct> 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;

/**
 * @author: vfudongyang
 * @createTime: 2022年03月17日 15:57:00
 * @Description:
 */
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<GraphStruct> reqList = new ArrayList<>();

        Set<GraphStruct> 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<GraphStruct> 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<GraphStruct> 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<GraphStruct> 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<GraphStruct> 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<GraphStruct> graphStructs = GraphUtil.bestCombination(reqList);
        System.out.println("======最佳优惠券组合==========");
        graphStructs.forEach(e -> System.out.println(e.getCouponId()));

    }
}

4.4冗余设计其他类

package xyz.fudongyang.demo3;

/**
 * @author: vfudongyang
 * @createTime: 2022年03月18日 17:23:00
 * @Description:
 */
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;
    }
}

标签:叠加,优惠券,return,int,graphStruct,EC,算法,book,GraphStruct
来源: https://blog.csdn.net/qq_44112474/article/details/123616038