我想编写一个函数,该函数接受字母数组作为参数以及要选择的这些字母的数量。
比如你提供了一个由8个字母组成的数组,并希望从中选择3个字母。那么你应该得到:
8! / ((8 - 3)! * 3!) = 56
由3个字母组成的返回数组(或单词)。
我想编写一个函数,该函数接受字母数组作为参数以及要选择的这些字母的数量。
比如你提供了一个由8个字母组成的数组,并希望从中选择3个字母。那么你应该得到:
8! / ((8 - 3)! * 3!) = 56
由3个字母组成的返回数组(或单词)。
《计算机程序设计艺术》第4卷:分册3中有很多可能比我描述的更适合你特定情况的内容。
当然,你会遇到一个问题,那就是内存。在你的集合中,当元素数量达到20个时,很快就会出现问题——20C3 = 1140。如果你想遍历整个集合,最好使用一种修改过的格雷码算法,这样你就不需要将所有组合都保存在内存中。这些算法可以从前一个组合生成下一个组合,并避免重复。针对不同的用途,有很多种格雷码算法可供选择。我们是想最大化连续组合之间的差异吗?还是最小化?等等。
以下是一些描述格雷码的原始论文:
以下是一些涉及该主题的其他论文:Phillip J Chase, 《{{link5:算法382:从N个对象中选择M个的组合》(1970)
C语言中的算法...
您还可以通过索引(按字典顺序)引用组合。意识到索引应该是从右到左的某种变化量,我们可以构建一个可以恢复组合的东西。
所以,我们有一个集合{1,2,3,4,5,6}...,我们想要三个元素。假设{1,2,3},我们可以说元素之间的差异是一个,并且是按顺序和最小的。{1,2,4}有一个变化,并且在字典顺序中是第2个。因此,最后一位的'变化'数量对字典顺序中的一个变化进行计数。第二位,有一个变化{1,3,4},有一个变化,但由于它在第二位(与原始集合中的元素数量成比例),所以它对变化的贡献更大。
我所描述的方法是一种解构,从集合到索引,我们需要做相反的操作——这要困难得多。这就是Buckles解决这个问题的方式。我写了一些C来计算它们,只是做了一些小的改动——我使用了集合的索引而不是数字范围来表示集合,所以我们总是从0...n开始工作。还有另一种方法:它的概念更容易理解和编程,但没有Buckles的优化。幸运的是,它也不会产生重复的组合。
举个例子:27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
。所以,第27个字典序的四个元素组合是:{1,2,5,6},这些是你想要查看的集合的索引。下面是一个例子(OCaml),需要choose
函数,请读者自行实现:
(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
(* maximize function -- maximize a that is aCb *)
(* return largest c where c < i and choose(c,i) <= z *)
let rec maximize a b x =
if (choose a b ) <= x then a else maximize (a-1) b x
in
let rec iterate n x i = match i with
| 0 -> []
| i ->
let max = maximize n i x in
max :: iterate n (x - (choose max i)) (i-1)
in
if x < 0 then failwith "errors" else
let idxs = iterate (List.length set) x k in
List.map (List.nth set) (List.sort (-) idxs)
k
的限制。let iter_combs n k f =
let rec iter v s j =
if j = k then f v
else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
iter [] 0 0
let fold_combs n k f x =
let rec loop i s c x =
if i < n then
loop (i+1) s c @@
let c = i::c and s = s + 1 and i = i + 1 in
if s < k then loop i s c x else f c x
else x in
loop 0 0 [] x
在C#中:
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] {e}).Concat(c)));
}
使用方法:
var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3);
结果:
123
124
125
134
135
145
234
235
245
345
var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
。 - Dave CousineauJava简短解决方案:
import java.util.Arrays;
public class Combination {
public static void main(String[] args){
String[] arr = {"A","B","C","D","E","F"};
combinations2(arr, 3, 0, new String[3]);
}
static void combinations2(String[] arr, int len, int startPosition, String[] result){
if (len == 0){
System.out.println(Arrays.toString(result));
return;
}
for (int i = startPosition; i <= arr.length-len; i++){
result[result.length - len] = arr[i];
combinations2(arr, len-1, i+1, result);
}
}
}
结果将会是
[A, B, C]
[A, B, D]
[A, B, E]
[A, B, F]
[A, C, D]
[A, C, E]
[A, C, F]
[A, D, E]
[A, D, F]
[A, E, F]
[B, C, D]
[B, C, E]
[B, C, F]
[B, D, E]
[B, D, F]
[B, E, F]
[C, D, E]
[C, D, F]
[C, E, F]
[D, E, F]
我可以介绍一下我用Python写的递归解法吗?
def choose_iter(elements, length):
for i in xrange(len(elements)):
if length == 1:
yield (elements[i],)
else:
for next in choose_iter(elements[i+1:], length-1):
yield (elements[i],) + next
def choose(l, k):
return list(choose_iter(l, k))
使用示例:
>>> len(list(choose_iter("abcdefgh",3)))
56
我喜欢它的简洁性。
len(tuple(itertools.combinations('abcdefgh',3)))
在Python中可以用更少的代码实现相同的功能。 - hgus1294for i in xrange(len(elements) - length + 1):
吗?虽然在Python中超出切片索引会被优雅地处理,但这才是正确的算法。 - Stephan Dollberg假设您的字母数组看起来像这样:“ABCDEFGH”。您有三个索引(i,j,k),指示您将使用哪些字母来构成当前单词。您从以下位置开始:
A B C D E F G H ^ ^ ^ i j k
首先你变化 k,所以下一步看起来像这样:
A B C D E F G H ^ ^ ^ i j k
如果您到达了末尾,那么您会继续变化 j,然后再次变化 k。
A B C D E F G H ^ ^ ^ i j k
A B C D E F G H ^ ^ ^ i j k
一旦 j 达到 G,也开始变化 i。
A B C D E F G H ^ ^ ^ i j k
A B C D E F G H ^ ^ ^ i j k ...
用代码写出来类似于这样:
void print_combinations(const char *string)
{
int i, j, k;
int len = strlen(string);
for (i = 0; i < len - 2; i++)
{
for (j = i + 1; j < len - 1; j++)
{
for (k = j + 1; k < len; k++)
printf("%c%c%c\n", string[i], string[j], string[k]);
}
}
}
i
i
与从大于 i
的元素集合中递归选择的每个 k-1
元素组合相结合。i
执行上述迭代。i
更大,以避免重复。这样,[3,5] 将只被选一次,作为 [3] 与 [5] 组合而成,而不是两次(该条件消除了 [5] + [3])。如果没有此条件,则会得到变化而不是组合。Python中的简短示例:
def comb(sofar, rest, n):
if n == 0:
print sofar
else:
for i in range(len(rest)):
comb(sofar + rest[i], rest[i+1:], n-1)
>>> comb("", "abcde", 3)
abc
abd
abe
acd
ace
ade
bcd
bce
bde
cde
为了解释,下面用一个例子来描述递归方法:
例子:A B C D E
所有长度为3的组合如下:
#include <algorithm>
template <typename Iterator>
bool next_combination(const Iterator first, Iterator k, const Iterator last)
{
/* Credits: Mark Nelson http://marknelson.us */
if ((first == last) || (first == k) || (last == k))
return false;
Iterator i1 = first;
Iterator i2 = last;
++i1;
if (last == i1)
return false;
i1 = last;
--i1;
i1 = k;
--i2;
while (first != i1)
{
if (*--i1 < *i2)
{
Iterator j = k;
while (!(*i1 < *j)) ++j;
std::iter_swap(i1,j);
++i1;
++j;
i2 = k;
std::rotate(i1,j,last);
while (last != j)
{
++j;
++i2;
}
std::rotate(k,i2,last);
return true;
}
}
std::rotate(first,k,last);
return false;
}
它可以像这样使用:
#include <string>
#include <iostream>
int main()
{
std::string s = "12345";
std::size_t comb_size = 3;
do
{
std::cout << std::string(s.begin(), s.begin() + comb_size) << std::endl;
} while (next_combination(s.begin(), s.begin() + comb_size, s.end()));
return 0;
}
123
124
125
134
135
145
234
235
245
345
我发现这个帖子很有用,想给出一个JavaScript解决方案,可以将其插入到Firebug中。根据您的JS引擎,如果起始字符串很大,可能需要一些时间。
function string_recurse(active, rest) {
if (rest.length == 0) {
console.log(active);
} else {
string_recurse(active + rest.charAt(0), rest.substring(1, rest.length));
string_recurse(active, rest.substring(1, rest.length));
}
}
string_recurse("", "abc");
输出应该如下:
abc
ab
ac
a
bc
b
c
static IEnumerable<string> Combinations(List<string> characters, int length)
{
for (int i = 0; i < characters.Count; i++)
{
// only want 1 character, just return this one
if (length == 1)
yield return characters[i];
// want more than one character, return this one plus all combinations one shorter
// only use characters after the current one for the rest of the combinations
else
foreach (string next in Combinations(characters.GetRange(i + 1, characters.Count - (i + 1)), length - 1))
yield return characters[i] + next;
}
}