高效遍历一个数组的所有排列组合情况
1. 前言
本文主要是基于Aviad P.的2篇文章:A C# List Permutation Iterator,A C# Combinations Iterator。分别介绍了如何遍历排列组合情况。使用的算法不需要额外分配空间,所以比较高效。
2. 实现
public static class Iterator
{
private static void RotateLeft<T>(IList<T> sequence, int start, int count)
{
var tmp = sequence[start];
sequence.RemoveAt(start);
sequence.Insert(start + count - 1, tmp);
}
private static void RotateRight<T>(IList<T> sequence, int count)
{
var tmp = sequence[count - 1];
sequence.RemoveAt(count - 1);
sequence.Insert(0, tmp);
}
private static IEnumerable<IList<T>> Combinations<T>(IList<T> sequence, int start, int count, int choose)
{
if (choose == 0) {
yield return sequence;
}
else {
for (var i = 0; i < count; ++i) {
foreach (var comb in Combinations(sequence, start + 1, count - 1 - i, choose - 1)) {
yield return comb;
}
RotateLeft(sequence, start, count);
}
}
}
/// <summary>
/// 迭代从sequence中取出choose个元素的所有组合情况
/// 即C(n,m),n为sequence.Count,m为choose
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sequence"></param>
/// <param name="choose"></param>
/// <returns>注意返回的list的长度没有缩短成choose,用户可以只遍历list的前choose个元素即可,或者调用Take(choose)取出前choose个元素</returns>
public static IEnumerable<IList<T>> Combinations<T>(this IList<T> sequence, int choose)
{
return Combinations(sequence, 0, sequence.Count, choose);
}
private static IEnumerable<IList<T>> Permutations<T>(IList<T> sequence, int count)
{
if (count == 1) {
yield return sequence;
}
else {
for (var i = 0; i < count; ++i) {
foreach (var perm in Permutations(sequence, count - 1)) {
yield return perm;
}
RotateRight(sequence, count);
}
}
}
/// <summary>
/// 迭代sequence所有的排列情况
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sequence"></param>
/// <returns></returns>
public static IEnumerable<IList<T>> Permutations<T>(this IList<T> sequence)
{
return Permutations(sequence, sequence.Count);
}
/// <summary>
/// 返回从字符串s中取出count个字母的所有组合情况
/// </summary>
/// <param name="s"></param>
/// <param name="count"></param>
/// <returns></returns>
public static IEnumerable<string> Combinations(this string s, int count)
{
foreach (var comb in s.ToCharArray().ToList().Combinations(count)) {
yield return string.Join(string.Empty, comb.Take(count));
}
}
/// <summary>
/// 返回字符串s的所有排列情况
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static IEnumerable<string> Permutations(this string s)
{
foreach (var prem in s.ToCharArray().ToList().Permutations()) {
yield return string.Join(string.Empty, prem);
}
}
}
3. 测试
foreach (var s in "abcdef".Combinations(3)) {
Console.Write("{0,-8}", s);
}
Console.WriteLine("---------------------");
foreach (var s in "abc".Permutations()) {
Console.Write("{0,-8}", s);
}
上面会输出
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. 总结
这个算法完全不需要分配额外空间,直接在原空间里进行遍历,所以对内存比较友好。但由于遍历过程会直接修改原数组,如果你不能接受这种情况,可以考虑在遍历前拷贝一份,对拷贝的那个数组进行遍历即可。