1

考虑以下形式的排列列表(与顺序相关的组合):

(1 2 3)
(1 2 4)
(5 2 3)
(5 2 4)

我需要为这个排列组找到最少数量的生成集。例如,鉴于上述排列,

(1 2 3,4)
(5 2 3,4)

不是最优解。最优解是 (1,5 2 3,4)。

您会注意到此解包含集合 A={1, 5} B={2} 和 C={3,4} 排列的原始列表是这些集合的有序笛卡尔积:AXBX C。

我想将排列列表划分为尽可能少的组,表示为集合 A、B 和 C,其乘积包含列表中的所有排列。最终的答案通常是(但不总是)一组集合列表,因为并不总是可以将排列列表减少到单个生成集列表。也就是说,通常情况下,集合 A、B 和 C 的乘积说明了列表中的某些排列,但集合 D、E 和 F 需要说明列表中的其他排列,依此类推.

我解决这个问题的粗略尝试包括检查我是否在列表中的两个排列槽中的任何一个上匹配并递归地合并它们。如果是这样,我合并了这两种组合,即

(1 2 3)
(1 2 4)

生产

(1 2 3,4)

不幸的是,这种组合的合并顺序不是关联的。一个理想的解决方案是以这样一种方式合并这些组合,即最终的集合列表将包含尽可能多的排列。

为了演示关联性问题,举这个例子,它不能减少到少于两个生成集列表:

(1 2 4) 
(1 2 3)
(1 2 5) 
(5 2 3) 
(5 2 4)

假设您要根据以下规则递归地合并这些列,“如果任何两列匹配,我会通过按原样保留这些列来合并。然后我合并第三列中的两个集合以生成新集合。这两行合并后,我把原来的两行都扔掉了,所以没有重新合并,也没有重复计算。” 这些合并的顺序很重要。鉴于上面的排列列表,如果我合并 (1 2 3) 和 (1 2 4),我得到 (1 2 3,4)。现在,如何进行下一次合并以优化生成集?假设我查看 (1 2 5) 并看到它在两列上匹配 (1 2 3,4),我执行合并并得到 (1 2 3,4,5)。一切似乎都很好。但是,然后我将 (5 2 3) 和 (5 2 4) 合并为 (5 2 3,4)。我比较 (5 2 3,4) 和 (1 2 3,4,5)。我没有两列匹配,所以我停止合并。

现在假设我以不同的顺序合并。我合并 (1 2 3) 和 (1 2 4) 以产生 (1 2 3,4)。然后我合并 (5 2 3) 和 (5 2 4) 以产生 (5 2 3,4)。我看到我在这两个产品之间有一个匹配。然后我合并 (1 2 3,4) 和 (5 2 3,4) 以产生 (1,5 2 3,4)。发电机组的最终列表是 (1,5 2 3,4) 和 (1 2 5)。所以你看到合并顺序产生了两个不同的答案:(1,5 2 3,4) 和 (1 2 5) 与 (5 2 3,4) 和 (1 2 3,4,5)。

在这种情况下,我可能会选择任何一个答案,因为在每个答案中都会出现相同数量 (2) 的发电机组列表。但是,(1,5 2 3,4) 和 (1 2 5) 稍微更可取,因为 (1,5 2 3,4) 包含尽可能多的组合。然而,假设我们有一个包含 900 个组合的列表。问题的自下而上解决方案的合并顺序将导致您以非最佳方式减少问题,其中优化是集合列表中可能的最小计数。如果不查看所有可能的合并路径,很难知道合并顺序是什么,这并不比寻找匹配的蛮力方法更有效,我也尝试过这种方法。

我也尝试过蛮力方法。为什么蛮力法的效率让人无法接受?该方法首先在所有列中找到唯一整数列表。然后它生成这些整数的每个可能组合的幂集。它对列/集 A、列/集 B 和列/集 C 执行此操作。然后按大小对这些集进行排序(从大到小),计算每个集在其他列中的所有其他集之间的笛卡尔积,然后它循环遍历这些笛卡尔积,这些笛卡尔积由它们的生成集键入,以检查笛卡尔积是否与输入中的排列列表匹配。这大约是 O(n^3),其中 n=输入列表中的组合数。如果我什至可以在 O(n^2) 中做到这一点,与现在相比,这将是一场胜利。

就内存考虑而言。假设每个插槽的域是整数 1-12。三个插槽中不同组合的数量为 12!/3! 您正在查看超过 7900 万种原始组合。那是在它们甚至被 Google 的 Guava Collection API 划分为集合之前(我强烈推荐 BTW)。我们可能会以某种方式懒惰地生成集合,我感觉 Google 会这样做,但内存限制仍然很大。

我真的在寻找一种算法来解决这个问题。但是,如果有一个 Java 库或方法可以以最小的痛苦解决这个问题,我也愿意。谢谢。

4

1 回答 1

0

感谢大家的见解和答案。我想将此答案归功于 John Kolen ( http://www.johnfkolen.com ),他提出了如下可行的解决方案:

三元组最大覆盖率的贪心算法

输入:一个包含子集 A x B x C 的三元组集合,其中 A、B 和 C 是整数集合。

输出:一组 n 个三元组(A_i,B_i,C_i),其中 A_i 在 A,B_i 在 B,C_i 在 C,Union_i A_i x B_i x C_i = X 和 Intersection_i A_i x B_i x C_i = 空。

算法

该算法可以分为三个部分:寻找对覆盖,寻找三重覆盖,最后构建一个总覆盖。

从 B x C 的子集中查找对的覆盖涉及构建从 B 的元素到 C 集的映射。本质上是一个小的前缀树或 trie,这种数据结构可以很容易地找到一组前缀的最大覆盖。例如,如果 b_1->C_1 和 b_2->C_2,则涉及 b_1 和 b_2 的最大覆盖将是 <[b_1,b_2],C_1 交点 C_2>。

一旦我们可以找到最大对,那么就可以构造最大三元组。然而,这一次,前缀 (A) 映射到一组对 (BxC)。通过使用前面的方法,可以找到检查 A 的所有子集及其在后缀上的匹配对覆盖。

贪心方法找到局部最佳覆盖并将其添加到解决方案集中。当前覆盖捕获的三元组被从考虑中删除,并且该过程继续进行,直到局部最佳覆盖是单例。剩余的三元组被添加到解决方案中。

附上相关代码。

import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;

class Triples {
    private ArrayList<int[]> _data;
    private HashMap<Integer,HashSet<Integer>> _tree;

    /**
     * Instantiates a container of triples read from the provided file.
     * <p>
     * File should contain lines of the form '[#,#,#]', where # is an
     * integer. Spaces are ignored.
     *
     * @param filename a path to a file containing triples
     */
    public Triples(String filename) {
    _data = new ArrayList<int[]>();
    _tree = new HashMap<Integer, HashSet<Integer>>();
    readfile(filename);
    }

    /**
     * Returns the number of triples.
     *
     * @return the number of triples.
     */
    int size() {
    return _data.size();
    }

    /**
     * Returns a set of encoded pairs (b,c) where (first, b, c) is in the
     * set of triples. The pairs are encoded as integers using b * 255 + c.
     *
     * @param  first  the first value of a triple
     * @return        a set of pair encondig
     */
    HashSet<Integer> tree(int first) {
    return _tree.get(first);
    }

    public class PairCover 
    {
    public ArrayList<Integer> a;
    public ArrayList<Integer> b;
    PairCover() 
    {
        a = b = null;
    }
    PairCover(ArrayList<Integer> ax, ArrayList<Integer> bx) 
    {
        a = ax;
        b = bx;
    }
    /**
     * Returns the number of pairs covered by this cover.
     *
     * @return the number of pairs covered by this cover.
     */
    public int score()
    {
        if (a != null && b != null)
        return a.size() * b.size();
        else
        return 0;
    }
    public String toString() {
        return "<"+a+","+b+">";
    }
    }

    /**
     * Compute the maximal pair covering (B,C) for a set of suffix pairs.
     *
     * @return pair covering where |BxC| is maximized.
     */
    public PairCover maxPairCover(HashSet<Integer> set) {
    // unpack the pairs
    HashMap<Integer,NSet> pairs = new HashMap<Integer,NSet>();
    for(Integer value : set) {
        Integer a = value / 256;
        Integer b = value & 255;
        if (!pairs.containsKey(a))
        pairs.put(a, new NSet());
        pairs.get(a).add(b);
    }

    _mpc_best = new PairCover();
    Integer[] remain_ary = pairs.keySet().toArray(new Integer[0]);

    // recursively examine all subsets pair first values and find matching
    // second value groups.
    maxPairCoverRec(pairs,
            new ArrayList<Integer>(),
            new ArrayList<Integer>(Arrays.asList(remain_ary)),
            null);

    return _mpc_best;
    }

    /**
     * Recursively compute the maximal pair covering (B,C) for a set of 
     * suffix pairs from a set of active prefixes by combining all possible
     * subsets of the remaining prefixes.
     * <p>
     * Stores the best pair cover in _mpc_best. This "global" variable makes it
     * easy to prune search.
     *
     * @param pairs tree-compressed set of pairs
     * @param active currently active pair prefixes
     * @param remain set of pair prefixes to explore
     * @param set the pair suffixes shared by all the active prefixes
     * @return pair covering where |BxC| is maximized.
     */
    void maxPairCoverRec(HashMap<Integer,NSet> pairs,
                 ArrayList<Integer> active,
                 ArrayList<Integer> remain,
                 NSet set) 
    {
    if (set != null) {
        // Prune search if suffix set is empty or that there is no way
        // to generate a better pair cover than the current cover.
        if (set.isEmpty() ||
        (active.size() + remain.size()) * set.size()
        <= _mpc_best.score())
        return ;
    }
    if (remain.isEmpty()) {
        if (active.size() > 0) {
        // Save the current pair cover if it's better than the current
        // cover.
        if (_mpc_best.score() < set.size() * active.size()) {
            _mpc_best.a = (ArrayList<Integer>)(active.clone());
            _mpc_best.b = (ArrayList<Integer>)(set.to_array_list());
        }
        }
        return;
    }

    ArrayList<Integer> active_r = (ArrayList<Integer>)(active.clone());
    ArrayList<Integer> remain_r = (ArrayList<Integer>)(remain.clone());
    Integer a = remain_r.remove(0);
    // Explore active sets without element a.
    maxPairCoverRec(pairs, active_r, remain_r, set);

    // Explore active sets with element a. Perform intersection of 
    // current suffix set with suffixes of a.
    if (set == null) {
        set = pairs.get(a);
    }
    else {
        set = set.intersection(pairs.get(a));
    }
    active_r.add(a);
    maxPairCoverRec(pairs, active_r, remain_r, set);
    }

    public class TripleCover 
    {
    public ArrayList<Integer> a;
    public ArrayList<Integer> b;
    public ArrayList<Integer> c;
    TripleCover() 
    {
        a = b = c = null;
    }
    TripleCover(ArrayList<Integer> ax,
            ArrayList<Integer> bx,
            ArrayList<Integer> cx) 
    {
        a = ax;
        b = bx;
        c = cx;
    }
    TripleCover(int ax,
            int bx,
            int cx) 
    {
        a = new ArrayList<Integer>();
        a.add(ax);
        b = new ArrayList<Integer>();
        b.add(bx);
        c = new ArrayList<Integer>();
        c.add(cx);
    }
    /**
     * Returns the number of pairs covered by this cover.
     *
     * @return the number of pairs covered by this cover.
     */
    public int score()
    {
        if (a != null && b != null && c != null)
        return a.size() * b.size() * c.size();
        else
        return 0;
    }
    public String toString() {
        return "<"+a+","+b+","+c+">";
    }
    }

    /**
     * Compute the maximal triple cover for the pairs currently stored
     * in _tree.
     *
     * @return a triple cover with |AxBxC| maximized
     */
    TripleCover maxTripleCover() {
    _mtc_best = new TripleCover();
    Integer[] remain_ary = _tree.keySet().toArray(new Integer[0]);
    ArrayList<Integer> remain = 
        new ArrayList<Integer>(Arrays.asList(remain_ary));
    maxTripleCoverRec(new ArrayList<Integer>(),
                 remain,
                 null);
    return _mtc_best;
    }

    /**
     * Recursively explore all subsets of values in first position and
     * find the largest cover for the second and third positions.
     * <p>
     * Stores the best triple cover in _mtc_best. This "global" variable 
     * makes it easy to prune search.
     *
     * @param active the active values of the first position
     * @param remain the additional values of the first position to explore
     * @param set the current set of second/third position pairs that are shared by the active values
     */
    void maxTripleCoverRec(ArrayList<Integer> active,
               ArrayList<Integer> remain,
               HashSet<Integer> set) {
    if (set != null &&
        (set.isEmpty() ||
         (remain.size()>0 && 
          (active.size() + remain.size()) * set.size()
          <= _mtc_best.score()))){
        return;
    }
    if (remain.isEmpty()) {
        if (active.size() > 0) {
        PairCover mpc = maxPairCover(set);
        if (_mtc_best.score() < mpc.score() * active.size()) {
            _mtc_best.a = (ArrayList<Integer>)(active.clone());
            _mtc_best.b = mpc.a;
            _mtc_best.c = mpc.b;
        }
        }
        return;
    }

    ArrayList<Integer> active_r = (ArrayList<Integer>)(active.clone());
    ArrayList<Integer> remain_r = (ArrayList<Integer>)(remain.clone());
    Integer a = remain_r.remove(0);
    maxTripleCoverRec(active_r, remain_r, set);
    if (set == null) {
        set = (HashSet<Integer>)(_tree.get(a).clone());
    }
    else {
        set = (HashSet<Integer>)(set.clone());
        set.retainAll(_tree.get(a));
    }
    active_r.add(a);
    maxTripleCoverRec(active_r, remain_r, set);

    }

    /**
     * Finds a covering set for the triples using a greedy approach.
     * The largest cover of the current triples is found, recorded, and
     * the affected triples are removed from consideration. This process
     * continues until singleton covers are left.
     *
     * @return a list of covers
     */
    ArrayList<TripleCover> greedy() {
    // Since the prefix tree is modified as new covers are found,
    // we make a copy
    HashMap<Integer,HashSet<Integer>> old_tree = _tree;
    _tree = (HashMap<Integer,HashSet<Integer>>)(_tree.clone());

    ArrayList<TripleCover> solution = new ArrayList<TripleCover>();
    TripleCover cover;
    do {
        cover = maxTripleCover();  // find best
        solution.add(cover);  // record it
        remove(cover);  // remove covered triples from tree
        System.out.println("" + cover + " ==> " + cover.score());
    } while (cover.score() > 1);

    // Nothing left but singletons, so add them to solution
    for(Integer a : _tree.keySet()) {
        for(Integer pair : _tree.get(a)) {
        int b = pair / 256;
        int c = pair & 255;
        solution.add(new TripleCover(a,b,c));
        }
    }

    // restore prefix tree
    _tree = old_tree; 

    return solution;
    }

    //************************************************************

    private PairCover _mpc_best;
    private TripleCover _mtc_best;


    /**
     * Reads triples from the specified file. Will exit if file does not
     * exist.
     *
     * @param filename a path to a file containing triples
     */
    private void readfile(String filename) {
    try {
        FileReader fr = new FileReader(filename);
        BufferedReader reader = new BufferedReader(fr);
        String line = null;
        while ((line = reader.readLine()) != null) {
        line = line.replace("[","").replace("]","").replace(" ","");
        String[] svalues = line.split(",");
        int[] values = new int[3];
        values[0] = Integer.parseInt(svalues[0]);
        values[1] = Integer.parseInt(svalues[1]);
        values[2] = Integer.parseInt(svalues[2]);
        _data.add(values);
        Integer key = new Integer(values[0]);
        if (!_tree.containsKey(key)) {
            _tree.put(key, new HashSet<Integer>());
        }
        _tree.get(key).add(values[1] * 256 + values[2]);
        }
    } catch (IOException e) {
        System.out.println("Could not open " + filename);
        System.exit(1);
    }
    }

    /**
     * Removes covered triples from the prefix tree
     *
     * @param tc a covering
     */
    private void remove(TripleCover tc) {
    for(Integer a : tc.a) {
        HashSet<Integer> set = _tree.get(a);
        for(Integer b : tc.b) {
        for(Integer c : tc.c) {
            set.remove(b * 256 + c);
        }
        }
        if (set.isEmpty()) {
        _tree.remove(a);
        }
    }
    }

}
于 2014-01-06T19:34:28.693 回答