组合问题

时间:2011-05-12 20:32:03

标签: algorithm combinations combinatorics

我认为这是一个“枚举组合”问题。

我需要从15个中重复选择7个元素,我想知道是否有一种简单的方法可以将所有组合存储在数组中并直接找到我感兴趣的元素。

基本上我正在构建一个大的查找表(包含非常昂贵的计算值),我想知道我是否可以使用一个简单的公式访问它(注意这不是功课,检查我的个人资料)。

15个中有7个重复的组合数量是116 280(我仔细检查它是否正确)。

以下是代码:

public static void main(String[] args) {
    final Random r = new Random( System.currentTimeMillis() );
    final List<String> ls = new ArrayList<String>();
    for (int i = 0; i < 15; i++) {
        for (int j = i; j < 15; j++) {
            for (int k = j; k < 15; k++) {
                for (int l = k; l < 15; l++) {
                    for (int m = l; m < 15; m++) {
                        for (int n = m; n < 15; n++) {
                            for (int o = n; o < 15; o++) {
                                ls.add( i + " " + j + " " + k + " " + l + " " + m + " " + n + " " + o + ": " + r.nextLong() );
                            }
                        }
                    }
                }
            }
        }
    }
    System.out.println( "We have " + ls.size() + " entries" );
    System.out.println( "Entry @ 5,7,2,10,11,8,3 is " + getEntryAt(5,7,2,10,11,8,3) );
}

private static String getEntryAt( int i, int j, int k, int l, int m, int n, int o ) {
    return "FILL ME"; // What goes here ?
}

在上面的例子中,我只是在查找数组中放置一个随机值,但基本上就是这样:我想得到,比方说,(5,7,2,10,11,8,3),我可以吗? “计算”很容易就是它的位置?

请注意,我在数组中存储元素的方式并不重要:我可以按照制作最快“公式”的方式存储它们,如果有的话。

如果有人知道我在说什么,那么任何帮助都会受到欢迎。

5 个答案:

答案 0 :(得分:3)

分解它的简单方法是将计数总和为零件。 (对于我的例子,我使用了基于1的索引)

假设您(元数字较少,但原理相同)元组(2,3,4)。它的位置只是以下的总和:

  • (1,x,y) - 适合此
  • 的所有数字
  • (2,2,x)
  • (2,3,x) - 其中x < 4

你可以反复思考这个问题。

现在,对于D = 3位数和K项,您可以绘制出模式并看看它是如何增长的:

K = 1

1 1 1

K = 2

1 1 1
1 1 2

1 2 2

2 2 2

K = 3

1 1 1
1 1 2
1 1 3

1 2 2
1 2 3

1 3 3

2 2 2
2 2 3

2 3 3

3 3 3

对于每次迭代,你所做的实际上是采用先前的分组(包括空分组)并添加一个额外的数字 - 如三角形数字序列。当你增加第一个数字时,你甚至可以递归地想到这一点 - 在上面D = 3,K = 3的例子中,你可以重新映射不以“1”开头的东西的符号,从而它们不包含任何“1” - D仍然是3,但K现在是2:

K = 3 (ignoring 1's)

2 2 2
2 2 3

2 3 3

3 3 3

变为:

K = 2

1 1 1
1 1 2

1 2 2

2 2 2

这是你要添加到K的方法。(对于D = 3,注意它们是三角形数字。)

添加数字怎么样?好吧,对于K = 3,D = 3,你可以想象,鉴于此:

K = 3

1 1 1
1 1 2
1 1 3

1 2 2
1 2 3

1 3 3

2 2 2
2 2 3

2 3 3

3 3 3

并在它们前面添加一个数字。您可以在所有这些前面添加“1”。您只能在“2”或更高的前面添加“2”,在只有“3”的前面添加“3”。现在你可以看到递归结构了。

举一个简单的例子,找到(2,4,4)索引,D = 3,K = 5:

index( 2, 4, 4 ) =
   # number of leading 1's, and re-index
   index( 3, 3 ) + count( D = 2, K = 5 ) =
   index( 3, 3 ) + 15 =
   # number of leading 1's and 2's, and re-index
   index( 1 ) + count( D = 1, K = 4 ) + count( D = 1, K = 3 ) + 15 =
   index( 1 ) + 4 + 3 + 15 = index( 1 ) + 22 = 
   22

索引(2,4,4)= 22

现在棘手的部分是计算计数(D,K),实际上只是C(K + D - 1,D)。您现在可以将其概括为K = 15,D = 7.

// This is actually 0-based.
// Really should use an array or something to make it easy to generalize, 
// so I'm going to skip a lot of cut and paste
private static int getEntryAt( int i, int j, int k, int l, int m, int n, int o ) {
   int D = 7, K = 15;
   int total = 0;

   if ( i > 0 ) {
      for ( int index = 0; index < i; index++ ) {
         total += count( D, K - index );
      }
   }

   j -= i, k -= i, l -= i, m -= i, n -= i, o -= i;
   D--;
   K -= i;
   // repeat for j, k, ...

   return count;
}

答案 1 :(得分:0)

long[,,,,,,] ls = new long[15, 15, 15, 15, 15, 15, 15];

并在深度嵌套的for循环中:

ls[i, j, k, l, m, n, o] = r.nextLong();

对于你的get语句,它就像:

一样简单
return ls[i, j, k, l, m, n, o];

但是为此,ls需要作为参数传递给getter函数,或者它需要是全局的。

答案 2 :(得分:0)

构建一个高度为7的树。根节点有15个子节点,名为1-15。每个子名称对应于集合中该数字的选择。编号为n的节点具有数字为m的子节点(n <= m <= 15) 在树的叶子(所有116 280个,都有深度7),您链接到该组合的预先计算的解决方案。

查找给定集合的解决方案然后需要您在树中跟踪相关路径,这可以在恒定时间内完成。

答案 3 :(得分:0)

也许您可以使用combinatorial number system来解决问题?

答案 4 :(得分:-1)

好像你想要一个Map<Multiset,T>,其中T是你的计算价值昂贵的类型。 HashMap<Multiset,T>会在内部使用数组,所以我猜这是一个有效的答案。

使密钥成为Multiset的关键是多集合上的equals()方法应该考虑两个多重集合,如果它们包含相同数量的每个元素,则忽略排序。 (顺便说一下,JDK没有Multiset类,但你可以找到第三方Multiset类。)