生成按属性排序的组合

时间:2009-02-07 13:52:43

标签: sum combinatorics combinations

我正在寻找一种方法来生成由单个属性排序的对象组合。我不认为词典顺序是我正在寻找的...我会试着举个例子。假设我有一个对象列表A,B,C,D,其中我想要通过3,3,2,1来排序属性值。这给出了A3,B3,C2,D1对象。现在我想生成2个对象的组合,但它们需要以降序排列:

  • A3 B3
  • A3 C2
  • B3 C2
  • A3 D1
  • B3 D1
  • C2 D1

生成所有组合并对其进行排序是不可接受的,因为真实场景涉及大型集合和数百万种组合。 (40的一组,8的顺序),我只需要高于特定阈值的组合。

实际上我需要计算超过阈值的组合,这些组合按给定属性的总和分组,但我认为这样做要困难得多 - 所以我决定开发超过阈值的所有组合并计算它们。如果可能的话。

编辑 - 我原来的问题不是很精确......我实际上并不需要这些组合排序,只是认为它有助于隔离超过阈值的组合。更准确地说,在上面的例子中,给出一个阈值5,我正在寻找一个信息,给定的集合产生1个组合,总和为6(A3 B3),2为总和5(A3 C2, B3 C2)。我实际上并不需要这些组合。

我正在调查子集和问题,但是如果我理解正确的动态解决方案它只会给你信息是否有给定的总和或没有,不计算总和。

由于

6 个答案:

答案 0 :(得分:2)

实际上,我认为你需要词典顺序,而是降序而不是提升。另外:

  • 我从你的描述中不清楚A,B,...... D在你的答案中扮演任何角色(除了可能作为价值观的容器)。
  • 我认为您的问题示例只是“对于每个至少为5的整数,最多可能有两个值的总和,来自集合{3,3,2,1}的多少个不同的对具有该整数的总和? “
  • 有趣的部分是早期救助,一旦找不到可能的解决办法(剩余可实现的数额太小)。

我稍后会发布示例代码。

以下是我承诺的示例代码,以下是一些评论:

public class Combos {

    /* permanent state for instance */
    private int values[];
    private int length;

    /* transient state during single "count" computation */
    private int n;
    private int limit;
    private Tally<Integer> tally;
    private int best[][];  // used for early-bail-out

    private void initializeForCount(int n, int limit) {
        this.n = n;
        this.limit = limit;
        best = new int[n+1][length+1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= length - i; ++j) {
                best[i][j] = values[j] + best[i-1][j+1];
            }
        }
    }

    private void countAt(int left, int start, int sum) {
        if (left == 0) {
            tally.inc(sum);
        } else {
            for (
                int i = start;
                i <= length - left
                && limit <= sum + best[left][i];  // bail-out-check
                ++i
            ) {
                countAt(left - 1, i + 1, sum + values[i]);
            }
        }
    }

    public Tally<Integer> count(int n, int limit) {
        tally = new Tally<Integer>();
        if (n <= length) {
            initializeForCount(n, limit);
            countAt(n, 0, 0);
        }
        return tally;
    }

    public Combos(int[] values) {
        this.values = values;
        this.length = values.length;
    }

}

前言备注:

这使用一个名为Tally的小助手类,它只隔离列表(包括从未见过的键的初始化)。我会把它放在最后。

为了保持这种简洁,我采用了一些对于“真正的”代码不是一个好习惯的快捷方式:

  • 这不会检查空值数组等
  • 我假设值数组已按降序排序,这是早期纾困技术所必需的。 (良好的生产代码将包括排序。)
  • 我将瞬态数据放入实例变量中,而不是将它们作为支持count的私有方法中的参数传递。这使得这个类非线程安全。

说明:

创建Combos的实例,并使用(降序排列的)整数数组进行组合。每个实例设置一次value数组,但可以使用不同的种群大小和限制对count进行多次调用。

count方法触发n valueslimit整数组的唯一组合的(大部分)标准递归遍历。 countAt参数给出了感兴趣的总和的下限。

values方法检查来自left的整数组合。 n参数是总计start个整数剩余的整数,valuessum中要搜索的位置,best是部分金额。

早期纾困机制基于计算best[n][p],这是一个二维数组,用于指定从给定状态可到达的“最佳”总和。 n中的值是原p位置values开始的countAt值的最大总和。

当正确的人口积累时,sum的底部递归;这会将当前ntally值)添加到countAt。如果values未达到最低点,它会从start位置扫过sum以增加当前的部分values,只要:

  • 足够的位置保留在best以实现指定的人口,
  • 剩余的limit(最大)小计足以构成 int[] values = {3, 3, 2, 1}; Combos mine = new Combos(values); Tally<Integer> tally = mine.count(2, 5); for (int i = 5; i < 9; ++i) { int n = tally.get(i); if (0 < n) { System.out.println("found " + tally.get(i) + " sums of " + i); } }

运行问题数据的示例:

found 2 sums of 5
found 1 sums of 6

生成您指定的结果:

public static class Tally<T> {
    private Map<T,Integer> tally = new HashMap<T,Integer>();
    public Tally() {/* nothing */}
    public void inc(T key) {
        Integer value = tally.get(key);
        if (value == null) {
            value = Integer.valueOf(0);
        }
        tally.put(key, (value + 1));
    }
    public int get(T key) {
        Integer result = tally.get(key);
        return result == null ? 0 : result;
    }
    public Collection<T> keys() {
        return tally.keySet();
    }
}

这是Tally代码:

{{1}}

答案 1 :(得分:1)

我编写了一个类来处理使用二项式系数的常用函数,这是您的问题所处的问题类型。它执行以下任务:

  1. 以任意N选择K到文件的格式输出所有K索引。 K索引可以用更具描述性的字符串或字母代替。这种方法使解决这类问题变得非常简单。

  2. 将K索引转换为已排序二项系数表中条目的正确索引。这种技术比依赖迭代的旧发布技术快得多。它通过使用Pascal三角形中固有的数学属性来实现。我的论文谈到了这一点。我相信我是第一个发现和发布这种技术的人,但我可能错了。

  3. 将已排序的二项系数表中的索引转换为相应的K索引。

  4. 使用Mark Dominus方法计算二项式系数,这样就不太可能溢出并使用更大的数字。

  5. 该类是用.NET C#编写的,它提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用名为InitTable的bool值,当为true时,将创建一个通用列表来保存要管理的对象。如果此值为false,则不会创建表。不需要创建表来执行上述4种方法。提供访问者方法来访问该表。

  6. 有一个关联的测试类,它显示了如何使用该类及其方法。它已经过2个案例的广泛测试,并且没有已知的错误。

  7. 要阅读此课程并下载代码,请参阅Tablizing The Binomial Coeffieicent

答案 2 :(得分:0)

在stackoverflow中查看此问题:Algorithm to return all combination s

我还使用下面的java代码生成所有排列,但它可以很容易地用于生成给定索引的唯一组合。

public static <E> E[] permutation(E[] s, int num) {//s is the input elements array and num is the number which represents the permutation

    int factorial = 1;

    for(int i = 2; i < s.length; i++)
        factorial *= i;//calculates the factorial of (s.length - 1)

    if (num/s.length >= factorial)// Optional. if the number is not in the range of [0, s.length! - 1] 
        return null;

    for(int i = 0; i < s.length - 1; i++){//go over the array

        int tempi = (num / factorial) % (s.length - i);//calculates the next cell from the cells left (the cells in the range [i, s.length - 1])
        E temp = s[i + tempi];//Temporarily saves the value of the cell needed to add to the permutation this time 

        for(int j = i + tempi; j > i; j--)//shift all elements to "cover" the "missing" cell
            s[j] = s[j-1];

        s[i] = temp;//put the chosen cell in the correct spot

        factorial /= (s.length - (i + 1));//updates the factorial

    }

    return s;
}

答案 3 :(得分:0)

我非常抱歉(在评论中的所有澄清之后)说我无法找到解决此问题的有效方法。我在过去的一个小时里试过没有结果。

原因(我认为)是这个问题非常类似于旅行商问题。除非您尝试所有组合,否则无法知道哪些属性会累加到阈值。

似乎没有聪明的技巧可以解决这类问题。

您仍可以对实际代码进行许多优化。

尝试根据属性对数据进行排序。当您发现较高的值无法满足阈值时,您可以避免从列表中处理某些值(因此可以消除所有较低的值)。

答案 4 :(得分:0)

这是计算这些子集数量的递归方法:我们定义一个函数count(minIndex,numElements,minSum),它返回大小为numElements的子集数量,其总和至少为{ {1}},包含索引为minSum或更高的元素。

在问题陈述中,我们按降序对元素进行排序,例如[3,3,2,1],并调用第一个索引零,以及元素N的总数。我们假设所有元素都是非负的。要查找总和至少为5的所有2个子集,我们会调用minIndex

示例代码(Java):

count(0,2,5)

顺便说一句,我运行了上面的40个元素的数组,以及8个子集,并且在不到一秒的时间内一直得到结果。

答案 5 :(得分:0)

如果你正在使用C#,那么有一个相当不错的泛型库here。请注意,某些排列的生成不是按字典顺序排列