• 排列组合的一些算法


    排列组合有多种实现方法,下面介绍整理的一些方法。

    一、最简单直接的就是递归

           原理比较直接:计算一个集合的组合,首先选择一个元算,然后在剩下的集合中选择剩下的元素。看下面的源代码:

    /**************************

     * 计算一个集合的组合

     *************************/

    #include<stdlib.h>

    #include<assert.h>

    /*************************

     * 递归: 首先选择一个元素,然后在剩下的集合中选择其余元素

     ************************/

    typedef struct LiStack

    {

              char element;

              struct LiStack* prev;

              struct LiStack* next;

    }LiStack;

    typedef struct SqStack

    {

             char *elements;

             int top;       /*栈指针*/

    }SqStack;

    //采用链式存储的栈, 双向链表:由栈顶指向栈底

    void CalCombinationLi(const char Elements[], int SetLg, int k, LiStack *StackHead, LiStack *StackTail)

    {//Elements:集合, SetLg:集合长度, k:要选取的元素个数, stackHead:指向栈顶, StackTail:指向栈底

                       LiStack* StackTmp;

                       LiStack* StackN;

                       int i;

                       assert(k<=SetLg);//如果要选取的元素个数超过集合长度,则出错

                      

                       if(k==0)

                       {//输出该次选取的元素组合

                                StackTmp = StackTail;

                                while(StackTmp)

                                {

                                                   printf("%c ",StackTmp->element);

                                                   StackTmp = StackTmp->prev;

                                }

                                printf(""n");

                                return;

                       }

                       //从该集合中顺序选取一个元素[i], 因为共选取k个元素, 所以最后一个可选择的元素为[SetLg-k]

                       //然后从剩下的集合[i+1:end]中选取其余的k-1个元素

                       //这样就构成了从集合(长度为SetLg)中选取k个元素, 按字典序的组合

                       for(i=0; i<=SetLg-k; ++i)

                       {

                                //将元素[i]压栈

                                StackN = (LiStack*)malloc(sizeof(LiStack));

                                StackN->element = Elements[i];

                                StackN->next = NULL;

                                StackN->prev = NULL;

                               

                                if(StackHead)

                                {

                                         StackHead->prev = StackN;

                                         StackN->next    = StackHead;         

                                }

                                else

                                {

                                         StackTail = StackN;  

                                }

                                StackHead = StackN;

                               

                                CalCombinationLi(Elements+i+1, SetLg-i-1, k-1, StackHead, StackTail);//从剩下的集合中选取k-1个元素

                               

                                //将元素[i]弹出栈

                                StackTmp = StackHead;

                                StackHead = StackHead->next;

                                free(StackTmp);

                                if(StackHead)

                                {

                                         StackHead->prev = NULL;        

                                }

                                else

                                {

                                         StackHead = NULL;

                                         StackTail = NULL;    

                                }

                       }

    }

    //采用顺序存储的栈

    void CalCombinationSq(const char Elements[], int SetLg, int k, SqStack *stack)

    {//Elements:集合, SetLg:集合长度, k:要选取的元素个数, stack:

             assert(k<=SetLg);

            

             int i;

             if(k==0)

             {//输出此次选取的元素组合

                       for(i=0; i<=stack->top; i++)//从栈底到栈顶

                       {

                                printf("%c ",stack->elements[i]);

                       }

                       printf(""n");

                       return;

             }

             for(i=0; i<=SetLg-k; i++)

             {

                      //将元素[i]压栈

                       stack->top++;

                       stack->elements[stack->top]=Elements[i];

                      

                       CalCombinationSq(Elements+i+1, SetLg-i-1, k-1, stack);

                      

                       //将元素[i]弹出栈

                       stack->top--;

             }

    }

    //测试

    int main()

    {

             char elements[] = {'a', 'b', 'c', 'd'};

             const int NUM = sizeof(elements) / sizeof(elements[0]);

             LiStack *StackHead=NULL, *StackTail=NULL;

             int i;

             SqStack *stack=(SqStack *)malloc(sizeof(SqStack));

             stack->elements = (char *)malloc(sizeof(elements));

             for(i=1; i<=NUM; i++)

             {

                       //CalCombinationLi(elements, NUM, i, StackHead, StackTail); 

                       CalCombinationSq(elements, NUM, i, stack);

             }

    }

    排列的源程序和上面的类似,其实上面的组合输出具有顺序性,和排列的输出没有多大的区别。

    二、使用位运算(算法和程序来自网络)

           对于元素个数为n的集合,可以使用n为来表示每一个元素,为1表示该元素被选中,为0表示该元素未被选中。那么,计算组合C(n, k)就相当于计算出n位数中有k1位的所有数,每一个计算出的数就表示一个选中的组合。源代码如下:

    /*******************************

     * 计算一个集合的组合

     ******************************/

    /******************************

     * 使用位运算来实现组合计算:对于元素个数为n的集合,可以使用n位来表示每一个元素,为1表示该元素被选中,为0表示该元素

     * 未被选中。那么,计算组合C(n, k)就相当于计算出n位数中有k1位的所有数,每一个计算出的数就表示一个选中的组合

     *****************************/

    #include<iostream>

    #include<cassert>

    using namespace std;

    typedef unsigned int unint32_t;

     

    //输出数表示的组合

    template <typename T>

    void OutputCombination(const T elements[], int num, uint32_t combinationBits)

    {

             for(int i=0; i<num; i++)

             {

                       if(combinationBits & unint32_t(1)<<i)

                                cout << elements[i];

             }

             cout << endl;    

    }

    //产生跟pre有相同的1位,且比pre大的下一个数,如果存在,返回

    uint32_t NextCombination(uint32_t pre)

    {

             uint32_t lastOne = pre & -pre;

             uint32_t high = pre+lastOne;

            

             if(high == 0)

                       return 0;//已经达到最大值

             uint32_t mid = high^pre;

             uint32_t low = (mid>>2)/lastOne;

             return high | low;

    }

    template <typename T>

    void GenAllCombination(const T elements[], int num, int k)

    {

             assert(1<=num && num<=32 && 1<=k && k<=num);

            

             //产生最小的具有k1的数

             uint32_t number = (uint32_t(1)<<k) - 1;

             //具有k1的最大的num位数

             uint32_t maxNumber = number << (num-k);

            

             for(; true; number=NextCombination(number))

             {

                       OutputCombination(elements, num, number);

                       if(number==maxNumber)

                                break;

             }

    }

    //测试

    int main()

    {

             const int NUM = 5;

             char elements[NUM];

            

             for(int i=0; i<NUM; i++)

                       elements[i] = 'a'+i;

             GenAllCombination(elements, NUM, 2);

    }

    三、(算法来自网络)

           组合算法:开一个数组,其下标表示1m个数,数组元素的值为1表示其下标代表的数被选中,为0则没有选中。

           首先初始化,将数组前n个元素置1,表示第一个组合为前n个数;然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为“01”组合,同时将其左边的所有“1”全部移动到数组的最左端;当第一个“1”移动到数组的m-n位置,即n个“1”全部移动到最右端时,就得到了最后一个组合。

           例如求5中选3的组合:

           1     1     1     0     0     //1, 2, 3

           1     1     0     1     0     //1, 2, 4

           1     0     1     1     0     //1, 3, 4

           0     1     1     1     0     //2, 3, 4

           1     1     0     0     1     //1, 2, 5

           1     0     1     0     1     //1, 3, 5

           0     1     1     0     1     //2, 3, 5

           1     0     0     1     1     //1, 4, 5

           0     1     0     1     1     //2, 4, 5

           0     0     1     1     1     //3, 4, 5

    源程序如下:

           /***********************

     * 计算一个集合的组合

     **********************/

    #include<stdlib.h>

    /*输出某个组合*/

    void OutputCom(const char elements[], int *flags, int length)

    {

             int i;

             for(i=0; i<length; i++)

             {

                       if(flags[i]==1)

                       {

                                printf("%c ",elements[i]);

                       }

             }

             printf(""n");

    }

    /*计算组合*/

    int GenCom(const char elements[], int setLg, int k)

    //elements:集合元素; setLg:集合长度; k:从集合中要选取的元素个数

    {

             if(k>setLg || k<=0)

                       return -1;

     

             int i,j;                   //循环变量

             int has10; //是否有"10"组合的标志:1-;0-

             int bound; //第一个"10"组合的索引

             int num1;           //"10"组合左边的"1"的个数

             int *flags = (int *)malloc(setLg*sizeof(int));//与元素集合对应的标志:1-被选中;0-未被选中

             //初始化,将标志的前k个元素置1,表示第一个组合为前k个数

             for(i=0; i<k; i++)

                       flags[i]=1;

             for(i=k; i<setLg; i++)

                       flags[i]=0;

     

             OutputCom(elements, flags, setLg);//输出初始化的组合

             /*

                       从左到右扫描标志的"10"组合,找到第一个"10"组合后将其变为"01"组合,同时将其左边的所有"1"全部移动到数组的最左端

             */

             while(1)

             {

                       num1 = 0;

                       has10= 0;

                       for(i=0; i<setLg-1; i++)

                       {

                                if(flags[i]==1 && flags[i+1]==0)//找到第一个"10"组合

                                {

                                         bound = i;

     

                                         flags[i]=0;//将该"10"组合变为"01"组合

                                         flags[i+1]=1;

     

                                         for(j=0; j<num1; j++)//将其左边的所有"1"全部移动到数组的最左端

                                         {

                                                   flags[j]=1;

                                         }

                                         for(j=num1; j<bound; j++)

                                         {

                                                   flags[j]=0;

                                         }

     

                                         has10 = 1;

                                         break;

                                }

                                else if(flags[i]==1)

                                {

                                         num1++;

                                }

                       }

                       if(has10==0)//没有"10"组合了,代表组合计算完毕

                                break;

                       OutputCom(elements, flags, setLg);

             }

            

             return 1;

    }

    /*测试*/

    int main()

    {

             const char elements[5]="abcde";

             int setLg=5;

             GenCom(elements, setLg, 3);

             return 0;

    }

     

           还有关于排列组合的其他算法,在此不述。在这些算法中,很容易理解的是递归,但其效率也很不理想。

  • 相关阅读:
    Cmd Markdown 公式指导手册
    ubuntu 21.10 上的一些必备库的安装 opengl, opencv
    MarkDown 数学公式
    免费代理IP地址列表
    pyexecl的使用
    磁盘IO满负荷性能分析
    DRM 简介
    Oracle修改字符集ORA02374,ORA12899,ORA02372
    oracle grid修改ip
    Oracle性能问题一般排查方法
  • 原文地址:https://www.cnblogs.com/luluping/p/1445860.html
Copyright © 2020-2023  润新知