关于用进制类实现全排列算法
闲来无聊。写了个用进制类(当然,不用类也可以)实现全排列算法的库。
排列/组合是数学中非常常用的算法,当然也是计算彩票中奖几率的基础……
从n个不同的元素中,取r个不重复的元素,按次序排列,称为从n个中取r个的无重排列。排列的全体组成的集合用排列表示。当r=n时称为全排列。从n个不同元素中取r个不重复的元素组成一个子集,而不考虑其元素的顺序,称为从n个中取r个的无重组合。组合的全体组成的集合用组合表示。
用来计算给定n/r时排列、组合的可能数量的公式是很简单的。这里不再列出,但是实际问题往往是要在一个给定的集合中,给出各个可能的组合、排列的构成。例如:给定025689这六个数字(或者abikpq这六个字母),给出这六个数字(字母)的全排列等等。
从网络上的搜索结果来看,我个人的认为是比较复杂,而且思路和代码不是很简单明了。让我以P8/4(在0-7这八个数字中任选4个的排列)作为一个例子,给出我的思路。
首先,我认为网络上的思路的郁结之处就在于过分考虑了无重排列的要求。无重排列当然是排列的主流,但是不能排除重复排列,而且我认为无重排列是重复排列的一个子集。生成一个排列结果后,判断其是否为重复排列是非常简单的。
第二,如果我们观察一下人工进行排列结果计算的过程:0000->0001->0002->……->0007- >0010->0011……->7777,就可以发现这实际上就是一个8进制数的加1过程。这个集合的总数只有4096个(也就是8的 4次方)。如果要得到无重排列,那么数量会降到1680个。
所以,我的思路就是用进制的加法来模拟全排列。下面就是我的类的声明,由于只是为了解决全排列的问题,所以这个进制类并不完整,只实现了操作符+以及输出符号<<的重载。我将它编译成一个库,这样在需要使用的程序中可以随时调用。
#ifndef BaseMH
#define BaseMH
const int MAX_BASE=62;
const String BASE_DIGIT="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const int DEFAULT_LENGTH=8;
const int DEFAULT_BASE=16;
class BaseM
{
public:
BaseM();
BaseM(int Base);
BaseM(const BaseM &aNumber);
BaseM(long l, int Base);
BaseM(String s, int Base);
~BaseM();
// Operators
BaseM& operator = (const BaseM &);
BaseM operator + (const BaseM &);
BaseM operator + (const int &M);
friend ostream& operator<< (ostream& os, const BaseM &M);
private:
int _Base; // Base for the number
int _Digits; // How many digits to store
char * Digits; // String to store digits
void Allocate();
long _DecValue;
};
//-------------------------------------------------------------
#endif
当然,还需要一些辅助的函数来将十进制的数字换算成N进制的“字符串”以及反向转换的函数,以及判断字符串是否有重复的函数等。这些都比较简单,我将其包含在另一个Support单元中。用户可以自行扩充这个Support单元。
为了测试这个库,我编写了一个简单的测试单元:
#include <vcl.h>
#pragma hdrstop
#include "BaseM.h"
#include "Support.h"
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
BaseM a(12345, 16);
BaseM b("0000", 8);
for (int i=1;i<=4096;i++)
{
b=b+1;
String s=FillString(b.StrPresentation(), 4, '0');
if(StringHasDuplicate(s))
continue;
else
cout<<s.c_str()<<" ";
}
return 0;
}
如果要进行无重排列,也只要简单修改上述代码即可。实际运行时,可以看到程序输出了从最小的0123到最大的7654。
在我的算法中,得到组合的难度反而要比得到排列的难度要高。所以获得组合不应该用进制类的算法,而应该用其它的组合生成算法。这里摘录的算法来自:http://www.faq-it.org/archives/structure/5ce188903ad4d8611fd22ab458d2a32d.php。我没有验证,读者自己验证即可:
组合生成:从n个元素中取r个元素的组合
由一个组合c[1]c[2]...c[r]生成下一个组合的算法:
a) i=max{j¦c[j]<n-r+j}
b) c[i]=c[i]+1
c) c[j]=c[j-1]+1,j=i+1,i+2,...,r
不过,我想应该也可以根据排列的进制算法来解决组合的问题。这个就留待有兴趣的朋友自行研究了。
另外,我这里的程序中,用到了VCL的库,这主要是为了能加快开发速度,就没有更多的考虑效率、速度的问题。用纯C/C++类库改写这个类和相应的支持库也是没有问题的。
完整的源代码可以向我来信索取。我的EMAIL?看这里:http://go4pro.org/member.asp!
100分给那些对我的思路有创新/补充并共享出来的朋友。