ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-03-20 16:59:44  阅读:283  来源: 互联网

标签:叠加 优惠券 return int graphStruct EC 算法 book GraphStruct


可叠加的优惠券最佳组合算法(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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有