Algorithm to return all combinations of k elements from n
我想编写一个函数,它将字母数组作为参数,并选择一些字母。
假设您提供一个8个字母的数组,并希望从中选择3个字母。然后你应该得到:
1 | 8! / ((8 - 3)! * 3!) = 56 |
返回的数组(或单词),每个数组由3个字母组成。
《计算机编程艺术》第4卷:第3分册有大量这样的内容,可能比我描述的更适合你的特定情况。
格雷码您将遇到的一个问题当然是内存问题,而且速度非常快,在您的集合中有20个元素——20c3=1140。如果你想在集合上迭代,最好使用一个修改过的灰色代码算法,这样你就不会把它们都保存在内存中。它们从前面生成下一个组合,避免重复。其中有许多用于不同的用途。我们想最大化连续组合之间的差异吗?减少?等等。
一些描述灰色代码的原始文件:
以下是其他一些涉及该主题的论文:
蔡斯的旋转(算法)
Phillip J Chase,"算法382:n个对象中m个的组合"(1970)
C语言中的算法…
词典编纂顺序组合索引(Buckles算法515)还可以通过其索引引用组合(按词典编纂顺序)。意识到索引应该是基于索引从右到左的一些变化,我们可以构建一些应该恢复组合的内容。
所以,我们有一套1,2,3,4,5,6…我们需要三种元素。假设1,2,3我们可以说元素之间的差异是1,有序和最小的。1,2,4有一个变化,在字典上是数字2。因此,最后一个位置的"变化"数量说明了词典编纂顺序的一个变化。第二个位置,一个变化1,3,4有一个变化,但由于它在第二个位置(与原始集合中的元素数量成比例),所以变化更多。
我描述的方法是一种解构主义,从集合到索引,我们需要做相反的事情——这要复杂得多。这就是扣环解决问题的方法。我写了一些C来计算它们,只是做了一些小的改动——我使用集合的索引而不是数字范围来表示集合,所以我们总是从0…n开始工作。注:
按字典编纂顺序排列的组合索引(McCaffrey)
还有另一种方法:它的概念更容易理解和编程,但它没有扣环的优化。幸运的是,它也不会产生重复的组合:
使最大化的集合,其中最大化。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (* 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) |
一个简单的小组合迭代器
以下两种算法用于教学目的。它们在所有组合上实现迭代器和(更一般的)文件夹。它们尽可能快,具有复杂性o(nck)。内存消耗受
我们将从迭代器开始,它将为每个组合调用用户提供的函数
1 2 3 4 5 | 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 |
更一般的版本将从初始状态开始调用用户提供的函数以及状态变量。因为我们需要在不同的状态之间传递状态,所以不会使用for循环,而是使用递归,
1 2 3 4 5 6 7 8 | 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中:
1 2 3 4 5 6 | 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))); } |
用途:
1 | var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3); |
结果:
1 2 3 4 5 6 7 8 9 10 | 123 124 125 134 135 145 234 235 245 345 |
JAVA短解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); } } } |
结果将是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [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解决方案吗?
1 2 3 4 5 6 7 8 9 | 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:len(elements)], length-1): yield (elements[i],) + next def choose(l, k): return list(choose_iter(l, k)) |
示例用法:
1 2 | >>> len(list(choose_iter("abcdefgh",3))) 56 |
我喜欢它的简单。
假设您的字母数组如下所示:"abcdefgh"。你有三个索引(i,j,k),表示你要用哪个字母来表示当前单词,你从以下开始:
1 2 3 | A B C D E F G H ^ ^ ^ i j k |
首先你改变k,所以下一步看起来是这样的:
1 2 3 | A B C D E F G H ^ ^ ^ i j k |
如果你到了终点,你继续改变j,然后再改变k。
1 2 3 4 5 6 7 | A B C D E F G H ^ ^ ^ i j k A B C D E F G H ^ ^ ^ i j k |
一旦你达到G,你也开始改变I。
1 2 3 4 5 6 7 8 | A B C D E F G H ^ ^ ^ i j k A B C D E F G H ^ ^ ^ i j k ... |
用代码写的这个看起来像那样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 ", string[i], string[j], string[k]); } } } |
以下递归算法从有序集中选取所有k元素组合:
- 选择组合的第一个元素
i - 将
i 与从大于i 的元素集合中递归选择的k-1 元素的每个组合结合起来。
对集合中的每个
重要的是,您选择其他元素大于
我发现这个线程很有用,我想我可以添加一个JavaScript解决方案,您可以将其放入Firebug。根据JS引擎的不同,如果启动字符串很大,可能需要一些时间。
1 2 3 4 5 6 7 8 9 | 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"); |
输出应如下:
1 2 3 4 5 6 7 | abc ab ac a bc b c |
在C++中,下面的例程将产生范围(第一,最后)之间的所有长度组合(第一,k):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include 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; } |
它可以这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #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; } |
这将打印以下内容:
1 2 3 4 5 6 7 8 9 10 | 123 124 125 134 135 145 234 235 245 345 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } } |
python中的简短示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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种组合都是:
- A,其余所有2种组合(b c d e)
- b其余所有2种组合(c d e)
- C,其余所有2种组合(d e)
Haskell中的简单递归算法
1 2 3 4 5 6 7 | import Data.List combinations 0 lst = [[]] combinations n lst = do (x:xs) <- tails lst rest <- combinations (n-1) xs return $ x : rest |
我们首先定义特殊情况,即选择零元素。它生成一个单一的结果,即空列表(即包含空列表的列表)。
对于n>0,
1 2 | > combinations 3"abcde" ["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"] |
当然,由于haskell是懒惰的,列表会根据需要逐步生成,因此您可以部分地计算指数级大的组合。
1 2 3 4 | > let c = combinations 8"abcdefghijklmnopqrstuvwxyz" > take 10 c ["abcdefgh","abcdefgi","abcdefgj","abcdefgk","abcdefgl","abcdefgm","abcdefgn", "abcdefgo","abcdefgp","abcdefgq"] |
还有外公考博,这是一种非常恶毒的语言。
让我们假设一个包含34个元素的数组,每个元素8个字节(纯粹是任意选择)。其思想是枚举所有可能的4元素组合,并将它们加载到一个数组中。
我们使用4个指数,每组4个位置各一个。
数组的处理方式如下:
1 2 3 4 | idx1 = 1 idx2 = 2 idx3 = 3 idx4 = 4 |
我们将IDX4从4改为4。对于每个IDX4,我们都有一个独特的组合四人一组。当IDX4到达数组末尾时,我们将IDX3增加1,并将IDX4设置为IDX3+1。然后我们再次运行IDX4。我们以这种方式继续,分别增加idx3、idx2和idx1,直到idx1的位置与数组末端的距离小于4。完成了算法。
1 2 3 4 5 6 7 8 | 1 --- pos.1 2 --- pos 2 3 --- pos 3 4 --- pos 4 5 6 7 etc. |
第一次迭代:
1 2 3 4 5 6 7 8 9 10 11 | 1234 1235 1236 1237 1245 1246 1247 1256 1257 1267 etc. |
COBOL示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 01 DATA_ARAY. 05 FILLER PIC X(8) VALUE "VALUE_01". 05 FILLER PIC X(8) VALUE "VALUE_02". etc. 01 ARAY_DATA OCCURS 34. 05 ARAY_ITEM PIC X(8). 01 OUTPUT_ARAY OCCURS 50000 PIC X(32). 01 MAX_NUM PIC 99 COMP VALUE 34. 01 INDEXXES COMP. 05 IDX1 PIC 99. 05 IDX2 PIC 99. 05 IDX3 PIC 99. 05 IDX4 PIC 99. 05 OUT_IDX PIC 9(9). 01 WHERE_TO_STOP_SEARCH PIC 99 COMP. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | * Stop the search when IDX1 is on the third last array element: COMPUTE WHERE_TO_STOP_SEARCH = MAX_VALUE - 3 MOVE 1 TO IDX1 PERFORM UNTIL IDX1 > WHERE_TO_STOP_SEARCH COMPUTE IDX2 = IDX1 + 1 PERFORM UNTIL IDX2 > MAX_NUM COMPUTE IDX3 = IDX2 + 1 PERFORM UNTIL IDX3 > MAX_NUM COMPUTE IDX4 = IDX3 + 1 PERFORM UNTIL IDX4 > MAX_NUM ADD 1 TO OUT_IDX STRING ARAY_ITEM(IDX1) ARAY_ITEM(IDX2) ARAY_ITEM(IDX3) ARAY_ITEM(IDX4) INTO OUTPUT_ARAY(OUT_IDX) ADD 1 TO IDX4 END-PERFORM ADD 1 TO IDX3 END-PERFORM ADD 1 TO IDX2 END_PERFORM ADD 1 TO IDX1 END-PERFORM. |
如果您可以使用SQL语法-例如,如果您使用LINQ访问结构或数组的字段,或者直接访问只有一个char字段"letter"的表名为"alphabet"的数据库,则可以修改以下代码:
1 2 3 4 | SELECT A.Letter, B.Letter, C.Letter FROM Alphabet AS A, Alphabet AS B, Alphabet AS C WHERE A.Letter<>B.Letter AND A.Letter<>C.Letter AND B.Letter<>C.Letter AND A.Letter<B.Letter AND B.Letter<C.Letter |
这将返回3个字母的所有组合,不管"字母表"中有多少个字母(可以是3、8、10、27等)。
如果您想要的是所有排列,而不是组合(即,您希望"acb"和"abc"计数为不同的,而不是仅仅出现一次),只需删除最后一行(和一行)即可完成。
编辑后:重新阅读问题后,我意识到需要的是通用算法,而不仅仅是选择3个项目的具体算法。亚当休斯的回答是完整的,不幸的是我还不能投票决定。这个答案很简单,但只适用于你想要3个项目的时候。
这里是scala中一个优雅的通用实现,如99个scala问题所描述的那样。
1 2 3 4 5 6 7 8 9 10 11 12 13 | object P26 { def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = ls match { case Nil => Nil case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f) } def combinations[A](n: Int, ls: List[A]): List[List[A]] = if (n == 0) List(Nil) else flatMapSublists(ls) { sl => combinations(n - 1, sl.tail) map {sl.head :: _} } } |
这里有一个用C编码的算法的延迟评估版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | static bool nextCombination(int[] num, int n, int k) { bool finished, changed; changed = finished = false; if (k > 0) { for (int i = k - 1; !finished && !changed; i--) { if (num[i] < (n - 1) - (k - 1) + i) { num[i]++; if (i < k - 1) { for (int j = i + 1; j < k; j++) { num[j] = num[j - 1] + 1; } } changed = true; } finished = (i == 0); } } return changed; } static IEnumerable Combinations<T>(IEnumerable<T> elements, int k) { T[] elem = elements.ToArray(); int size = elem.Length; if (k <= size) { int[] numbers = new int[k]; for (int i = 0; i < k; i++) { numbers[i] = i; } do { yield return numbers.Select(n => elem[n]); } while (nextCombination(numbers, size, k)); } } |
测试部分:
1 2 3 4 5 6 7 8 9 10 | static void Main(string[] args) { int k = 3; var t = new[] {"dog","cat","mouse","zebra"}; foreach (IEnumerable<string> i in Combinations(t, k)) { Console.WriteLine(string.Join(",", i)); } } |
希望这对你有帮助!
另一个C版本,延迟生成组合索引。此版本维护一个索引数组,以定义所有值列表与当前组合值之间的映射,即在整个运行时持续使用O(K)额外空间。代码在O(k)时间内生成单个组合,包括第一个组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public static IEnumerable<T[]> Combinations<T>(this T[] values, int k) { if (k < 0 || values.Length < k) yield break; // invalid parameters, no combinations possible // generate the initial combination indices var combIndices = new int[k]; for (var i = 0; i < k; i++) { combIndices[i] = i; } while (true) { // return next combination var combination = new T[k]; for (var i = 0; i < k; i++) { combination[i] = values[combIndices[i]]; } yield return combination; // find first index to update var indexToUpdate = k - 1; while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= values.Length - k + indexToUpdate) { indexToUpdate--; } if (indexToUpdate < 0) yield break; // done // update combination indices for (var combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < k; indexToUpdate++, combIndex++) { combIndices[indexToUpdate] = combIndex; } } } |
测试代码:
1 2 3 4 | foreach (var combination in new[] {'a', 'b', 'c', 'd', 'e'}.Combinations(3)) { System.Console.WriteLine(String.Join("", combination)); } |
输出:
1 2 3 4 5 6 7 8 9 10 | a b c a b d a b e a c d a c e a d e b c d b c e b d e c d e |
我有一个用于Project Euler的置换算法,在python中:
1 2 3 4 5 6 7 8 9 10 11 | def missing(miss,src): "Returns the list of items in src not present in miss" return [i for i in src if i not in miss] def permutation_gen(n,l): "Generates all the permutations of n items of the l list" for i in l: if n<=1: yield [i] r = [i] for j in permutation_gen(n-1,missing([i],l)): yield r+j |
如果
1 | n<len(l) |
你应该拥有你需要的所有组合,而不是重复,你需要吗?
它是一个发电机,所以你用它来做类似的事情:
1 2 | for comb in permutation_gen(3,list("ABCDEFGH")): print comb |
https://gist.github.com/3118596
有一个JavaScript实现。它具有获取K组合和任意对象数组的所有组合的函数。实例:
1 2 3 4 5 | k_combinations([1,2,3], 2) -> [[1,2], [1,3], [2,3]] combinations([1,2,3]) -> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]] |
Clojure版本:
1 2 3 4 5 6 7 | (defn comb [k l] (if (= 1 k) (map vector l) (apply concat (map-indexed #(map (fn [x] (conj x %2)) (comb (dec k) (drop (inc %1) l))) l)))) |
假设您的字母数组如下所示:"abcdefgh"。你有三个索引(i,j,k),表示你要用哪个字母来表示当前单词,你从以下开始:
1 2 3 | A B C D E F G H ^ ^ ^ i j k |
首先你改变k,所以下一步看起来是这样的:
1 2 3 | A B C D E F G H ^ ^ ^ i j k |
如果你到了终点,你继续改变j,然后再改变k。
1 2 3 4 5 6 7 | A B C D E F G H ^ ^ ^ i j k A B C D E F G H ^ ^ ^ i j k |
一旦你达到G,你也开始改变I。
1 2 3 4 5 6 7 8 | A B C D E F G H ^ ^ ^ i j k A B C D E F G H ^ ^ ^ i j k ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | function initializePointers($cnt) { $pointers = []; for($i=0; $i<$cnt; $i++) { $pointers[] = $i; } return $pointers; } function incrementPointers(&$pointers, &$arrLength) { for($i=0; $i<count($pointers); $i++) { $currentPointerIndex = count($pointers) - $i - 1; $currentPointer = $pointers[$currentPointerIndex]; if($currentPointer < $arrLength - $i - 1) { ++$pointers[$currentPointerIndex]; for($j=1; ($currentPointerIndex+$j)<count($pointers); $j++) { $pointers[$currentPointerIndex+$j] = $pointers[$currentPointerIndex]+$j; } return true; } } return false; } function getDataByPointers(&$arr, &$pointers) { $data = []; for($i=0; $i<count($pointers); $i++) { $data[] = $arr[$pointers[$i]]; } return $data; } function getCombinations($arr, $cnt) { $len = count($arr); $result = []; $pointers = initializePointers($cnt); do { $result[] = getDataByPointers($arr, $pointers); } while(incrementPointers($pointers, count($arr))); return $result; } $result = getCombinations([0, 1, 2, 3, 4, 5], 3); print_r($result); |
基于https://stackoverflow.com/a/127898/2628125,但对于任何大小的指针都更抽象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Array.prototype.combs = function(num) { var str = this, length = str.length, of = Math.pow(2, length) - 1, out, combinations = []; while(of) { out = []; for(var i = 0, y; i < length; i++) { y = (1 << i); if(y & of && (y !== of)) out.push(str[i]); } if (out.length >= num) { combinations.push(out); } of--; } return combinations; } |
所有的一切都说了又做了,这就是O'Caml代码。从代码中可以看出算法。
1 2 3 4 5 6 7 8 9 | let combi n lst = let rec comb l c = if( List.length c = n) then [c] else match l with [] -> [] | (h::t) -> (combi t (h::c))@(combi t c) in combi lst [] ;; |
这是我最近在Java中编写的一个代码,它计算并返回"NUM"元素与"Outof"元素的所有组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // author: Sourabh Bhat ([email protected]) public class Testing { public static void main(String[] args) { // Test case num = 5, outOf = 8. int num = 5; int outOf = 8; int[][] combinations = getCombinations(num, outOf); for (int i = 0; i < combinations.length; i++) { for (int j = 0; j < combinations[i].length; j++) { System.out.print(combinations[i][j] +""); } System.out.println(); } } private static int[][] getCombinations(int num, int outOf) { int possibilities = get_nCr(outOf, num); int[][] combinations = new int[possibilities][num]; int arrayPointer = 0; int[] counter = new int[num]; for (int i = 0; i < num; i++) { counter[i] = i; } breakLoop: while (true) { // Initializing part for (int i = 1; i < num; i++) { if (counter[i] >= outOf - (num - 1 - i)) counter[i] = counter[i - 1] + 1; } // Testing part for (int i = 0; i < num; i++) { if (counter[i] < outOf) { continue; } else { break breakLoop; } } // Innermost part combinations[arrayPointer] = counter.clone(); arrayPointer++; // Incrementing part counter[num - 1]++; for (int i = num - 1; i >= 1; i--) { if (counter[i] >= outOf - (num - 1 - i)) counter[i - 1]++; } } return combinations; } private static int get_nCr(int n, int r) { if(r > n) { throw new ArithmeticException("r is greater then n"); } long numerator = 1; long denominator = 1; for (int i = n; i >= r + 1; i--) { numerator *= i; } for (int i = 2; i <= n - r; i++) { denominator *= i; } return (int) (numerator / denominator); } } |
我在SQL Server 2005中为此创建了一个解决方案,并将其发布在我的网站上:http://www.jessemclain.com/downloads/code/sql/fn_etmchoosencombos.sql.htm
下面是一个演示用法的示例:
1 | SELECT * FROM dbo.fn_GetMChooseNCombos('ABCD', 2, '') |
结果:
1 2 3 4 5 6 7 8 9 10 | Word ---- AB AC AD BC BD CD (6 row(s) affected) |
下面是一个方法,它从一个随机长度的字符串中为您提供指定大小的所有组合。类似于Quinmars的解决方案,但适用于不同的输入和k。
代码可以更改为环绕,即"dab"来自输入"abcd"w k=3。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public void run(String data, int howMany){ choose(data, howMany, new StringBuffer(), 0); } //n choose k private void choose(String data, int k, StringBuffer result, int startIndex){ if (result.length()==k){ System.out.println(result.toString()); return; } for (int i=startIndex; i<data.length(); i++){ result.append(data.charAt(i)); choose(data,k,result, i+1); result.setLength(result.length()-1); } } |
"abcde"的输出:
abc abd abe acd ace ade bcd bce bde cde
这是我在C++中的建议
我试图尽可能少地对迭代器类型施加限制,所以这个解决方案假定只使用正向迭代器,它可以是一个常量迭代器。这应该适用于任何标准容器。在参数没有意义的情况下,它抛出std::invalidu参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #include <vector> #include <stdexcept> template <typename Fci> // Fci - forward const iterator std::vector<std::vector<Fci> > enumerate_combinations(Fci begin, Fci end, unsigned int combination_size) { if(begin == end && combination_size > 0u) throw std::invalid_argument("empty set and positive combination size!"); std::vector<std::vector<Fci> > result; // empty set of combinations if(combination_size == 0u) return result; // there is exactly one combination of // size 0 - emty set std::vector<Fci> current_combination; current_combination.reserve(combination_size + 1u); // I reserve one aditional slot // in my vector to store // the end sentinel there. // The code is cleaner thanks to that for(unsigned int i = 0u; i < combination_size && begin != end; ++i, ++begin) { current_combination.push_back(begin); // Construction of the first combination } // Since I assume the itarators support only incrementing, I have to iterate over // the set to get its size, which is expensive. Here I had to itrate anyway to // produce the first cobination, so I use the loop to also check the size. if(current_combination.size() < combination_size) throw std::invalid_argument("combination size > set size!"); result.push_back(current_combination); // Store the first combination in the results set current_combination.push_back(end); // Here I add mentioned earlier sentinel to // simplyfy rest of the code. If I did it // earlier, previous statement would get ugly. while(true) { unsigned int i = combination_size; Fci tmp; // Thanks to the sentinel I can find first do // iterator to change, simply by scaning { // from right to left and looking for the tmp = current_combination[--i]; // first"bubble". The fact, that it's ++tmp; // a forward iterator makes it ugly but I } // can't help it. while(i > 0u && tmp == current_combination[i + 1u]); // Here is probably my most obfuscated expression. // Loop above looks for a"bubble". If there is no"bubble", that means, that // current_combination is the last combination, Expression in the if statement // below evaluates to true and the function exits returning result. // If the"bubble" is found however, the ststement below has a sideeffect of // incrementing the first iterator to the left of the"bubble". if(++current_combination[i] == current_combination[i + 1u]) return result; // Rest of the code sets posiotons of the rest of the iterstors // (if there are any), that are to the right of the incremented one, // to form next combination while(++i < combination_size) { current_combination[i] = current_combination[i - 1u]; ++current_combination[i]; } // Below is the ugly side of using the sentinel. Well it had to haave some // disadvantage. Try without it. result.push_back(std::vector<Fci>(current_combination.begin(), current_combination.end() - 1)); } } |
简洁的javascript解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Array.prototype.combine=function combine(k){ var toCombine=this; var last; function combi(n,comb){ var combs=[]; for ( var x=0,y=comb.length;x<y;x++){ for ( var l=0,m=toCombine.length;l<m;l++){ combs.push(comb[x]+toCombine[l]); } } if (n<k-1){ n++; combi(n,combs); } else{last=combs;} } combi(1,toCombine); return last; } // Example: // var toCombine=['a','b','c']; // var results=toCombine.combine(4); |
算法:
- 从1到2^n计数。
- 将每个数字转换为二进制表示。
- 根据位置将每个"on"位转换为集合中的元素。
C中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void Main() { var set = new [] {"A","B","C","D" }; //,"E","F","G","H","I","J" }; var kElement = 2; for(var i = 1; i < Math.Pow(2, set.Length); i++) { var result = Convert.ToString(i, 2).PadLeft(set.Length, '0'); var cnt = Regex.Matches(Regex.Escape(result), "1").Count; if (cnt == kElement) { for(int j = 0; j < set.Length; j++) if ( Char.GetNumericValue(result[j]) == 1) Console.Write(set[j]); Console.WriteLine(); } } } |
为什么有效?
N元素集的子集和N位序列之间存在双射。
这意味着我们可以通过计算序列来计算出有多少个子集。
例如,下面的四个元素集可以用0,1 x 0,1 x 0,1 x 0,1(或2^4)不同的序列表示。
所以-我们要做的就是从1数到2^n来找到所有的组合。(我们忽略了空的集合。)接下来,将数字转换为二进制表示。然后用"on"位替换集合中的元素。
如果只需要k元素结果,则仅在k位为"on"时打印。
(如果希望所有子集而不是k长度子集,请删除cnt/kelement部分。)
(有关证明,请参阅麻省理工学院计算机科学免费课件《数学》,雷曼等人,第11.2.2节。https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics-for-computer-science-fall-2010/readings/)
我已经编写了一个处理二项式系数的公共函数的类,这是您的问题所属的问题类型。它执行以下任务:
以很好的格式将所有k-索引输出给任意n个文件,选择k。k索引可以用更具描述性的字符串或字母代替。这种方法使得解决这类问题变得非常简单。
将k-索引转换为排序二项式系数表中某项的正确索引。这项技术比以前发布的依赖迭代的技术快得多。它通过使用帕斯卡三角固有的数学特性来实现这一点。我的论文谈到这个。我相信我是第一个发现并发布这种技术的人,但我可能错了。
将排序后的二项式系数表中的索引转换为相应的k索引。
采用Mark-Domius方法计算二项系数,二项系数越小,越大的数值越容易溢出。
该类是用.NET C编写的,并提供了一种使用通用列表管理与问题相关的对象(如果有)的方法。此类的构造函数接受一个名为inittable的bool值,如果为true,则将创建一个包含要管理的对象的泛型列表。如果该值为假,则不会创建表。为了执行上述4种方法,不需要创建表。提供了访问器方法来访问表。
有一个关联的测试类,它显示如何使用该类及其方法。它已经过广泛的测试,有2个案例,没有已知的错误。
要阅读关于这个类的内容并下载代码,请参见建立二项式系数。
将这个类转换为C++并不困难。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void combine(char a[], int N, int M, int m, int start, char result[]) { if (0 == m) { for (int i = M - 1; i >= 0; i--) std::cout << result[i]; std::cout << std::endl; return; } for (int i = start; i < (N - m + 1); i++) { result[m - 1] = a[i]; combine(a, N, M, m-1, i+1, result); } } void combine(char a[], int N, int M) { char *result = new char[M]; combine(a, N, M, M, 0, result); delete[] result; } |
在第一个函数中,m表示还需要选择多少,start表示必须从数组中的哪个位置开始选择。
lisp宏为所有值r生成代码(每次执行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | (defmacro txaat (some-list taken-at-a-time) (let* ((vars (reverse (truncate-list '(a b c d e f g h i j) taken-at-a-time)))) `( ,@(loop for i below taken-at-a-time for j in vars with nested = nil finally (return nested) do (setf nested `(loop for ,j from ,(if (< i (1- (length vars))) `(1+ ,(nth (1+ i) vars)) 0) below (- (length ,some-list) ,i) ,@(if (equal i 0) `(collect (list ,@(loop for k from (1- taken-at-a-time) downto 0 append `((nth ,(nth k vars) ,some-list))))) `(append ,nested)))))))) |
所以,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | CL-USER> (macroexpand-1 '(txaat '(a b c d) 1)) (LOOP FOR A FROM 0 TO (- (LENGTH '(A B C D)) 1) COLLECT (LIST (NTH A '(A B C D)))) T CL-USER> (macroexpand-1 '(txaat '(a b c d) 2)) (LOOP FOR A FROM 0 TO (- (LENGTH '(A B C D)) 2) APPEND (LOOP FOR B FROM (1+ A) TO (- (LENGTH '(A B C D)) 1) COLLECT (LIST (NTH A '(A B C D)) (NTH B '(A B C D))))) T CL-USER> (macroexpand-1 '(txaat '(a b c d) 3)) (LOOP FOR A FROM 0 TO (- (LENGTH '(A B C D)) 3) APPEND (LOOP FOR B FROM (1+ A) TO (- (LENGTH '(A B C D)) 2) APPEND (LOOP FOR C FROM (1+ B) TO (- (LENGTH '(A B C D)) 1) COLLECT (LIST (NTH A '(A B C D)) (NTH B '(A B C D)) (NTH C '(A B C D)))))) T CL-USER> |
而且,
1 2 3 4 5 6 7 8 9 10 11 12 13 | CL-USER> (txaat '(a b c d) 1) ((A) (B) (C) (D)) CL-USER> (txaat '(a b c d) 2) ((A B) (A C) (A D) (B C) (B D) (C D)) CL-USER> (txaat '(a b c d) 3) ((A B C) (A B D) (A C D) (B C D)) CL-USER> (txaat '(a b c d) 4) ((A B C D)) CL-USER> (txaat '(a b c d) 5) NIL CL-USER> (txaat '(a b c d) 0) NIL CL-USER> |
在vb.net中,该算法从一组数字(poolarray)中收集n个数字的所有组合。例如,"8,10,20,33,41,44,47"中5个选择的所有组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Sub CreateAllCombinationsOfPicksFromPool(ByVal PicksArray() As UInteger, ByVal PicksIndex As UInteger, ByVal PoolArray() As UInteger, ByVal PoolIndex As UInteger) If PicksIndex < PicksArray.Length Then For i As Integer = PoolIndex To PoolArray.Length - PicksArray.Length + PicksIndex PicksArray(PicksIndex) = PoolArray(i) CreateAllCombinationsOfPicksFromPool(PicksArray, PicksIndex + 1, PoolArray, i + 1) Next Else ' completed combination. build your collections using PicksArray. End If End Sub Dim PoolArray() As UInteger = Array.ConvertAll("8,10,20,33,41,44,47".Split(","), Function(u) UInteger.Parse(u)) Dim nPicks as UInteger = 5 Dim Picks(nPicks - 1) As UInteger CreateAllCombinationsOfPicksFromPool(Picks, 0, PoolArray, 0) |
下面是一些打印所有C(N,M)组合的简单代码。它通过初始化和移动一组指向下一个有效组合的数组索引来工作。这些索引被初始化为指向最低的m索引(在词典上是最小的组合)。然后,从第m个指数开始,我们尝试向前移动这些指数。如果某个索引已达到其限制,我们将尝试上一个索引(一直到索引1)。如果我们可以向前移动一个索引,那么我们会重置所有更大的索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | m=(rand()%n)+1; // m will vary from 1 to n for (i=0;i<n;i++) a[i]=i+1; // we want to print all possible C(n,m) combinations of selecting m objects out of n printf("Printing C(%d,%d) possible combinations ... ", n,m); // This is an adhoc algo that keeps m pointers to the next valid combination for (i=0;i<m;i++) p[i]=i; // the p[.] contain indices to the a vector whose elements constitute next combination done=false; while (!done) { // print combination for (i=0;i<m;i++) printf("%2d", a[p[i]]); printf(" "); // update combination // method: start with p[m-1]. try to increment it. if it is already at the end, then try moving p[m-2] ahead. // if this is possible, then reset p[m-1] to 1 more than (the new) p[m-2]. // if p[m-2] can not also be moved, then try p[m-3]. move that ahead. then reset p[m-2] and p[m-1]. // repeat all the way down to p[0]. if p[0] can not also be moved, then we have generated all combinations. j=m-1; i=1; move_found=false; while ((j>=0) && !move_found) { if (p[j]<(n-i)) { move_found=true; p[j]++; // point p[j] to next index for (k=j+1;k<m;k++) { p[k]=p[j]+(k-j); } } else { j--; i++; } } if (!move_found) done=true; } |
这是一个递归程序,它为集合中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #include<stdio.h> #include<stdlib.h> int nCk(int n,int loopno,int ini,int *a,int k) { static int count=0; int i; loopno--; if(loopno<0) { a[k-1]=ini; for(i=0;i<k;i++) { printf("%d,",a[i]); } printf(" "); count++; return 0; } for(i=ini;i<=n-loopno-1;i++) { a[k-1-loopno]=i+1; nCk(n,loopno,i+1,a,k); } if(ini==0) return count; else return 0; } void main() { int n,k,*a,count; printf("Enter the value of n and k "); scanf("%d %d",&n,&k); a=(int*)malloc(k*sizeof(int)); count=nCk(n,k,0,a,k); printf("No of combinations=%d ",count); } |
由于没有提到编程语言,我假设列表也可以。所以这里有一个适合短列表(非尾部递归)的OCAML版本。给定任意类型元素的列表l和整数n,如果我们假定结果列表中元素的顺序被忽略,即列表['a';'b']与['b';'a']相同,并将报告一次,则它将返回包含n个元素的所有可能列表的列表。所以结果列表的大小将是((list.length l)选择n)。
递归的直觉如下:取列表的头,然后进行两个递归调用:
- 递归调用1(rc1):指向列表的尾部,但选择n-1元素
- 递归调用2(rc2):指向列表的尾部,但选择n个元素
要组合递归结果,请将列表的头与rc1的结果相乘(请使用奇数名称),然后将rc2的结果附加(@)。list multiple是以下操作
1 | a lmul [ l1 ; l2 ; l3] = [a::l1 ; a::l2 ; a::l3] |
1 | List.map (fun x -> h::x) |
当列表的大小等于要选择的元素数时,递归将终止,在这种情况下,您只需返回列表本身。
因此,OCAML中有一个实现上述算法的四行程序:
1 2 3 4 | let rec choose l n = match l, (List.length l) with | _, lsize when n==lsize -> [l] | h::t, _ -> (List.map (fun x-> h::x) (choose t (n-1))) @ (choose t n) | [], _ -> [] |
跳上潮流,发布另一个解决方案。这是一个通用的Java实现。输入:>)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public static <T> List<List<T>> getCombinations(int k, List<T> list) { List<List<T>> combinations = new ArrayList<List<T>>(); if (k == 0) { combinations.add(new ArrayList<T>()); return combinations; } for (int i = 0; i < list.size(); i++) { T element = list.get(i); List<T> rest = getSublist(list, i+1); for (List<T> previous : getCombinations(k-1, rest)) { previous.add(element); combinations.add(previous); } } return combinations; } public static <T> List<T> getSublist(List<T> list, int i) { List<T> sublist = new ArrayList<T>(); for (int j = i; j < list.size(); j++) { sublist.add(list.get(j)); } return sublist; } |
这里有一个clojure版本,使用我在OCAML实现答案中描述的相同算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | (defn select ([items] (select items 0 (inc (count items)))) ([items n1 n2] (reduce concat (map #(select % items) (range n1 (inc n2))))) ([n items] (let [ lmul (fn [a list-of-lists-of-bs] (map #(cons a %) list-of-lists-of-bs)) ] (if (= n (count items)) (list items) (if (empty? items) items (concat (select n (rest items)) (lmul (first items) (select (dec n) (rest items))))))))) |
它提供了三种调用方法:
(a)对于问题所需的N个选定项目:
1 2 | user=> (count (select 3"abcdefgh")) 56 |
(b)对于N1和N2之间的选定项目:
1 2 | user=> (select '(1 2 3 4) 2 3) ((3 4) (2 4) (2 3) (1 4) (1 3) (1 2) (2 3 4) (1 3 4) (1 2 4) (1 2 3)) |
(c)对于介于0和集合选定项的大小之间的值:
1 2 | user=> (select '(1 2 3)) (() (3) (2) (1) (2 3) (1 3) (1 2) (1 2 3)) |
基于Java解决方案的K元素从N(二项系数)返回所有组合的短PHP算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $array = array(1,2,3,4,5); $array_result = NULL; $array_general = NULL; function combinations($array, $len, $start_position, $result_array, $result_len, &$general_array) { if($len == 0) { $general_array[] = $result_array; return; } for ($i = $start_position; $i <= count($array) - $len; $i++) { $result_array[$result_len - $len] = $array[$i]; combinations($array, $len-1, $i+1, $result_array, $result_len, $general_array); } } combinations($array, 3, 0, $array_result, 3, $array_general); echo"[cc]"; print_r($array_general); echo" |
;< /代码>
相同的解决方案,但在javascript中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var newArray = [1, 2, 3, 4, 5]; var arrayResult = []; var arrayGeneral = []; function combinations(newArray, len, startPosition, resultArray, resultLen, arrayGeneral) { if(len === 0) { var tempArray = []; resultArray.forEach(value => tempArray.push(value)); arrayGeneral.push(tempArray); return; } for (var i = startPosition; i <= newArray.length - len; i++) { resultArray[resultLen - len] = newArray[i]; combinations(newArray, len-1, i+1, resultArray, resultLen, arrayGeneral); } } combinations(newArray, 3, 0, arrayResult, 3, arrayGeneral); console.log(arrayGeneral); |
另一个解决方案是C:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | static List<List<T>> GetCombinations<T>(List<T> originalItems, int combinationLength) { if (combinationLength < 1) { return null; } return CreateCombinations<T>(new List<T>(), 0, combinationLength, originalItems); } static List<List<T>> CreateCombinations<T>(List<T> initialCombination, int startIndex, int length, List<T> originalItems) { List<List<T>> combinations = new List<List<T>>(); for (int i = startIndex; i < originalItems.Count - length + 1; i++) { List<T> newCombination = new List<T>(initialCombination); newCombination.Add(originalItems[i]); if (length > 1) { List<List<T>> newCombinations = CreateCombinations(newCombination, i + 1, length - 1, originalItems); combinations.AddRange(newCombinations); } else { combinations.Add(newCombination); } } return combinations; } |
使用示例:
1 2 3 | List<char> initialArray = new List<char>() { 'a','b','c','d'}; int combinationLength = 3; List<List<char>> combinations = GetCombinations(initialArray, combinationLength); |
这是我的scala解决方案:
1 2 3 4 | def combinations[A](s: List[A], k: Int): List[List[A]] = if (k > s.length) Nil else if (k == 1) s.map(List(_)) else combinations(s.tail, k - 1).map(s.head :: _) ::: combinations(s.tail, k) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #include <stdio.h> unsigned int next_combination(unsigned int *ar, size_t n, unsigned int k) { unsigned int finished = 0; unsigned int changed = 0; unsigned int i; if (k > 0) { for (i = k - 1; !finished && !changed; i--) { if (ar[i] < (n - 1) - (k - 1) + i) { /* Increment this element */ ar[i]++; if (i < k - 1) { /* Turn the elements after it into a linear sequence */ unsigned int j; for (j = i + 1; j < k; j++) { ar[j] = ar[j - 1] + 1; } } changed = 1; } finished = i == 0; } if (!changed) { /* Reset to first combination */ for (i = 0; i < k; i++) { ar[i] = i; } } } return changed; } typedef void(*printfn)(const void *, FILE *); void print_set(const unsigned int *ar, size_t len, const void **elements, const char *brackets, printfn print, FILE *fptr) { unsigned int i; fputc(brackets[0], fptr); for (i = 0; i < len; i++) { print(elements[ar[i]], fptr); if (i < len - 1) { fputs(",", fptr); } } fputc(brackets[1], fptr); } int main(void) { unsigned int numbers[] = { 0, 1, 2 }; char *elements[] = {"a","b","c","d","e" }; const unsigned int k = sizeof(numbers) / sizeof(unsigned int); const unsigned int n = sizeof(elements) / sizeof(const char*); do { print_set(numbers, k, (void*)elements,"[]", (printfn)fputs, stdout); putchar(' '); } while (next_combination(numbers, n, k)); getchar(); return 0; } |
短python代码,生成索引位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def yield_combos(n,k): # n is set size, k is combo size i = 0 a = [0 for i in range(k)] while i > -1: for j in range(i+1, k): a[j] = a[j-1]+1 i=j yield a while a[i] == i + n - k: i -= 1 a[i] += 1 |
我在为PHP寻找类似的解决方案,遇到了以下问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | class Combinations implements Iterator { protected $c = null; protected $s = null; protected $n = 0; protected $k = 0; protected $pos = 0; function __construct($s, $k) { if(is_array($s)) { $this->s = array_values($s); $this->n = count($this->s); } else { $this->s = (string) $s; $this->n = strlen($this->s); } $this->k = $k; $this->rewind(); } function key() { return $this->pos; } function current() { $r = array(); for($i = 0; $i < $this->k; $i++) $r[] = $this->s[$this->c[$i]]; return is_array($this->s) ? $r : implode('', $r); } function next() { if($this->_next()) $this->pos++; else $this->pos = -1; } function rewind() { $this->c = range(0, $this->k); $this->pos = 0; } function valid() { return $this->pos >= 0; } protected function _next() { $i = $this->k - 1; while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i) $i--; if($i < 0) return false; $this->c[$i]++; while($i++ < $this->k - 1) $this->c[$i] = $this->c[$i - 1] + 1; return true; } } foreach(new Combinations("1234567", 5) as $substring) echo $substring, ' '; |
来源
我不确定这门课有多高效,但我只是把它用在播种机上。
《计算机编程技术》第4卷第7.2.1.3节"组合算法"第1部分"算法L"(词典组合)的C代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <stdio.h> #include <stdlib.h> void visit(int* c, int t) { // for (int j = 1; j <= t; j++) for (int j = t; j > 0; j--) printf("%d", c[j]); printf(" "); } int* initialize(int n, int t) { // c[0] not used int *c = (int*) malloc((t + 3) * sizeof(int)); for (int j = 1; j <= t; j++) c[j] = j - 1; c[t+1] = n; c[t+2] = 0; return c; } void comb(int n, int t) { int *c = initialize(n, t); int j; for (;;) { visit(c, t); j = 1; while (c[j]+1 == c[j+1]) { c[j] = j - 1; ++j; } if (j > t) return; ++c[j]; } free(c); } int main(int argc, char *argv[]) { comb(5, 3); return 0; } |
在Python中像安德里亚一样埋伏,但没有硬编码选择三个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def combinations(list, k): """Choose combinations of list, choosing k elements(no repeats)""" if len(list) < k: return [] else: seq = [i for i in range(k)] while seq: print [list[index] for index in seq] seq = get_next_combination(len(list), k, seq) def get_next_combination(num_elements, k, seq): index_to_move = find_index_to_move(num_elements, seq) if index_to_move == None: return None else: seq[index_to_move] += 1 #for every element past this sequence, move it down for i, elem in enumerate(seq[(index_to_move+1):]): seq[i + 1 + index_to_move] = seq[index_to_move] + i + 1 return seq def find_index_to_move(num_elements, seq): """Tells which index should be moved""" for rev_index, elem in enumerate(reversed(seq)): if elem < (num_elements - rev_index - 1): return len(seq) - rev_index - 1 return None |
递归地,一个非常简单的答案,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | procedure combinata (n, k :integer; producer :oneintproc); procedure combo (ndx, nbr, len, lnd :integer); begin for nbr := nbr to len do begin productarray[ndx] := nbr; if len < lnd then combo(ndx+1,nbr+1,len+1,lnd) else producer(k); end; end; begin combo (0, 0, n-k, n-1); end; |
"Producer"处理为每个组合制作的ProductArray。
不需要集合操作。这个问题几乎与在K嵌套循环上循环一样,但是必须注意索引和边界(忽略Java和OOP内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class CombinationsGen { private final int n; private final int k; private int[] buf; public CombinationsGen(int n, int k) { this.n = n; this.k = k; } public void combine(Consumer<int[]> consumer) { buf = new int[k]; rec(0, 0, consumer); } private void rec(int index, int next, Consumer<int[]> consumer) { int max = n - index; if (index == k - 1) { for (int i = 0; i < max && next < n; i++) { buf[index] = next; next++; consumer.accept(buf); } } else { for (int i = 0; i < max && next + index < n; i++) { buf[index] = next; next++; rec(index + 1, next, consumer); } } } } |
使用如下:
1 2 3 4 5 6 7 8 | CombinationsGen gen = new CombinationsGen(5, 2); AtomicInteger total = new AtomicInteger(); gen.combine(arr -> { System.out.println(Arrays.toString(arr)); total.incrementAndGet(); }); System.out.println(total); |
获取预期结果:
1 2 3 4 5 6 7 8 9 10 11 | [0, 1] [0, 2] [0, 3] [0, 4] [1, 2] [1, 3] [1, 4] [2, 3] [2, 4] [3, 4] 10 |
最后,将索引映射到您可能拥有的任何数据集。
简单但速度慢的C++回溯算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <iostream> void backtrack(int* numbers, int n, int k, int i, int s) { if (i == k) { for (int j = 0; j < k; ++j) { std::cout << numbers[j]; } std::cout << std::endl; return; } if (s > n) { return; } numbers[i] = s; backtrack(numbers, n, k, i + 1, s + 1); backtrack(numbers, n, k, i, s + 1); } int main(int argc, char* argv[]) { int n = 5; int k = 3; int* numbers = new int[k]; backtrack(numbers, n, k, 0, 1); delete[] numbers; return 0; } |
我们可以使用位的概念来实现这一点。让我们有一个字符串"a bc",我们希望有长度为2的所有元素组合(即"ab"、"ac"、"bc")。
我们可以在1到2^n(不含)的数字中找到集合位。这里是1到7,只要我们设置了bits=2,就可以从字符串中打印相应的值。
例如:
- 1—001
- 2—010
- 3-011->
print ab (str[0] , str[1]) 。 - 4—100
- 5-101->
print ac (str[0] , str[2]) 。 - 6-110->
print ab (str[1] , str[2]) 。 - 7—111。
< BR>代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class StringCombinationK { static void combk(String s , int k){ int n = s.length(); int num = 1<<n; int j=0; int count=0; for(int i=0;i<num;i++){ if (countSet(i)==k){ setBits(i,j,s); count++; System.out.println(); } } System.out.println(count); } static void setBits(int i,int j,String s){ // print the corresponding string value,j represent the index of set bit if(i==0){ return; } if(i%2==1){ System.out.print(s.charAt(j)); } setBits(i/2,j+1,s); } static int countSet(int i){ //count number of set bits if( i==0){ return 0; } return (i%2==0? 0:1) + countSet(i/2); } public static void main(String[] arhs){ String s ="abcdefgh"; int k=3; combk(s,k); } } |
我为C++中的组合做了一个普通的类。它是这样使用的。
1 2 3 4 5 6 | char ar[] ="0ABCDEFGH"; nCr ncr(8, 3); while(ncr.next()) { for(int i=0; i<ncr.size(); i++) cout << ar[ncr[i]]; cout << ' '; } |
我的库ncr[i]从1返回,而不是从0返回。这就是数组中有0的原因。如果你想考虑订单,只需将NCR类更改为NPR。用法相同。
结果
基础知识脱落酸安倍ABF阿布格ABHACD王牌ACFACG胆碱阿德ADF自动增益控制腺苷脱氢酶原子能基金会AEGAEHAFGAFHAGHBCDBCEBCF卡介苗BCHBDEBDFBDGBDHBEF乞讨贝赫高炉煤气BFH牛生长激素化学需氧量彩色多普勒血流CDGCDH头孢噻吩CEGCEHCFGCFH计算全息DEFDEGDEH分布式光纤光栅二氢氟化氢生长激素EFGEFH表皮生长因子生长激素释放激素
这是头文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #pragma once #include <exception> class NRexception : public std::exception { public: virtual const char* what() const throw() { return"Combination : N, R should be positive integer!!"; } }; class Combination { public: Combination(int n, int r); virtual ~Combination() { delete [] ar;} int& operator[](unsigned i) {return ar[i];} bool next(); int size() {return r;} static int factorial(int n); protected: int* ar; int n, r; }; class nCr : public Combination { public: nCr(int n, int r); bool next(); int count() const; }; class nTr : public Combination { public: nTr(int n, int r); bool next(); int count() const; }; class nHr : public nTr { public: nHr(int n, int r) : nTr(n,r) {} bool next(); int count() const; }; class nPr : public Combination { public: nPr(int n, int r); virtual ~nPr() {delete [] on;} bool next(); void rewind(); int count() const; private: bool* on; void inc_ar(int i); }; |
以及实施。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | #include"combi.h" #include <set> #include<cmath> Combination::Combination(int n, int r) { //if(n < 1 || r < 1) throw NRexception(); ar = new int[r]; this->n = n; this->r = r; } int Combination::factorial(int n) { return n == 1 ? n : n * factorial(n-1); } int nPr::count() const { return factorial(n)/factorial(n-r); } int nCr::count() const { return factorial(n)/factorial(n-r)/factorial(r); } int nTr::count() const { return pow(n, r); } int nHr::count() const { return factorial(n+r-1)/factorial(n-1)/factorial(r); } nCr::nCr(int n, int r) : Combination(n, r) { if(r == 0) return; for(int i=0; i<r-1; i++) ar[i] = i + 1; ar[r-1] = r-1; } nTr::nTr(int n, int r) : Combination(n, r) { for(int i=0; i<r-1; i++) ar[i] = 1; ar[r-1] = 0; } bool nCr::next() { if(r == 0) return false; ar[r-1]++; int i = r-1; while(ar[i] == n-r+2+i) { if(--i == -1) return false; ar[i]++; } while(i < r-1) ar[i+1] = ar[i++] + 1; return true; } bool nTr::next() { ar[r-1]++; int i = r-1; while(ar[i] == n+1) { ar[i] = 1; if(--i == -1) return false; ar[i]++; } return true; } bool nHr::next() { ar[r-1]++; int i = r-1; while(ar[i] == n+1) { if(--i == -1) return false; ar[i]++; } while(i < r-1) ar[i+1] = ar[i++]; return true; } nPr::nPr(int n, int r) : Combination(n, r) { on = new bool[n+2]; for(int i=0; i<n+2; i++) on[i] = false; for(int i=0; i<r; i++) { ar[i] = i + 1; on[i] = true; } ar[r-1] = 0; } void nPr::rewind() { for(int i=0; i<r; i++) { ar[i] = i + 1; on[i] = true; } ar[r-1] = 0; } bool nPr::next() { inc_ar(r-1); int i = r-1; while(ar[i] == n+1) { if(--i == -1) return false; inc_ar(i); } while(i < r-1) { ar[++i] = 0; inc_ar(i); } return true; } void nPr::inc_ar(int i) { on[ar[i]] = false; while(on[++ar[i]]); if(ar[i] != n+1) on[ar[i]] = true; } |
作为迭代器对象实现的metatrader MQL4的快速组合。
代码很容易理解。
我对很多算法进行了基准测试,这一个非常快——比大多数下一个_Combination()函数快3倍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class CombinationsIterator { private: int input_array[]; // 1 2 3 4 5 int index_array[]; // i j k int m_elements; // N int m_indices; // K public: CombinationsIterator(int &src_data[], int k) { m_indices = k; m_elements = ArraySize(src_data); ArrayCopy(input_array, src_data); ArrayResize(index_array, m_indices); // create initial combination (0..k-1) for (int i = 0; i < m_indices; i++) { index_array[i] = i; } } // https://stackoverflow.com/questions/5076695 // bool next_combination(int &item[], int k, int N) bool advance() { int N = m_elements; for (int i = m_indices - 1; i >= 0; --i) { if (index_array[i] < --N) { ++index_array[i]; for (int j = i + 1; j < m_indices; ++j) { index_array[j] = index_array[j - 1] + 1; } return true; } } return false; } void getItems(int &items[]) { // fill items[] from input array for (int i = 0; i < m_indices; i++) { items[i] = input_array[index_array[i]]; } } }; |
用于测试上述迭代器类的驱动程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ // driver program to test above class #define N 5 #define K 3 void OnStart() { int myset[N] = {1, 2, 3, 4, 5}; int items[K]; CombinationsIterator comboIt(myset, K); do { comboIt.getItems(items); printf("%s", ArrayToString(items)); } while (comboIt.advance()); } |
1 2 3 4 5 6 7 8 9 10 11 | Output: 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5 |
下面是一个简单的JS解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | function getAllCombinations(n, k, f1) { indexes = Array(k); for (let i =0; i< k; i++) { indexes[i] = i; } var total = 1; f1(indexes); while (indexes[0] !== n-k) { total++; getNext(n, indexes); f1(indexes); } return {total}; } function getNext(n, vec) { const k = vec.length; vec[k-1]++; for (var i=0; i<k; i++) { var currentIndex = k-i-1; if (vec[currentIndex] === n - i) { var nextIndex = k-i-2; vec[nextIndex]++; vec[currentIndex] = vec[nextIndex] + 1; } } for (var i=1; i<k; i++) { if (vec[i] === n - (k-i - 1)) { vec[i] = vec[i-1] + 1; } } return vec; } let start = new Date(); let result = getAllCombinations(10, 3, indexes => console.log(indexes)); let runTime = new Date() - start; console.log({ result, runTime }); |
我想提出我的解决方案。在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class Combinations { final int pos[]; final List<Object> set; public Combinations(List<?> l, int k) { pos = new int[k]; set=new ArrayList<Object>(l); reset(); } public void reset() { for (int i=0; i < pos.length; ++i) pos[i]=i; } public boolean next() { int i = pos.length-1; for (int maxpos = set.size()-1; pos[i] >= maxpos; --maxpos) { if (i==0) return false; --i; } ++pos[i]; while (++i < pos.length) pos[i]=pos[i-1]+1; return true; } public void getSelection(List<?> l) { @SuppressWarnings("unchecked") List<Object> ll = (List<Object>)l; if (ll.size()!=pos.length) { ll.clear(); for (int i=0; i < pos.length; ++i) ll.add(set.get(pos[i])); } else { for (int i=0; i < pos.length; ++i) ll.set(i, set.get(pos[i])); } } } |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 | static void main(String[] args) { List<Character> l = new ArrayList<Character>(); for (int i=0; i < 32; ++i) l.add((char)('a'+i)); Combinations comb = new Combinations(l,5); int n=0; do { ++n; comb.getSelection(l); //Log.debug("%d: %s", n, l.toString()); } while (comb.next()); Log.debug("num = %d", n); } |
短快速C实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h> void main(int argc, char *argv[]) { const int n = 6; /* The size of the set; for {1, 2, 3, 4} it's 4 */ const int p = 4; /* The size of the subsets; for {1, 2}, {1, 3}, ... it's 2 */ int comb[40] = {0}; /* comb[i] is the index of the i-th element in the combination */ int i = 0; for (int j = 0; j <= n; j++) comb[j] = 0; while (i >= 0) { if (comb[i] < n + i - p + 1) { comb[i]++; if (i == p - 1) { for (int j = 0; j < p; j++) printf("%d", comb[j]); printf(" "); } else { comb[++i] = comb[i - 1]; } } else i--; } } |
要查看它有多快,请使用此代码并测试它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <time.h> #include <stdio.h> void main(int argc, char *argv[]) { const int n = 32; /* The size of the set; for {1, 2, 3, 4} it's 4 */ const int p = 16; /* The size of the subsets; for {1, 2}, {1, 3}, ... it's 2 */ int comb[40] = {0}; /* comb[i] is the index of the i-th element in the combination */ int c = 0; int i = 0; for (int j = 0; j <= n; j++) comb[j] = 0; while (i >= 0) { if (comb[i] < n + i - p + 1) { comb[i]++; /* if (i == p - 1) { for (int j = 0; j < p; j++) printf("%d", comb[j]); printf(" "); } */ if (i == p - 1) c++; else { comb[++i] = comb[i - 1]; } } else i--; } printf("%d!%d == %d combination(s) in %15.3f second(s) ", p, n, c, clock()/1000.0); } |
用cmd.exe(Windows)测试:
1 2 3 4 5 6 7 | Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. c:\Program Files\lcc\projects>combination 16!32 == 601080390 combination(s) in 5.781 second(s) c:\Program Files\lcc\projects> |
祝你今天愉快。
这是我提出的解决这个问题的算法。它以C++编写,但可以适用于支持逐位操作的任何语言。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void r_nCr(const unsigned int &startNum, const unsigned int &bitVal, const unsigned int &testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1) { unsigned int n = (startNum - bitVal) << 1; n += bitVal ? 1 : 0; for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s cout << (n >> (i - 1) & 1); cout << endl; if (!(n & testNum) && n != startNum) r_nCr(n, bitVal, testNum); if (bitVal && bitVal < testNum) r_nCr(startNum, bitVal >> 1, testNum); } |
你可以在这里看到它是如何工作的解释。
在Python中,利用递归和所有事情都是通过引用完成的事实。对于非常大的集合,这将占用大量的内存,但其优点是初始集合可以是一个复杂的对象。它只能找到唯一的组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import copy def find_combinations( length, set, combinations = None, candidate = None ): # recursive function to calculate all unique combinations of unique values # from [set], given combinations of [length]. The result is populated # into the 'combinations' list. # if combinations == None: combinations = [] if candidate == None: candidate = [] for item in set: if item in candidate: # this item already appears in the current combination somewhere. # skip it continue attempt = copy.deepcopy(candidate) attempt.append(item) # sorting the subset is what gives us completely unique combinations, # so that [1, 2, 3] and [1, 3, 2] will be treated as equals attempt.sort() if len(attempt) < length: # the current attempt at finding a new combination is still too # short, so add another item to the end of the set # yay recursion! find_combinations( length, set, combinations, attempt ) else: # the current combination attempt is the right length. If it # already appears in the list of found combinations then we'll # skip it. if attempt in combinations: continue else: # otherwise, we append it to the list of found combinations # and move on. combinations.append(attempt) continue return len(combinations) |
你这样用。传递"result"是可选的,因此您可以使用它来获取可能的组合数…尽管这会非常低效(最好通过计算来完成)。
1 2 3 4 5 6 7 | size = 3 set = [1, 2, 3, 4, 5] result = [] num = find_combinations( size, set, result ) print"size %d results in %d sets" % (size, num) print"result: %s" % (result,) |
您应该从该测试数据中获得以下输出:
1 2 | size 3 results in 10 sets result: [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]] |
如果你的套装看起来像这样,它也能正常工作:
1 2 3 4 5 6 7 | set = [ [ 'vanilla', 'cupcake' ], [ 'chocolate', 'pudding' ], [ 'vanilla', 'pudding' ], [ 'chocolate', 'cookie' ], [ 'mint', 'cookie' ] ] |
C简单算法。(自从我尝试使用你们上传的那个,我就发布了,但是出于某种原因我不能编译它——扩展一个类?所以我自己写了一封信,以防别人也面临同样的问题。顺便说一句,我对C语言的了解不比基本编程多,但这一个很好用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public static List<List<int>> GetSubsetsOfSizeK(List<int> lInputSet, int k) { List<List<int>> lSubsets = new List<List<int>>(); GetSubsetsOfSizeK_rec(lInputSet, k, 0, new List<int>(), lSubsets); return lSubsets; } public static void GetSubsetsOfSizeK_rec(List<int> lInputSet, int k, int i, List<int> lCurrSet, List<List<int>> lSubsets) { if (lCurrSet.Count == k) { lSubsets.Add(lCurrSet); return; } if (i >= lInputSet.Count) return; List<int> lWith = new List<int>(lCurrSet); List<int> lWithout = new List<int>(lCurrSet); lWith.Add(lInputSet[i++]); GetSubsetsOfSizeK_rec(lInputSet, k, i, lWith, lSubsets); GetSubsetsOfSizeK_rec(lInputSet, k, i, lWithout, lSubsets); } |
用法:
您可以修改它来迭代您正在处理的任何内容。
祝你好运!
这是一个C++解决方案,我使用递归和位移位来解决。它也可以用C语言工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void r_nCr(unsigned int startNum, unsigned int bitVal, unsigned int testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1) { unsigned int n = (startNum - bitVal) << 1; n += bitVal ? 1 : 0; for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s cout << (n >> (i - 1) & 1); cout << endl; if (!(n & testNum) && n != startNum) r_nCr(n, bitVal, testNum); if (bitVal && bitVal < testNum) r_nCr(startNum, bitVal >> 1, testNum); } |
你可以在这里找到这是如何工作的解释。
短时间快速C实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | public static IEnumerable<IEnumerable<T>> Combinations<T>(IEnumerable<T> elements, int k) { return Combinations(elements.Count(), k).Select(p => p.Select(q => elements.ElementAt(q))); } public static List<int[]> Combinations(int setLenght, int subSetLenght) //5, 3 { var result = new List<int[]>(); var lastIndex = subSetLenght - 1; var dif = setLenght - subSetLenght; var prevSubSet = new int[subSetLenght]; var lastSubSet = new int[subSetLenght]; for (int i = 0; i < subSetLenght; i++) { prevSubSet[i] = i; lastSubSet[i] = i + dif; } while(true) { //add subSet ad result set var n = new int[subSetLenght]; for (int i = 0; i < subSetLenght; i++) n[i] = prevSubSet[i]; result.Add(n); if (prevSubSet[0] >= lastSubSet[0]) break; //start at index 1 because index 0 is checked and breaking in the current loop int j = 1; for (; j < subSetLenght; j++) { if (prevSubSet[j] >= lastSubSet[j]) { prevSubSet[j - 1]++; for (int p = j; p < subSetLenght; p++) prevSubSet[p] = prevSubSet[p - 1] + 1; break; } } if (j > lastIndex) prevSubSet[lastIndex]++; } return result; } |
也许我遗漏了这一点(您需要的是算法,而不是现成的解决方案),但scala似乎是开箱即用的(现在):
1 2 3 | def combis(str:String, k:Int):Array[String] = { str.combinations(k).toArray } |
使用如下方法:
1 | println(combis("abcd",2).toList) |
将产生:
1 | List(ab, ac, ad, bc, bd, cd) |
这是我在javascript中的贡献(无递归)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | set = ["q0","q1","q2","q3"] collector = [] function comb(num) { results = [] one_comb = [] for (i = set.length - 1; i >= 0; --i) { tmp = Math.pow(2, i) quotient = parseInt(num / tmp) results.push(quotient) num = num % tmp } k = 0 for (i = 0; i < results.length; ++i) if (results[i]) { ++k one_comb.push(set[i]) } if (collector[k] == undefined) collector[k] = [] collector[k].push(one_comb) } sum = 0 for (i = 0; i < set.length; ++i) sum += Math.pow(2, i) for (ii = sum; ii > 0; --ii) comb(ii) cnt = 0 for (i = 1; i < collector.length; ++i) { n = 0 for (j = 0; j < collector[i].length; ++j) document.write(++cnt," -" + (++n) +" -", collector[i][j],"") document.write("") } |
这是一个coffeescript实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | combinations: (list, n) -> permuations = Math.pow(2, list.length) - 1 out = [] combinations = [] while permuations out = [] for i in [0..list.length] y = ( 1 << i ) if( y & permuations and (y isnt permuations)) out.push(list[i]) if out.length <= n and out.length > 0 combinations.push(out) permuations-- return combinations |
还有另一个递归解决方案(您应该能够将其移植到使用字母而不是数字的端口),它使用的堆栈比大多数版本都要短一些:
1 2 3 4 5 6 7 8 9 10 11 12 13 | stack = [] def choose(n,x): r(0,0,n+1,x) def r(p, c, n,x): if x-c == 0: print stack return for i in range(p, n-(x-1)+c): stack.append(i) r(i+1,c+1,n,x) stack.pop() |
4选择3,或者我要从0到4开始的所有3个数字组合
1 2 3 4 5 6 7 8 9 10 11 12 | choose(4,3) [0, 1, 2] [0, 1, 3] [0, 1, 4] [0, 2, 3] [0, 2, 4] [0, 3, 4] [1, 2, 3] [1, 2, 4] [1, 3, 4] [2, 3, 4] |
下面是一个使用宏的Lisp方法。这在普通的lisp中有效,并且应该在其他lisp方言中有效。
下面的代码创建"n"嵌套循环,并为列表
1 2 3 4 5 6 7 | (defmacro do-combinations ((var lst num) &body body) (loop with syms = (loop repeat num collect (gensym)) for i on syms for k = `(loop for ,(car i) on (cdr ,(cadr i)) do (let ((,var (list ,@(reverse syms)))) (progn ,@body))) then `(loop for ,(car i) on ,(if (cadr i) `(cdr ,(cadr i)) lst) do ,k) finally (return k))) |
让我们看看…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (macroexpand-1 '(do-combinations (p '(1 2 3 4 5 6 7) 4) (pprint (mapcar #'car p)))) (LOOP FOR #:G3217 ON '(1 2 3 4 5 6 7) DO (LOOP FOR #:G3216 ON (CDR #:G3217) DO (LOOP FOR #:G3215 ON (CDR #:G3216) DO (LOOP FOR #:G3214 ON (CDR #:G3215) DO (LET ((P (LIST #:G3217 #:G3216 #:G3215 #:G3214))) (PROGN (PPRINT (MAPCAR #'CAR P)))))))) (do-combinations (p '(1 2 3 4 5 6 7) 4) (pprint (mapcar #'car p))) (1 2 3 4) (1 2 3 5) (1 2 3 6) ... |
由于默认情况下不存储组合,因此存储量保持在最小值。选择
这个答案怎么样……这个打印所有长度3的组合……它可以概括为任何长度……工作代码…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include<iostream> #include<string> using namespace std; void combination(string a,string dest){ int l = dest.length(); if(a.empty() && l == 3 ){ cout<<dest<<endl;} else{ if(!a.empty() && dest.length() < 3 ){ combination(a.substr(1,a.length()),dest+a[0]);} if(!a.empty() && dest.length() <= 3 ){ combination(a.substr(1,a.length()),dest);} } } int main(){ string demo("abcd"); combination(demo,""); return 0; } |
下面是我的javascript解决方案,通过使用reduce/map,它可以消除几乎所有的变量,使其功能更加强大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function combinations(arr, size) { var len = arr.length; if (size > len) return []; if (!size) return [[]]; if (size == len) return [arr]; return arr.reduce(function (acc, val, i) { var res = combinations(arr.slice(i + 1), size - 1) .map(function (comb) { return [val].concat(comb); }); return acc.concat(res); }, []); } var combs = combinations([1,2,3,4,5,6,7,8],3); combs.map(function (comb) { document.body.innerHTML += comb.toString() + '<br />'; }); document.body.innerHTML += '<br /> Total combinations = ' + combs.length; |
C/C++中的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #include <unistd.h> #include <stdio.h> #include <iconv.h> #include <string.h> #include <errno.h> #include <stdlib.h> int main(int argc, char **argv) { int opt = -1, min_len = 0, max_len = 0; char ofile[256], fchar[2], tchar[2]; ofile[0] = 0; fchar[0] = 0; tchar[0] = 0; while((opt = getopt(argc, argv,"o:f:t:l:L:")) != -1) { switch(opt) { case 'o': strncpy(ofile, optarg, 255); break; case 'f': strncpy(fchar, optarg, 1); break; case 't': strncpy(tchar, optarg, 1); break; case 'l': min_len = atoi(optarg); break; case 'L': max_len = atoi(optarg); break; default: printf("usage: %s -oftlL \t-o output file \t-f from char \t-t to char \t-l min seq len \t-L max seq len", argv[0]); } } if(max_len < 1) { printf("error, length must be more than 0 "); return 1; } if(min_len > max_len) { printf("error, max length must be greater or equal min_length "); return 1; } if((int)fchar[0] > (int)tchar[0]) { printf("error, invalid range specified "); return 1; } FILE *out = fopen(ofile,"w"); if(!out) { printf("failed to open input file with error: %s ", strerror(errno)); return 1; } int cur_len = min_len; while(cur_len <= max_len) { char buf[cur_len]; for(int i = 0; i < cur_len; i++) buf[i] = fchar[0]; fwrite(buf, cur_len, 1, out); fwrite(" ", 1, 1, out); while(buf[0] != (tchar[0]+1)) { while(buf[cur_len-1] < tchar[0]) { (int)buf[cur_len-1]++; fwrite(buf, cur_len, 1, out); fwrite(" ", 1, 1, out); } if(cur_len < 2) break; if(buf[0] == tchar[0]) { bool stop = true; for(int i = 1; i < cur_len; i++) { if(buf[i] != tchar[0]) { stop = false; break; } } if(stop) break; } int u = cur_len-2; for(; u>=0 && buf[u] >= tchar[0]; u--) ; (int)buf[u]++; for(int i = u+1; i < cur_len; i++) buf[i] = fchar[0]; fwrite(buf, cur_len, 1, out); fwrite(" ", 1, 1, out); } cur_len++; } fclose(out); return 0; } |
这里我在C++中实现,它将所有的组合写入指定的文件,但是行为可以改变,我制作了各种字典,它接受最小和最大长度和字符范围,目前只支持ANSI,它满足了我的需要。
如果你是C++用户,你可以利用NExtx置换函数:
1 2 3 4 5 6 7 8 9 | string name ="abcdefg"; int desiredCharsNumber = 3; string chosenChars = string(name.size()-desiredCharsNumber,'0') + string(desiredCharsNumber, '1'); do { for (int i = 0; i < name.size(); ++i) if (chosenChars[i] == '1') cout << name[i]; cout << endl; } while (next_permutation(chosenChars.begin(), chosenChars.end())); |