题目:数组a中有n个元素,从其中选出m(m < n)个元素,输出这m个元素所有不同的组合
分析:
举例如:1 2 3 4 5
从中选出任意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
观察上述例子中,选择的步骤是这样的:
- 从{1, 2, 3, 4, 5}中任意选出第1个元素,如选出了1,
- 然后再从{2, 3, 4, 5}中选择第2个元素,选出2,
- 那么再从{3, 4, 5}中选出第3个元素,假设为3,
- 没有要选择的元素了,最终形成一个组合<1,2,3>。
仔细观察,可以发现,上述过程是递归进行的:
-
检查m是否大于等于1,若大于等于1,进行步骤2;否则执行步骤3;
-
从数组 a[i] ~ a[n-1] 中选出1个元素,假设被选元素在数组中的下标是j (i <= j <= n - 1),那么接下来应该从下标为 j ~ n-1 中选取出 m-1 个,我们令 m = m - 1;再次执行步骤1;
-
m 若等于0,说明 m 个元素已经已经选择出来了,此时将这些被选出来的元素输出即可。
为了引入递归函数combine,减少不必要的传参,我们可以将上述过程进行稍加改动:
每次选择时在数组a中从后向前选择,而不是从前向后,即每次选择都是从 a[n-1] ~ a[0] 倒序依次选择一个元素,假设选出的元素下标为i,引入数组sel记录每次选择的元素下标i,即 sel[m-1] = i; 然后令 n = i; 再次执行选择,直到 m = 0,进行回溯,此时进行一趟选择出m个元素的下标都已经被记录在sel数组中了,只需要将其逆序打印出来即可。
回溯条件:
if(m == 0){
//此处将sel数组记录的被选元素打印出来,或另做处理
return;
}
递归条件: m > 0
for(i = n-1; i >= 0; i--){
sel[m - 1] = i;
//...此处执行递归选择
}
完整递归代码如下:
#include <stdio.h>
/**
C(n,m) 组合--递归实现
参数:
arr -- 元素数组指针
n -- 数组大小
m -- 选择的个数,递归过程会变
sel -- 记录被选元素下标的数组指针
M -- m,初始选择元素的个数,固定不变
**/
void combine(int *arr, int n, int m, int *sel, const int M)
{
int i;
if(m == 0)
{
for(i = M - 1; i >= 0; i--)
{
printf("%d ", arr[sel[i]]);
}
printf("\n");
return ;
}
/*从最后 arr[n-1] ~ a[m-1] 之间依次选取一个数;为什么是到arr[m-1]?
因为arr[0] ~ arr[m-1]不够m个数了。
*/
for(i = n - 1; i >= m-1; i--)
{
sel[m - 1] = i; //选择当前位置作为第m个选择的数
combine(arr, i, m - 1, sel, M); //递归选择前m-1个数
}
}
int main()
{
int i,n, m, a[MAX_SIZE], s[MAX_SIZE];
scanf("%d", &n);
scanf("%d", &m);
for(i=0; i<n; i++)
{
scanf("%d", a + i);
}
combine(a, n, m, s, m);
return 0;
}