组合算法

1、计算组合数的实现

根据公式实现,无需过多讲解,唯一要注意的是,需要保证输入的参数无误,已经注意避免长度溢出。

/**
     * 计算C组合数
     * 
     * @param big
     *            下标
     * @param small
     *            上标
     * @return
     */
    public static long calC(int big, int small) {
        if (big <= 0 || small < 0 || big < small) {
            logger.error("[opt:calC, msg:param_error, big:{}, small:{}]", big, small);
            throw new CommonException(Result.PARAM_ERROR);
        }

        BigInteger res = BigInteger.valueOf(1);

        int b = big;
        for (int i = 0; i < small; i++) {
            res = res.multiply(BigInteger.valueOf(b));
            b--;
        }
        int s = small;
        for (int i = 0; i < small; i++) {
            res = res.divide(BigInteger.valueOf(s));
            s--;
        }

        return res.longValue();
    }

2、输出组合集的实现

因为组合结果的顺序无关性,所以只需保证组合集的各个结果有一个不相同即可,位移法是比较适合此场景的。

1、如5个数中选3个,选中的标为1,未中则为0,三个1从左到右标位第一位、第二位和最后一位:

1 2 3 4 5  // 原始数据
1 1 1 0 0 // 选中123
1 1 0 1 0 // 选中 124,最后一个进一位
1 0 1 1 0 // 134 ,倒数第二位进一位,以此类推
0 1 1 1 0 // 234 ,直到第一位无位可进
1 1 0 0 1 // 125 ,则最后一位在进一位,其他归位
1 0 1 0 1 // 135,把倒数第二位看作最后一位,进位方法如上,直到所有数无位可进
0 1 1 0 1 // 235
1 0 0 1 1 // 145
0 1 0 1 1 // 245
0 0 1 1 1 // 345

2、java实现中,必定需要保证高复用性,应当适用于所有类型的组合:

/**
     * 组合处理,当list.size < n时,返回空的list
     * 
     * @param list
     * @param n
     * @return
     */
    public static <T> List<List<T>> combine(List<T> list, int n) {

        List<List<T>> result = Lists.newArrayList();

        //选号位
        int[] pos = new int[n];

        //选不出来
        if (list.size() < n || list.size() <= 0 || n <= 0) {
            return result;
        }

        //初始化前n是选号位
        for (int i = 0; i < n; i++) {
            pos[i] = i;
        }

        //循环处理
        while (true) {
            //1.生成选择数据
            List one = Lists.newArrayList();
            for (int i = 0; i < n; i++) {
                one.add(list.get(pos[i]));
            }

            result.add(one);

            //2.进位
            //从选号位最右边开始,选择第一个可以右移的位置进行进位

            boolean is_move = false;

            for (int i = n - 1; i >= 0; i--) {
                //可以进位
                if (pos[i] < list.size() - n + i) {
                    pos[i]++; //选位右移

                    //所有右边的选号全部归位
                    for (int k = i + 1; k < n; ++k) {
                        pos[k] = pos[i] + k - i;
                    }
                    is_move = true;

                    break;
                }
            }

            //没有成功移位,到头了
            if (!is_move) {
                break;
            }
        }

        return result;
    }

3、组合算法扩展

组合算法的需求是:结果集两两只需有一个数不相同,则两个结果不相同。

那么如果有一个需求是,结果集中,两两结果不允许filterSameNun个数相同,那么又该如何高效地实现呢?


其实此问题难处在于如何高效地判断两个结果间结果相同,方法很多,本人使用的是查表法实现,

下表为8位表,通过游标可查8位的二进制数中有多少个1,将32位整数拆成4块即可查出32位整数有多少个1,有点是快,缺点就是输入的数最多只能32位,不过可以自己扩展;

 /**
     * 二进制数1的个数表
     */
    private static int[] binaryTable = new int[] { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3,
        4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,
        4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2,
        3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3,
        3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
        5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5,
        6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, };

    /**
     * 计算二进制数里面有多少个1
     * 
     * @param n
     * @return
     */
    private static int count1OfBinary(int n) {

        return binaryTable[n & 0xff] + binaryTable[(n >> 8) & 0xff] + binaryTable[(n >> 16) & 0xff]
            + binaryTable[(n >> 24) & 0xff];
    }

将结果集,两个数,两两相与 (&),即可得到一个只有重合1的数,在通过上面的方法判断此数有多少个1,如果1的数大于等于filterSameNun,则只保留其一,输出则为满足需求的结果集:

 /**
     * 组合处理,当list.size < n时,返回空的list
     *
     * @param list
     * @param n
     *            不能超过32位
     * @param filterSameNum
     *            不能相同的位数
     * @return
     */
    public static <T> List<List<T>> combineAndFilter(List<T> list, int n, int filterSameNum) {

        List<List<T>> result = Lists.newArrayList();
        List<Integer> binarys = Lists.newArrayList();

        //选号位
        int[] pos = new int[n];

        //选不出来
        if (list.size() < n || list.size() <= 0 || n <= 0 || n > 32) {
            return result;
        }

        //初始化前n是选号位
        for (int i = 0; i < n; i++) {
            pos[i] = i;
        }

        //循环处理
        while (true) {
            //1.生成选择数据
            List one = Lists.newArrayList();
            int binary = 0;
            for (int i = 0; i < n; i++) {
                one.add(list.get(pos[i]));
                binary |= 1 << pos[i];
            }

            //判断是否有filterSameNum重复的1位
            boolean isValidBinary = true;
            for (int b: binarys) {
                if (count1OfBinary(b & binary) >= filterSameNum) {
                    isValidBinary = false;
                    break;
                }
            }

            if (isValidBinary) {
                result.add(one);
                binarys.add(binary);
            }

            //2.进位
            //从选号位最右边开始,选择第一个可以右移的位置进行进位

            boolean is_move = false;

            for (int i = n - 1; i >= 0; i--) {
                //可以进位
                if (pos[i] < list.size() - n + i) {
                    pos[i]++; //选位右移

                    //所有右边的选号全部归位
                    for (int k = i + 1; k < n; ++k) {
                        pos[k] = pos[i] + k - i;
                    }
                    is_move = true;

                    break;
                }
            }

            //没有成功移位,到头了
            if (!is_move) {
                break;
            }
        }

        return result;
    }


二当家的黑板报

hello world

欢迎关注




欢迎关注二当家的黑板报

✬如果你喜欢这篇文章,欢迎分享到朋友圈✬

评论功能现已开启,灰常接受一切形式的吐槽和赞美☺





猜你喜欢

转载自blog.csdn.net/u012367513/article/details/78931191
今日推荐