C语言实现的排列组合问题的通用算法、解决方法
尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:
1.首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
2.从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。
下面是递归方法的实现:
///求从数组a[1..n]中任选m个元素的所有组合。
///a[1..n]表示候选集,n为候选集大小,n>=m>0。
///b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
///常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
voidcombine(inta[],intn,intm, intb[],constintM)
{
for(inti=n;i>=m;i--) //注意这里的循环范围
{
b[m-1]=i-1;
if(m>1)
combine(a,i-1,m-1,b,M);
else //m==1,输出一个组合
{
for(intj=M-1;j>=0;j--)
cout<<a[b[j]]<<"";
cout<<endl;
}
}
}
因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:
1 2 3 4
2 3 4
3 4
上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。显然,任一路径经过的数字序列就对应一个符合要求的组合。
下面是非递归的回溯方法的实现:
///a[1..n]表示候选集,m表示一个组合的元素个数。
///返回所有组合的总数。
intcombine(inta[],intn,intm)
{
m=m>n?n:m;
int*order=newint[m+1];
for(inti=0;i<=m;i++)
order[i]=i-1; //注意这里order[0]=-1用来作为循环判断标识
intcount=0;
intk=m;
boolflag=true; //标志找到一个有效组合
while(order[0]==-1)
{
if(flag) //输出符合要求的组合
{
for(i=1;i<=m;i++)
cout<<a[order[i]]<<"";
cout<<endl;
count++;
flag=false;
}
order[k]++; //在当前位置选择新的数字
if(order[k]==n) //当前位置已无数字可选,回溯
{
order[k--]=0;
continue;
}
if(k<m) //更新当前位置的下一位置的数字
{
order[++k]=order[k-1];
continue;
}
if(k==m)
flag=true;
}
delete[]order;
returncount;
}
下面是测试以上函数的程序:
intmain()
{
constintN=4;
constintM=3;
inta[N];
for(inti=0;i<N;i++)
a[i]=i+1;
//回溯方法
cout<<combine(a,N,3)<<endl;
//递归方法
intb[M];
combine(a,N,M,b,M);
return0;
}
由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。
n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:
1.从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii;
2.再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*j(j可能等于ii),将i,j元素对调;
3.将ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。
其实现代码如下:
template<classBidirectionalIterator>
boolnext_permutation(BidirectionalIteratorfirst,BidirectionalIteratorlast)
{
if(first==last)returnfalse; //空???br/> BidirectionalIteratori=first;
++i;
if(i==last)returnfalse; //只有一??元素
i=last; //i指向尾端
--i;
for(;;)
{
BidirectionalIteratorii=i;
--i;
//以上,?定一?(???)相?元素
if(*i<*ii) //如果前一??元素小於後一??元素
{
BidirectionalIteratorj=last; //令j指向尾端
while(!(*i<*--j)); //由尾端往前找,直到遇上比*i大的元素
iter_swap(i,j); //交?Qi,j
reverse(ii,last); //?ii之後的元素全部逆向重排
returntrue;
}
if(i==first) //?行至最前面了
{
reverse(first,last); //全部逆向重排
returnfalse;
}
}
}
下面程序演示了利用next_permutation来求取某个序列全排列的方法:
intmain()
{
intia[]={1,2,3,4};
vector<int>iv(ia,ia+sizeof(ia)/sizeof(int));
copy(iv.begin(),iv.end(),ostream_iterator<int>(cout,""));
cout<<endl;
while(next_permutation(iv.begin(),iv.end()))
{
copy(iv.begin(),iv.end(),ostream_iterator<int>(cout,""));
cout<<endl;
}
return0;
}
注意:上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。
相关文章
- FOC和SVPWM的C语言代码实现「建议收藏」
- [栈] 表达式求值 – C语言(多位数求值,2位数以上)
- C语言:大数取余_c语言15和50取余等于多少
- C语言入门经典书籍_C语言从入门到精通
- 2018年蓝桥杯全球变暖(简单方法求贡献)----C语言—菜鸟级
- [C语言] 数据结构-算法效率的度量方法-事前分析估算方法详解编程语言
- Linux下C语言源文件编程(linuxc源文件)
- 预定义宏,C语言预定义的宏详解
- C语言宏的定义和宏的使用方法(#define)
- 信息Linux系统C语言获取磁盘信息的方法(linuxc获取磁盘)
- Linux下C语言求和的实现方法(linux的求和c语言)
- C语言操作Oracle数据库的实现方法(c读取oracle数据库)
- 深入学习C语言与Linux应用开发(clinux学习)
- Linux环境下如何创建C语言项目(linux 创建c语言)
- MySQL上利用C语言编写的数据库操作方法(c 关于mysql的方法)
- C语言实现TCP连接MySQL的方法(c tcp连接mysql)
- C语言MySQL中用于生成随机数的方法(c mysql生成随机数)
- C语言编写MySQL数据备份程序实现方法(c mysql备份程序)
- 学习C语言连接Oracle的简单方法(c 链接oracle方法)
- 如何让C语言中的Oracle连接顺利配置(c 连oracle配置)
- 数据库编程 学习C语言 Oracle 数据库编程之旅(c语言的oracle)
- 如何优化C语言使用Oracle数据库的性能(c使用oracle数据库)
- C语言数与串之间转换的方法
- 深入C语言把文件读入字符串以及将字符串写入文件的解决方法
- C语言切割多层字符串(strtok_rstrtok使用方法)
- C语言金币阵列问题解决方法