r-组合生成器——字典序法
这篇文章老早就写了,还是在生日那天写的(泪流满面啊……),今晚重温,作为纪念~~~
题目:给出任意数组,和r, 生成所有的组合序列。
例如:int array = {1,2,3,4,5,6,7}, r = 5
输出:12345, 12346, 12347, 12356, 12357, 12367, 12456, 12457, 12467, 12567, 13456, 13457, 13467, 13567, 14567, 23456, 23457, 23467, 23567, 24567, 34567
个数为 C(7,5) = C(7, 2) = 7*6/2 = 21个
典型用途:N个组合中,选取其中最好的一组解。
例如:某公司领导很开恩,1个月30天,随意让你选择20天来上班,那你怎么来安排工作日与休假日来达到最优呢?你可以借助计算机,C(30,20)来加权选取其中一个最优解。
字典序法就是按照字典排序的思想逐一产生所有排列.
设想要得到由1,2,3,4以各种可能次序产生出4!个“单词”. 肯定先排1234,
再排1243,
下来是1324, 1342, …., 4321.
思路:
我们使用一个长度为r的数组indexArray,存放元素在数组中的序号,然后根据字典序法,动态改变标号。
每次循环从indexArray中从最后一个开始比对,如果其值(序号)小于最大的序号array.Length-1,那么值+1。
否则,继续从倒数第二个开始比对,如果其值(序号)小于第二最大的序号array.Length-2,那么值+1。同时把后续的序号都依次自增。
直到index < 0时,循环结束,输出了所有的序列。
public static void Combine(int[] source, int r)
{
int[] indexArray = new int[r]; //产生序号数组
for (int i = 0; i < r; i++) //初始化序号数组
indexArray[i] = i;
int index = 0; //生成新组合时,index中要变动的下标
int count = 0; //保证每一行输出一个r-组合
while (index >= 0) //终止条件:index == -1
{
foreach (int e in indexArray) //输出r-组合
{
Console.Write(source[e]);
if ((++count) % r == 0) Console.WriteLine();
}
var maxIndex = source.Length - 1; //开始为最大序号,每次循环结束则 - 1
//每次index从序号数组最后开始。
for (index = r - 1; index >= 0; index--, maxIndex--) //以字典序找新的r-组合
{
if (indexArray[index] < maxIndex)
{
indexArray[index]++; //增加序号
for (int j = index + 1; j < r; j++)
indexArray[j] = indexArray[j - 1] + 1; //后面的元素统统按字典序排列
break;
}
}
}
Console.WriteLine("总共有{0}组解", count / r);
}
算法参考:http://hi.baidu.com/woiwojia/blog/item/93ec1ade5e5fd55dcdbf1aac.html
同时我还找到一个老外的:http://www.codeproject.com/KB/recipes/Combinatorics.aspx
public class Combinations<T> : IEnumerable<IList<T>>
{
private List<IList<T>> _combinations;
private IList<T> _items;
private int _length;
private int[] _endIndices;
public Combinations(IList<T> itemList)
: this(itemList, itemList.Count)
{
}
public Combinations(IList<T> itemList, int length)
{
_items = itemList;
_length = length;
_combinations = new List<IList<T>>();
_endIndices = new int[length];
int j = length - 1;
for (int i = _items.Count - 1; i > _items.Count - 1 - length; i--)
{
_endIndices[j] = i;
j--;
}
ComputeCombination();
}
private void ComputeCombination()
{
int[] indices = new int[_length];
for (int i = 0; i < _length; i++)
{
indices[i] = i;
}
do
{
T[] oneCom = new T[_length];
for (int k = 0; k < _length; k++)
{
oneCom[k] = _items[indices[k]];
}
_combinations.Add(oneCom);
}
while (GetNext(indices));
}
private bool GetNext(int[] indices)
{
bool hasMore = true;
for (int j = _endIndices.Length - 1; j > -1; j--)
{
if (indices[j] < _endIndices[j])
{
indices[j]++;
for (int k = 1; j + k < _endIndices.Length; k++)
{
indices[j + k] = indices[j] + k;
}
break;
}
else if (j == 0)
{
hasMore = false;
}
}
return hasMore;
}
public int Count
{
get { return _combinations.Count; }
}
public IEnumerator<IList<T>> GetEnumerator()
{
return _combinations.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
上面这个可以直接在项目里面用,算法一样,也是字典序法,只是它自己保存了一个_endIndices来做保存结束序号,而我的算法是不需要的,因为我会计算结束序号。;)
算法复杂度,O(N^3),里面有3个内嵌的for循环。
有没有更有的算法呢?同时如何计算C(7,5)的值呢?给你们留个作业吧。