栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

优惠券满减算法(关于优惠券之间叠加规则)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

优惠券满减算法(关于优惠券之间叠加规则)

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

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

综上所述,分为两步:

    找到图的所有完全子图权重最大的那一组作为最佳搭配
三:求图的完全子图(团、集)

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

计数方案:

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

业务代码如下:

数据结构实体算法类测试类 4.1:数据结构实体

package xyz.fudongyang.demo3;

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



public class GraphStruct implements Comparable {

    
    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;
    }
}

4.2: 算法处理类
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;
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/773435.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号