获取集合、数组的全部子集(一)

前言

想起来之前写过一段业务代码来处理库存中的商品。逻辑如下:
仓库中存有多个箱子,每个箱子里面放着一定数量的商品,商品数量不一,现在我要分析出这几箱中商品数量之和最接近目标出库数量的箱子的信息。
本文使用递归实现模拟二进制运算,通过改变对应下标的标志位状态找到所有的组合方式。
该方法并不一定是最优方法,但可能会适合某些场景。
除此之外可以使用 递归+回溯 的方式去处理组合、子集之类的问题。

分析

因为每个箱子都是独立存在的,所以在这个列表里面不存在重复元素一说,分析起来相对简单,
对于有重复元素的添加一个判重方法就可以了。
我只要能够知道所有组合方式,并计算他们与出库数量的差值,获取最小差值的组合即可。
所以问题在于如何获取到所有的组合。

思考

我们可以创建一个和列表同样长度的数组,数组中的数值代表列表中相同下标的元素箱子是否被收纳到当前组合中。
0 表示不包含该箱子 1 表示包含该箱子
为什么要用 0 和 1 ?
根据组合数公式,对于一个存在 n 个不同元素的集合,我们从中任取 m 个元素组成一组的组法有 C nm 种。
而对于全部的组合之和: C n0 + C n1 + C n2 + … + C nn = 2n
在这里我们不考虑空集,所以我们能得到的组合数量是 2n-1
而 n 位长度的二进制能表示的无符号最大值为 2n-1,这两者的数值相同。
而且根据二进制的步进规则,满 2 进 1,除了0、1之外不存在其他的值,符合我们拿来做判断标志的要求。

简单的实例

有列表 :A 【箱子1,箱子2,箱子3】
创建数组 :b 【0,0,0】
模拟二进制加 1 :stepping
                   stepping
【0,0,0】--------->【1,0,0】 ==》【箱子1】

                   stepping
【1,0,0】--------->【0,1,0】 ==》【箱子2】

                   stepping
【0,1,0】--------->【1,1,0】 ==》【箱子1,箱子2】

… …
                   stepping
【1,0,1】--------->【0,1,1】 ==》【箱子2,箱子3】

                   stepping
【0,1,1】--------->【1,1,1】 ==》【箱子1,箱子2,箱子3】

完整代码



    //商品类
    static class Goods {
        private static SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");
        String name;
        Integer amount;
        Date inWmsTime;
        @Override
        public String toString() {
            return String.format("【商品:%s ,数量:%d ,入库时间:%s】",name,amount,null==inWmsTime?"":sdf.format(inWmsTime));
        }
    }

    public static void main(String[] args) {
		//创建六箱商品 商品默认一样 数量不同
        List<Goods> goods = new ArrayList<>();
        for(int i=0;i<6;i++){
            Goods good = new Goods();
            good.name = "good";
            good.amount = 1+i;
            goods.add(good);
        }
        List<List<Goods>> result = subsets(goods,7);
        result.stream().forEach(System.out::println);
    }

    /**
     *
     * @param
     * @return
     */
    public static List<List<Goods>> subsets(List<Goods> goods,Integer target) {
        List<List<Goods>> result =new ArrayList<>();
        int[] tmparr = new int[goods.size()];
        //最小差值
        Integer minDiffer = 0;
        //判断是否可以继续便利
        List<Goods> inner = new ArrayList<>();
        while(isContinue(tmparr)){
        	//步进+1
            stepping(0,tmparr);
            inner.clear();
            //取出集合对应数组相同下标 值为 1 的元素,加入到组合 inner中
            for (int i =0 ;i<tmparr.length;i++) {
                if(tmparr[i] == 1){
                    inner.add(goods.get(i));
                }
            }
			//计算当前组合商品总数
            Integer currentAmount = inner.stream().map(e->e.amount).reduce(0,Integer::sum);
			//计算差值
            Integer currentDiffer =  currentAmount - target;
            //获取不少于目标的最小值
            if(currentDiffer >= 0 && currentDiffer <= minDiffer)
                result.add(new ArrayList<>(inner));
        }

        return result;
    }

    /**
     * 模拟二进制
     * 例如:
     * candidates {1,2,3,4,5}
     * a {0,0,0,0,0}
     * 从下标第 0 位 + 1, 大于 1时,当前位置为 0 ,下一位 + 1 
     * =>{1,0,0,0,0}
     * =>{0,1,0,0,0}
     * =>{1,1,0,0,0}
     * =>{0,0,1,0,0}
     * ...
     * =>{1,1,1,1,1}
     * @param index
     * @return
     */
    private static void stepping(int index,int[] tmparr) {
        if (tmparr[index] == 0) {
            tmparr[index] = 1;
        } else {
            if (index < tmparr.length)
                tmparr[index] = 0;
            stepping(index + 1,tmparr);
        }
    }


    /**
     * 判断是否已经遍历到全部组合
     * 有 0 存在即没有遍历到全部 接续遍历
     * @return
     */
    private static boolean isContinue(int[] tmparr) {
        for (int i : tmparr) {
            if (i == 0 )
                return true;
        }
        return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mingvvv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值