题目描述
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n = 5 ,r = 3 ,所有组合为:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
输入
一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。
输出
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。
这个题如果单纯使用dfs的方法应该会更容易实现,但题目要求使用非递归方法来求解(当然你偷偷用递归方法求解也没人知道)。博主之前并没有将递归方法转换为其它方法的经验,不过经过一番思考与试验,还算是摸索出了点经验。
题目意思很容易理解,接下来看我的分析:按字典序输出所有组合,则想到下一个组合肯定和上一个组合有联系。比如例题中第一个组合为1 2 3,那么后一个组合要是接着的字典序最小的组合。怎么找出这个组合,自然能想到把上一个组合中最后一个数字3加1,得到1 2 4。
但是问题是,不能一直加最后一个数字,比如最后一个数字超过了5(即n),则需要进位,然后如果第二后的数字超了4(即n-1)(为什么是4,是因为如果第二后的数为5了那么最后一个数字就没得选了,选4或者更小的数是和前面的排列有重复的),则也需要进位。如此循环,直至哪一位加1后不需要进位为止,这个时候还没有结束。我们需要把这一位不需要进位的位数之后的所有为依次置为前一位加1。
比如当前一个组合是1 4 5时,寻找下一个组合时最后一位加1,得1 4 6,6超过了5,进位,得1 5 6,5超过了4,进位,得2 5 6,2不需要进位,把2后面得两位依次置为前一位加1,得2 3 4。
接着我们需要循环一直往下寻找下一个组合,当我们在进位时访问到0号位时,也就是最前面的那一位都需要进位时,循环就结束了。比如3 4 5,5加1,得3 4 6,进位得3 4 6,再进位得4 4 5,第一位为4,大于3,还需要进位。则表示循环结束。
贴出代码,代码也有帮助理解得注释,应该能看懂了。
#include<cstdio>
int main(){
int n=0,r=0,end=0,sum=0,a[25]={0};
while(scanf("%d",&n)==1){
scanf("%d",&r);
end=0;
for(int i=1;i<=r;i++){//初始化为第一个排列
a[i]=i;
}
while(!end){//判断输出是否到了结尾
for(int i=1;i<=r;i++)printf("%d%c",a[i],i==r?'\n':' ');
int flag=r;//flag表示现在在访问哪个元素
a[r]++;
while(1){
if(flag==0){//如果访问的第0个元素,则表示已经到了输出结尾,标记后break掉
end=1;
break;
}
if(a[flag]>n-r+flag){//还需要再往前进位
a[flag-1]++;
}else{
for(int i=flag+1;i<=n;i++){//不需要再往前进位,所以后面的每一位依次为前一位加一
a[i]=a[i-1]+1;
}
break;
}
flag--;
}
}
}
return 0;
}
都看到这了不妨点个赞吧!