C# 計算排列組合數,及列出所有組合形式的演算法
阿新 • • 發佈:2019-02-10
前段時間有同學問到,如何程式設計求排列組合數,以及列出所有排列組合形式的演算法。乘著放假,寫了一種實現的方法!怕時間長了,淹沒在硬盤裡,記錄在此!
/// <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:為了更明白的表達解題思路,演算法沒有做優化!