高效遍历一个数组的所有排列组合情况

1. 前言

本文主要是基于Aviad P.的2篇文章:A C# List Permutation IteratorA C# Combinations Iterator。分别介绍了如何遍历排列组合情况。使用的算法不需要额外分配空间,所以比较高效。

2. 实现

 1 public static class Iterator
 2 {
 3     private static void RotateLeft<T>(IList<T> sequence, int start, int count)
 4     {
 5         var tmp = sequence[start];
 6         sequence.RemoveAt(start);
 7         sequence.Insert(start + count - 1, tmp);
 8     }
 9 
10     private static void RotateRight<T>(IList<T> sequence, int count)
11     {
12         var tmp = sequence[count - 1];
13         sequence.RemoveAt(count - 1);
14         sequence.Insert(0, tmp);
15     }
16 
17     private static IEnumerable<IList<T>> Combinations<T>(IList<T> sequence, int start, int count, int choose)
18     {
19         if (choose == 0) {
20             yield return sequence;
21         }
22         else {
23             for (var i = 0; i < count; ++i) {
24                 foreach (var comb in Combinations(sequence, start + 1, count - 1 - i, choose - 1)) {
25                     yield return comb;
26                 }
27                 RotateLeft(sequence, start, count);
28             }
29         }
30     }
31 
32     /// <summary>
33     /// 迭代从sequence中取出choose个元素的所有组合情况
34     /// 即C(n,m),n为sequence.Count,m为choose
35     /// </summary>
36     /// <typeparam name="T"></typeparam>
37     /// <param name="sequence"></param>
38     /// <param name="choose"></param>
39     /// <returns>注意返回的list的长度没有缩短成choose,用户可以只遍历list的前choose个元素即可,或者调用Take(choose)取出前choose个元素</returns>
40     public static IEnumerable<IList<T>> Combinations<T>(this IList<T> sequence, int choose)
41     {
42         return Combinations(sequence, 0, sequence.Count, choose);
43     }
44 
45     private static IEnumerable<IList<T>> Permutations<T>(IList<T> sequence, int count)
46     {
47         if (count == 1) {
48             yield return sequence;
49         }
50         else {
51             for (var i = 0; i < count; ++i) {
52                 foreach (var perm in Permutations(sequence, count - 1)) {
53                     yield return perm;
54                 }
55                 RotateRight(sequence, count);
56             }
57         }
58     }
59 
60     /// <summary>
61     /// 迭代sequence所有的排列情况
62     /// </summary>
63     /// <typeparam name="T"></typeparam>
64     /// <param name="sequence"></param>
65     /// <returns></returns>
66     public static IEnumerable<IList<T>> Permutations<T>(this IList<T> sequence)
67     {
68         return Permutations(sequence, sequence.Count);
69     }
70 
71     /// <summary>
72     /// 返回从字符串s中取出count个字母的所有组合情况
73     /// </summary>
74     /// <param name="s"></param>
75     /// <param name="count"></param>
76     /// <returns></returns>
77     public static IEnumerable<string> Combinations(this string s, int count)
78     {
79         foreach (var comb in s.ToCharArray().ToList().Combinations(count)) {
80             yield return string.Join(string.Empty, comb.Take(count));
81         }
82     }
83 
84     /// <summary>
85     /// 返回字符串s的所有排列情况
86     /// </summary>
87     /// <param name="s"></param>
88     /// <returns></returns>
89     public static IEnumerable<string> Permutations(this string s)
90     {
91         foreach (var prem in s.ToCharArray().ToList().Permutations()) {
92             yield return string.Join(string.Empty, prem);
93         }
94     }
95 }

3. 测试

1 foreach (var s in "abcdef".Combinations(3)) {
2     Console.Write("{0,-8}", s);
3 }
4 
5 Console.WriteLine("---------------------");
6 
7 foreach (var s in "abc".Permutations()) {
8     Console.Write("{0,-8}", s);
9 }

上面会输出

abc     abd     abe     abf     acd     ace     acf     ade     adf     aef     bcd     bce     bcf     bde     bdf     bef     cde     cdf     cef     def
---------------------
abc     bac     cab     acb     bca     cba

4. 总结

这个算法完全不需要分配额外空间,直接在原空间里进行遍历,所以对内存比较友好。但由于遍历过程会直接修改原数组,如果你不能接受这种情况,可以考虑在遍历前拷贝一份,对拷贝的那个数组进行遍历即可。

posted @ 2022-03-04 12:39  电子_精灵  阅读(1105)  评论(0编辑  收藏  举报