一种获取所有组合状态的非递归算法

组合定义

组合的定义:从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 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值