1. 程式人生 > >C# 計算排列組合數,及列出所有組合形式的演算法

C# 計算排列組合數,及列出所有組合形式的演算法

前段時間有同學問到,如何程式設計求排列組合數,以及列出所有排列組合形式的演算法。乘著放假,寫了一種實現的方法!怕時間長了,淹沒在硬盤裡,記錄在此!

        /// <summary>
        /// 計算Int32型別的整數的階乘,目前最大隻能對20以內的正整數求階乘
        /// </summary>
        /// <param name="n">Int32型別的正整數</param>
        /// <returns></returns>
        public static long Factor(this
int n) { long result = -1; checked { try { if (n < 0) { result = -1; } else { if
(n == 0 || n == 1) { result = 1; } else { result = n * (n - 1).Factor(); } } } catch
(OverflowException) { result = -1; } } return result; }

計算階乘的遞迴演算法,後續會用到,雖然用了long型別,但也只能計算到20的階乘

        /// <summary>
        /// 計算從n個不同元素中任選m個元素的排列個數.n應該大於等於m!
        /// </summary>
        /// <param name="n">供排列選擇的元素個數,正整數</param>
        /// <param name="m">排列選取的元素個數,正整數</param>
        /// <returns>排列個數</returns>
        public static long Permutation(int n,int m)
        {
            int[] N = new int[n];
            int[] SubM = new int[n-m];
            long result = 0;
            if (n < m)
            {
                result = 0;
            }
            else
            { //初始化陣列N和M
                for (int i = 0; i < n; i++)
                {
                    N[i] = i + 1;
                    if (i < n - m)
                    {
                        SubM[i] = i + 1;
                    }
                }
                //消除兩個陣列中的重複元素
                for (int i = 0; i < n - m; i++)
                {
                    if (SubM[i] == N[i])
                    {
                        N[i] = 1;
                    }
                }
                //計算N中剩餘元素的累乘
                result = 1;
                checked
                {
                    try
                    {
                        for (int i = 0; i < n; i++)
                        {
                            result *= N[i];
                        }
                    }
                    catch (OverflowException)
                    {
                        result = -1;
                    }

                }
            }
            return result;
        }

計算排列數,主要思想是類似於小學數學中求分式相乘中的消除法,主要是為了避免求階乘的限制

        /// <summary>
        /// 計算從n個不同元素中選取m個元素的組合個數。n應該大於等於m
        /// </summary>
        /// <param name="n">供組合選擇的元素個數,正整數</param>
        /// <param name="m">組合選取的元素個數,正整數</param>
        /// <returns>組合個數</returns>
        public static long Combination(int n, int m)
        {
            long factM = m.Factor();
            long result = 0;
            int[] N, M, subM;

            if (n < m)
            {
                result = 0;
            }
            else
            {
                if (factM > 0)
                {
                    result = Permutation(n, m) / factM;
                }
                else
                {
                    N = new int[n]; M = new int[m]; subM = new int[n - m];
                    //初始化三個陣列
                    for (int i = 0; i < n; i++)
                    {
                        N[i] = i + 1;
                        if (i < m)
                        {
                            M[i] = i + 1;
                        }
                        if (i < n - m)
                        {
                            subM[i] = i + 1;
                        }
                    }
                    //消除重複元素,因為當m的階乘溢位時才會進入此分支,所以只考慮和陣列M進行消除
                    for(int i = 0; i < m; i++)
                    {
                        if (N[i] == M[i])
                        {
                            N[i] = 1;
                        }
                    }
                    //計算陣列N和subM的累乘
                    long rN = 1, rSubM = 1;
                    for (int i = 0; i < n; i++)
                    {
                        rN *= N[i];
                        if (i < n - m)
                        {
                            rSubM *= subM[i];
                        }
                    }
                    //計算組合個數
                    result = rN / rSubM;
                }
            }

            return result;
        }

計算組合數,思路和求排列數一致,不再贅述

/// <summary>
        /// 獲得從n個不同元素中任意選取m個元素的組合的所有組合形式的列表
        /// </summary>
        /// <param name="elements">供組合選擇的元素</param>
        /// <param name="m">組合中選取的元素個數</param>
        /// <returns>返回一個包含列表的列表,包含的每一個列表就是每一種組合可能</returns>
        public static List<List<T>> GetCombinationList<T>(List<T> elements,int m)
        {
            List<List<T>> result = new List<List<T>>();//存放返回的列表
            List<List<T>> temp = null; //臨時存放從下一級遞迴呼叫中返回的結果
            List<T> oneList = null; //存放每次選取的第一個元素構成的列表,當只需選取一個元素時,用來存放剩下的元素分別取其中一個構成的列表;
            T oneElment; //每次選取的元素
            List<T> source = new List<T>(elements); //將傳遞進來的元素列表拷貝出來進行處理,防止後續步驟修改原始列表,造成遞迴返回後原始列表被修改;
            int n = 0; //待處理的元素個數

            if (elements != null)
            {
                n = elements.Count;
            }
            if(n==m && m != 1)//n=m時只需將剩下的元素作為一個列表全部輸出
            {
                result.Add(source);
                return result;
            }
            if (m == 1)  //只選取一個時,將列表中的元素依次列出
            {
                foreach(T el in source)
                {
                    oneList = new List<T>();
                    oneList.Add(el);
                    result.Add(oneList);
                    oneList = null;
                }
                return result;
            }

            for (int i = 0; i <= n - m; i++)
            {
                oneElment = source[0];
                source.RemoveAt(0);
                temp = GetCombinationList(source, m - 1);
                for (int j = 0; j < temp.Count; j++)
                {
                    oneList = new List<T>();
                    oneList.Add(oneElment);
                    oneList.AddRange(temp[j]);
                    result.Add(oneList);
                    oneList = null;
                }
            }


            return result;
        }

求所有可能的組合形式,一個遞迴實現,依次選擇待組合元素中的前n-m個元素,每次選擇後在剩下的元素中選取m-1個元素,直到m==1或者列表元素個數等於m則停止遞迴
排列的所有形式可以在此基礎上得到,但是還沒有想到滿意的演算法!思考中……
PS:為了更明白的表達解題思路,演算法沒有做優化!