codeup-5973 【递归入门】组合的输出题解——关于怎么非递归解这道题
组合的输出
排列与组合是常用的数学方法,其中组合就是从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开始进行读取,到读完下标n结束的,即第几个下标就是几。
这道题,你仔细观察,组合,无序状态,你就按照从小到大排序输出(虽然题目也是叫你这么干的),不会重复不会漏,首先打一把1,2,3,这个循环就能实现的,接下来怎么做,你可以发现后面变成了1,2,4;1,2,5,显然是末尾++了,加到什么时候能结束呢?(dfs递归的时候必问:什么时候能结束?)那就是第r - i个等于n - i的时候,为什么?你第r - i个都是n - i了,你后面就n - i + 1, n - i + 2, ......, n,就这一种情况,你要是大于n - i,你让后面的兄弟怎么搞啊,对不对。好,退出,什么时候结束呢,当循环退出时i == r,不好意思,你的这时候已经到达第0个了,你总不能把第0位加一吧,我们是从第一位开始读取的,所以不存在第0位,这个时候退出。
好这个时候你知道进位了,进位完你怎么办呢?你仔细观察1,3,4;1,3,5,题目样例在进位完之后后面的数字都是比前一个多一,当然这样是比上一个大的最小集合,满足条件,也找到了套路,进位完的后面就一次比前面多一,由于该位最大为r - i,所以后面不会超过范围,然后一次这样。
AC代码:
#include <stdio.h> int ans[22]; bool vis[22]; int n, r; bool flag; void print() { for(int k = 1; k <= r; k++) { if(k != r) printf("%d ", ans[k]); else printf("%d\n", ans[k]); } } void solve() { if(ans[r] < n) ans[r]++; else { int i = 1; while(ans[r - i] == n - i && r - i > 0) { i++; } if(r == i) { flag = false; return; } ans[r - i]++; for(int j = r - i + 1; j <= r; j++) { ans[j] = ans[j - 1] + 1; } } print(); } int main(void) { while(scanf("%d %d", &n, &r) == 2) { flag = true; for(int i = 1; i <= r; i++) { ans[i] = i; } print(); while(flag) solve(); } return 0; }
总结:还是递归大法好,dfs题来时命能保(逃),非递归的话应该在一次调用函数时就算到递归找到解时的状态(例如本题调用一次函数就找到了一个答案),采用while循环控制什么时候能退出,在找到一次答案时也要在函数里面采用while循环模拟每次递归进行的操作(例如本题中while进行进位和末尾++)。