在Java中创建NON DISTINCT值的子集的所有多集

时间:2018-10-05 21:57:15

标签: java set subset multiset

给定一个对象数组,当数组中的值可能重复时,我需要尽可能高效地找到给定数组的所有不同子集集,其中包括所有值。

例如:如果数组为1, 2, 1, 2,那么我需要创建以下多重集:

  1. {[1], [1], [2], [2]}
  2. {[1], [1], [2, 2]}
  3. {[1], [2], [1, 2]}
  4. {[1], [1, 2, 2]}
  5. {[1, 1], [2], [2]}
  6. {[1, 1], [2, 2]}
  7. {[1, 2], [1, 2]}
  8. {[1, 1, 2], [2]}
  9. {[1, 1, 2, 2]}

请注意,子集内的值顺序和多重集内的子集顺序都不重要。像{[1, 2, 2], [1]}这样的多集与#4相同,而{[2, 1], [2], [1]}#3相同。

这里的示例是int,但实际上,我将不得不使用对象。

这应该尽可能高效。最好的方法是仅计算正确的(重复的)多集,而无需检查是否已经出现,因为创建它的方法将消除重叠现象。

我知道如何使用二进制表示形式创建所有子集。我将其与递归相结合来计算所有多重集。效果很好,只是当值重复时不起作用。这是我到目前为止所做的:

a 是给定数字的数组,   curr 是当前正在构建的多集,  并且 b 是所有多集的最终集合。)

public static void makeAll(ArrayList<Integer> a, 
                           ArrayList<ArrayList<Integer>> curr,
                           ArrayList<ArrayList<ArrayList<Integer>>> b) {

    ArrayList<ArrayList<Integer>> currCopy;
    ArrayList<Integer> thisGroup, restGroup;
    int currSize = 0, ii = 0;

    if (a.size() == 0)
        b.add(new ArrayList<ArrayList<Integer>>(curr));
    else {
        for (int i = 0; i < 1 << (a.size() - 1); i++) {
            thisGroup = new ArrayList<>();
            restGroup = new ArrayList<>();
            ii = (i << 1) + 1; // the first one is always in, keeps uniquness.

            for (int j = 0; j < a.size(); j++)
                if ((ii & 1 << j) > 0)
                    thisGroup.add(a.get(j));
                else
                    restGroup.add(a.get(j));

            currSize = curr.size();
            curr.add(new ArrayList<Integer>(thisGroup));

            makeAll(restGroup, curr, b);

            curr.subList(currSize, curr.size()).clear();
        }
    }
}

谢谢!

1 个答案:

答案 0 :(得分:0)

这是“功率集”问题,具有两个以上的常用集合(通常包含在子集中或从子集中排除,因此功率集具有2 ^ N个元素)。对于您这个问题的版本,任何元素都可以是N个子集中的任何一个的一部分,因此问题的范围将扩展为N ^ N(很快就会变大)。

要找到给定N个元素列表的所有唯一分区,请从概念上生成以N为底的所有N位数字,然后数字的每个数字的值输入中相应元素的分区索引(这意味着通常,在所有情况下,您最终都会得到空分区,除非分区数等于N)。使用这些数字索引将元素分组为共享相同索引的元素列表,从而生成列表列表。为了检测重复项,您必须对子列表进行排序,然后对列表列表进行排序,然后将排序后的列表列表添加到集合中。您无法避免最后的重复数据删除步骤,因为您的描述允许输入中包含重复的元素。

package main;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PrintPartitionings {
    /** A list of integers */
    public static class Partition extends ArrayList<Integer>
            implements Comparable<Partition> {
        // Lexicographic comparator
        @Override
        public int compareTo(Partition other) {
            for (int i = 0, ii = Math.min(this.size(),
                    other.size()); i < ii; i++) {
                int c = this.get(i).compareTo(other.get(i));
                if (c != 0) {
                    return c;
                }
            }
            return Integer.compare(this.size(), other.size());
        }
    }

    /** A list of lists of integers */
    public static class Partitioning extends ArrayList<Partition>
            implements Comparable<Partitioning> {
        public Partitioning() {
            super();
        }

        public Partitioning(int N) {
            super(N);
            // Pre-allocate sub-lists for convenience
            for (int j = 0; j < N; j++) {
                add(new Partition());
            }
        }

        // Lexicographic comparator
        @Override
        public int compareTo(Partitioning other) {
            for (int i = 0, ii = Math.min(this.size(),
                    other.size()); i < ii; i++) {
                int c = this.get(i).compareTo(other.get(i));
                if (c != 0) {
                    return c;
                }
            }
            return Integer.compare(this.size(), other.size());
        }
    }

    /** Print all unique partitionings of the passed array of integers */
    public static void printPartitionings(int[] elts) {
        int N = elts.length;
        Set<Partitioning> setOfPartitionings = new HashSet<>();
        // Generate integers in [0, N^N)
        for (long i = 0, ii = (long) Math.pow(N, N); i < ii; i++) {
            // Create empty partitioning
            Partitioning partitioning = new Partitioning(N);

            // Assign each element to a partition based on base N digit
            long digits = i;
            for (int j = 0; j < N; j++) {
                int digit = (int) (digits % N);
                digits /= N;
                partitioning.get(digit).add(elts[j]);
            }

            // Sort individual partitions, and remove empty partitions
            Partitioning partitioningSorted = new Partitioning();
            for (Partition partition : partitioning) {
                if (!partition.isEmpty()) {
                    Collections.sort(partition);
                    partitioningSorted.add(partition);
                }
            }

            // Sort the partitioning
            Collections.sort(partitioningSorted);

            // Add the result to the final set of partitionings
            setOfPartitionings.add(partitioningSorted);
        }

        // Sort lexicographically to make it easier to view the result
        List<Partitioning> setOfPartitioningsSorted = new ArrayList<>(
                setOfPartitionings);
        Collections.sort(setOfPartitioningsSorted);
        for (Partitioning partitioning : setOfPartitioningsSorted) {
            System.out.println(partitioning);
        }
    }

    public static void main(String[] args) {
        printPartitionings(new int[] { 1, 2, 1, 2 });
    }
}

实现没有特别优化-可以通过多种方式使其更快。同样,所示代码仅适用于中等大小的问题,当N ^ N <Long.MAX_VALUE时,即所示代码的N的最大值为15(但您不希望运行该问题)还是要调整大小,因为代码要花很长时间才能运行。

输出:

[[1], [1], [2], [2]]
[[1], [1], [2, 2]]
[[1], [1, 2], [2]]
[[1], [1, 2, 2]]
[[1, 1], [2], [2]]
[[1, 1], [2, 2]]
[[1, 1, 2], [2]]
[[1, 1, 2, 2]]
[[1, 2], [1, 2]]

对于输入{ 1, 2, 3, 2 },输出为:

[[1], [2], [2], [3]]
[[1], [2], [2, 3]]
[[1], [2, 2], [3]]
[[1], [2, 2, 3]]
[[1, 2], [2], [3]]
[[1, 2], [2, 3]]
[[1, 2, 2], [3]]
[[1, 2, 2, 3]]
[[1, 2, 3], [2]]
[[1, 3], [2], [2]]
[[1, 3], [2, 2]]