java求集合幂集算法

1、幂集

所谓幂集(Power Set), 就是原集合中所有的子集(包括全集和空集)构成的集族。
可数集是最小的无限集; 它的幂集和实数集一一对应(也称同势),是不可数集。
不是所有不可数集都和实数集等势,集合的势可以无限的大。如实数集的幂集也是不可数集,但它的势比实数集大。 
设X是一个有限集,|X| = k,则X的幂集的势为2的k次方。(摘自百度百科)


|X| 表示集合X中元素的个数。

首先了解下为什么幂集元素个数 = 2的k次方
    设集合X = {a,b,c}
    那么幂集为:
        {}、{a}、{b}、{a,b}、{c}、{a,c}、{b,c}、{a,b,c}
    将集合从成单元素慢慢到全集增长的过程就比较容易理解了
    每增长一个元素组成的新的幂集 = 增长前的幂集 + 该元素与增长前幂集的组合
    step1: X1 = {a}
        幂集: {}、{a}
    step2: X2 = {a,b}
        幂集: {}、{a} + ({}{b} + {a}{b}) = {} 、{a}、{b}、{a,b}
    step3: X3 = {a,b,c}
        幂集:{} 、{a}、{b}、{a,b} + ({}{c} + {a}{c} + {b}{c} + {a,b}{c}) = {}、{a}、{b}、{a,b}、{c}、{a,c}、{b,c}、{a,b,c}

    通过1、2、3可以看出一个规律 幂集生长的速度为 前一个幂集元素的个数 * 2
    k = 1时 个数为 2            2^1
    k = 2时 个数为 2*2          2^2
    k = 3时 个数为 2*2*2        2^3
    ...
    k = n时 个数为 2*2*2...*2   2^n

2、算法实现

2.1 实现一 组合法

通过上面step1、2、3的规律可以写一个很直观的求出幂集
下面是用java集合实现
public class PowerSetArith {

    public List<List<String>> powerSet(List<String> set) {
        //已知所求集合的幂集会有2^n个元素
        int size = 2 << set.size();
        List<List<String>> powerSet = new ArrayList<>(size);
        //首先空集肯定是集合的幂集
        powerSet.add(Collections.emptyList());
        for (String element : set) {
            //计算当前元素与已存在幂集的组合
            int preSize = powerSet.size();
            for (int i = 0; i < preSize; i++) {
                List<String> combineSubset = new ArrayList<>(powerSet.get(i));
                combineSubset.add(element);
                powerSet.add(combineSubset);
            }
        }
        return powerSet;
    }


    @Test
    public void testPowerSet() {
        List<String> set = Arrays.asList("a", "b", "c", "d");
        List<List<String>> powerSet = powerSet(set);
        System.out.println(Arrays.toString(powerSet.toArray()));
    }

}
上面的方法也可以使用数组代替集合实现

2.2 实现二 位图法

由于n个元素集合的幂集个数 是2^n,而n个二进制位的最大值等于 = 2^n - 1,算上0的话正好2^n个数值
而此时元素的个数n刚好与二进制数的位数相等,0刚好可以用来表示空集,因此可以作一个函数f(n)来描述0~2^n-1 的数进行一一对应。

例如
集合X = {a,b,c}
取二进制数2^3 - 1 = 7 与之对应
7的二进制表示为:111
0~7的过程为:
000 001 010 011 100 101 110 111

那么f(n)怎么定义就能一一对应了呢?
从上面的数字中可以找到规律,位数正好等于集合中元素的个数,可以将二进制数的位与集合中元素的角标对应起来,
再者通过所在位的值是1 还是 0 来决定对位元素是否存在,这样根据0~2^n-1 的位图组合对应出的一个新的集合就是对应集合的幂集了。

java实现算法:
public class PowerSetArith {


    public List<List<String>> bitPowerSet(List<String> set) {

        //取2^n - 1 的数字作为对应的位图数值,其中n为元素个数
        int bitmapMaxNum = 2 << (set.size() - 1);
        List<List<String>> powerSet = new ArrayList<>(2 << set.size());
        powerSet.add(Collections.emptyList()); //添加空集
        for (int i = 1; i < bitmapMaxNum; i++) { //0表示空集,因此直接从1开始即可
            powerSet.add(subset(set, i));
        }
        return powerSet;
    }

    //bitmap与集合的映射关系函数
    public List<String> subset(List<String> set, int bitMapNum) {
        List<String> subset = new ArrayList<>();
        for (int i = 0; i < set.size(); i++) {
            if (((bitMapNum >> i) & 1) == 1) { // 判断对位元素是否存在
                subset.add(set.get(i));
            }
        }
        return subset;
    }

    @Test
    public void testBitPowerSet() {
        List<String> set = Arrays.asList("a", "b", "c", "d");
        List<List<String>> powerSet = bitPowerSet(set);
        System.out.println(Arrays.toString(powerSet.toArray()));
    }
}

猜你喜欢

转载自blog.csdn.net/m0_38043362/article/details/80941604