高效遍歷一個數組的所有排列組合情況

高效遍歷一個數組的所有排列組合情況

1. 前言

本文主要是基於Aviad P.的2篇文章:A C# List Permutation IteratorA 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. 總結

這個算法完全不需要分配額外空間,直接在原空間裏進行遍歷,所以對內存比較友好。但由於遍歷過程會直接修改原數組,如果你不能接受這種情況,可以考慮在遍歷前拷貝一份,對拷貝的那個數組進行遍歷即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章