因为看了很多书,貌似学习到了很多知识,可是很多东西感觉一旦翻篇了,就很容易忘记,本人又是一个习惯很不好的人,经常找不到以前记录的笔记,所以打算开一个博客,来记录自己每天工作和学习的内容,不知道能坚持多久,就当是日记,一方面为了让自己学到和理解的东西不会马上被忘记,同时也督促自己锻炼自己的表达能力,也督促自己能始终坚持。
首先关于排列组合,是我们在高中或者初中数学中经常接触到的一些具有非常大难度的逻辑问题,当然这篇博客只针对最普通最普遍的排列组合的算法,诸如全排列,组合等等。
此篇博客完全不涉及效率问题,全当自己的一些推论和想法
进入正文:
关于排列的算法,各种大神都有自己的见解,网上看到最多算法都类似这种,我个人初看代码会觉得很不好理解
for(i = s; i <= e; i++) { swap(list, s, i); perm(list, s + 1, e, cbk); swap(list, s, i); } }
而因为我之前刚刚学过八皇后的回溯方法,我到觉得排列的方法可以用类似的回溯算法,这讲道理好像更像一种深度优先的算法。
比如以下我们写一个关于0,1,2三个数字的全排列的方式相当于a(3,3);
int number[10] = {};
int pp(int n) //用于打印而已
{
int i = 0;
for(i=0;i<n;i++)
{
printf("%d",number[i]);
}
printf("\n");
}
int check(int n,int v)
{
int i = 0;
for(i=0;i<=n;i++)
{
if(number[i] == v)
{
return 0;
}
}
return 1;
}
int allpermutation(int n)
{
if(n>=3)
{
pp(n);
}
else
{
int i = 0;
for(i=0;i<3;i++)
{
if(check(n-1,i))
{
number[n] = i;
allpermutation(n+1);
}
}
}
}
int main()
{
allpermutation(0);
}
而实际上,如果把上述代码的注释9999那段去除,该代码表现的便是0,1,2三个数字的可以重复的全排列,也就是3x3x3=27种排列方式
以上是关于回溯方法求解排列的一种思路,完全可以按照此思路定制自己的全排列方法,比如从m个数字中选取n个数字的全排列方法
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
关于组合的一些问题
之前有个小兄弟问我,怎么用c语言来算c(8,4)这种算法,即不用公式我们来算出从8个数字里面选取4个有几种方法,我觉得这个问题很有想法,关于计算器其实只会加法
,那么我们如何只用加法来算出组合的个数呢。
我想到的是构造问题,比如说我们怎么从坐标点(0,0)到(3,3)的最短路径的几种方法,每次只能往上或往右移动一格,就是相当于,因为这个问题的最短路径肯定是8,
而且肯定是4条横的,4条竖的,这样问题就转化为,在8条路径里选择4条横的,则问题转化为c(8,4).
代码如下:
int getnumber(int m,int n)
{
static int i = 0;
if(m>3&&n>3)
{
i++;
return i;
}
if(m>3)
{
getnumber(m,n+1);
}
else if(n>3)
{
getnumber(m+1,n);
}
else
{
getnumber(m,n+1);
getnumber(m+1,n);
}
}
其实上述也可以算一种深度优先搜索的方式,这也只是一种想法而已
然后关于组合的问题:
在组合的问题上市面上有很多很多算法,我个人觉得组合算法是对初接触算法的新手会相当不好理解,我本人就是,我也查阅了很多资料,发现有一个插1的方法,自己推理了一番,我个人觉得会很好理解,比如从5个格子中,选出2个格子,这就是c(5,2),我可以将5个格子初始化默认为全部为0,当选到哪个格子我就将哪个格子的值置为1,这样我置1的格子就相当于索引了,比如第2和第4个格子为1,那我的其中一种取法就是拿索引为2和4的格子
首先看一种错误的方法
代码如下:
//以下代码计算c(4,2);
int array[10] = {};
{
if(n<=0)
{
int j = 0;
for(j=0;j<m;j++)
{
printf("%d",array[j]);
}
printf("\n");
}
else
{
int i = 0;
for(i=0;i<m;i++)
{
if(array[i] == 1)
{
continue;
}
array[i] = 1;
errorcombine(m,n-1);
array[i] = 0; //因为需要记录值,因此递归后需要改回来
}
}
}
{
errorcombine(4,2);
}
终端打印为:
1100
1010
1001
1100
0110
0101
1010
0110
0011
1001
0101
0011
这明明是有顺序了,不是c(4,2)而是a(4,2)了,gdb调试后才发现,程序的改值变的有顺序了。因此我们在第二版加了一个哨兵,让从0改为1的值只在当前点的后面
代码如下:
int combine(int m,int n,int k)
{
if(n<=0)
{
int j = 0;
for(j=0;j<m;j++)
{
printf("%d",array[j]);
}
printf("\n");
}
else
{
int i = 0;
for(i=k;i<m;i++)
{
if(array[i] == 1)
{
continue;
}
array[i] = 1;
combine(m,n-1,i+1);
array[i] = 0;
}
}
}