一种获取所有排列状态的非递归算法

什么是排列

排列(permutation),数学的重要概念之一。有限集的子集按某种条件的序化法排成列、排成一圈、不许重复或许重复等。从n个不同元素中每次取出m(1≤m≤n)个不同元素,排成一列,称为从n个元素中取出m个元素的无重复排列或直线排列,简称排列。
从n个不同元素中取出m个不同元素的所有不同排列的个数称为排列种数或称排列数。用符号 A(n,m) 表示。

应用场景

排列比组合有更广阔的应用场景,因为与组合相比,排列还考虑了顺序问题。搜索所有排列同样也是暴力解决多种算法问题的思路,比如典型的各种旅行商问题。

算法思路

从length个元素中排列num个元素,可以创建一个长度为num的数组sequence,数组内存放的是0到length-1之间的不重复数字。

每一次生成排列,数组sequence内的规则如下:

  • sequence数组从左到右依次确定各个元素
    即先确定sequence[0],在确定sequence[1],最后确定sequence[num-1]
  • 每一次生成的排列都要确保不与生成过的重复
    例如sequence曾经为{0,1,2,3,4},那么{0,1,2,3,4}将不再出现
  • 每个元素都尽量的选取小的数
    当length为8,num为5。sequence前四个元素分别为0,2,3,4。且{0,2,3,4,1}出现过。那么sequence[4]可以选取的数字为5,6,7,选择最小的5。
  • 当sequence内的某个元素已经没有可选数字,则它的上一个元素重新选择。
    当length为8,num为5,sequence 为 {0,2,3,4,7},此时sequence[4]已经没有选择空间,则sequence[3]重新进行选择。

由此可见,在这种规则下,可以推断:

  • 当sequence[0]再也没有选择空间时,所有的排列已经搜索完毕。

源码分享

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int test(const int *p, int num);
int *sequenceAndTest(int length, int num, int (*testFunc)(const int *, int));

int main(int argc, char const *argv[])
{
    sequenceAndTest(4, 2, test);
    system("pause");
    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("\r\n");
    return 0;
}
/**
 * @brief 搜索排列状态,并测试
 * @param length 排列的长度
 * @param num 选择排列的个数
 * @param testFunc 测试函数的指针
 * @return 返回指针,指向符合要求的结果,全部不符合则返回NULL
 */
int *sequenceAndTest(int length, int num, int (*testFunc)(const int *, int))
{
    if (length <= 0 || length < num)
    {
        return NULL;
    }
    //初始化
    int *sequence = malloc(num * sizeof(int));
    int appoint = 0;      //当前需要确定的数的下标
    int minOpt[num];      //记录sequence[]中每一位可选择的最小值
    memset(minOpt, 0, sizeof(minOpt));
    while (1)
    {
        //sequenxe[appoint]选择
        int i = 0;
        for (i = 0; i < length; i++)
        {
            //先排除已经被前面选的情况
            int j = 0;
            for (j = 0; j < appoint; j++)
            {
                if (sequence[j] == i)
                {
                    
                    break;
                }
            }
            if (j < appoint)
            {
                //i已经被之前选中,考察下一个i
                continue;
            }

            if (i >= minOpt[appoint])
            {
                //该数字i可选
                sequence[appoint] = i;   //确定sequence[appoint]的选择
                minOpt[appoint] = i + 1; //下次只能从更大的选
                appoint++;
                break;
            }
        }

        if (i == length)
        {
            //sequence[appoint]没有选到数字
            if (appoint == 0)
            {
                //第0个数字都没能选到,再也没有新的排列了
                break;
            }
            minOpt[appoint] = 0; //sequence[appoint]可以重新选
            appoint--;//上一个数字sequence[appoint-1]重新选择
        }

        if (appoint == num)
        {
            //新排列寻找完毕
            if ((*testFunc)(sequence, num))
            {
                //测试通过
                return sequence;
            }
            appoint--;//最后一位数字重新选择
        }
    }
    //没有排列满足要求
    free(sequence);
    return NULL;
}

输出结果:

0 1
0 2
0 3
1 0
1 2
1 3
2 1
2 3
3 0
3 1
3 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值