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)的值呢?给你们留个作业吧。

posted @ 2011-05-26 23:40  primeli  阅读(2051)  评论(0编辑  收藏  举报