组合定义
组合的定义:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。
应用场景
在解决各种算法的问题时,有一种比较偷懒的方式,那就是把所有可能性都试一遍。而排列组合是比较常用的方式,可以通过某种编码方式,让一组排列组合结果来代表某个解,测试所有排列组合,便测试了所有解。
算法思路
从length个元素中选取num个元素,可以创建一个长度为num的数组select,数组内存放的是0到length-1之间的不重复数字。
每一次生成组合,数组select内的规则如下:
- select数组从左到右依次确定各个元素
即先确定select[0],在确定select[1],最后确定select[num-1] - select数组内的每一个元素都大于上一个元素
即select[0] < select[1] < select[2] …… - 每一次生成的组合都要确保不与生成过的重复
例如select曾经为{0,1,2,3,4},那么{0,1,2,3,4}将不再出现 - 每个元素都尽量的选取小的数
当length为8,num为5。select前四个元素分别为0,1,3,4。且{0,1,3,4,5}出现过。那么select[4]可以选取的数字为6,7,选择最小的6。 - 当select内的某个元素已经没有可选数字,则它的上一个元素重新选择。
当length为8,num为5,select 为 {0,1,3,4,7},此时select[4]已经没有选择空间,则select[3]重新进行选择。
由此可见,在这种规则下,可以推断:
- select[i]最大的取值为length - num + i
- 当select[0]为最大值length - num时,代表这是最后一个组合。
源码分享
#include <stdio.h>
#include <stdlib.h>
int test(const int* p,int num);
int *selectAndTest(int length, int num, int (*testFunc)(const int*,int));
int main(int argc, char const *argv[])
{
int *res = NULL;
//从0-3中选出2个数
res = selectAndTest(4,2,test);
if (res != NULL)
{
free(res);
res = NULL;
}
//从0-4中选出3个数
res = selectAndTest(5,3,test);
if (res != NULL)
{
free(res);
res = NULL;
}
return 0;
}
/**
* @brief 测试函数,测试找到的组合
* @param p 指向组合的下标
* @param num 下标的数量
* @return
* 返回 0:不符合要求,继续搜索
* 返回 1:符合要求,停止搜索
*/
int test(const int* p,int num)
{
for (int i = 0; i < num; i++)
{
printf("%d ",p[i]);
}
printf("\n");
return 0;
}
/**
* @brief 搜索组合状态,并测试
* @param length 组合的长度
* @param num 搜索的个数
* @param testFunc 测试函数的指针
* @return 返回指针,指向符合要求的结果,全部不符合则返回NULL
*/
int *selectAndTest(int length, int num, int (*testFunc)(const int*,int))
{
if (length <= 0 || length < num)
{
return NULL;
}
//初始化
int *select = malloc(num * sizeof(int));
for (int i = 0; i < num; i++)
{
select[i] = i;
}
if ((*testFunc)(select,num))
{
//测试通过
return select;
}
while (select[0] != length - num)
{
//继续寻找下一个组合
for (int i = num - 1; i >= 0; i--)
{
if (select[i] != length - num + i)
{
select[i]++;//select[i]可以右移
for (int j = i + 1; j < num;j++)
{
//select[i]之后的数全都紧挨着select[i]
select[j] = select[j - 1] + 1;
}
break;
}
else
{
continue;//寻找下一个可以右移的select[i]
}
}
//新组合寻找完毕
if ((*testFunc)(select,num))
{
//测试通过
return select;
}
}
//没有组合满足要求
free(select);
return NULL;
}
输出结果:
0 1
0 2
0 3
1 2
1 3
2 3
0 1 2
0 1 3
0 1 4
0 2 3
0 2 4
0 3 4
1 2 3
1 2 4
1 3 4
2 3 4