高效遍历一个数组的所有排列组合情况
1. 前言
本文主要是基于Aviad P.的2篇文章:A C# List Permutation Iterator,A 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. 总结
这个算法完全不需要分配额外空间,直接在原空间里进行遍历,所以对内存比较友好。但由于遍历过程会直接修改原数组,如果你不能接受这种情况,可以考虑在遍历前拷贝一份,对拷贝的那个数组进行遍历即可。