组合排列。。。初中的知识用到计算机上也这么麻烦。。

来源:互联网 发布:免费下载漫画的软件 编辑:程序博客网 时间:2024/04/29 20:25

编程很多时候用到排列组合。。。

组合可以搜索解决,也可以位运算轻松解决。

比如有len个数字num[]保存,求出所有组合。。。

那么就可以这样写

for (int i = 1; i < (1 << len); ++ i)// get zuhe{int len1 = 0;for (int j = 0; j < len; ++ j){if (i & (1 << j)){zuhe[len1 ++] = num[j];}}}


实际上就是有那个数那么就有n个对应的位,1 1 1 1 1。。。。这些位可以为0或者1两种情况,那么从1 到2^n-1不正是代表了所有种情况吗?然后在求为1位提出来就okle。

 

下面说一下全排列:

首先C++ STL直接调用next_permutation,注意这个函数是有顺序的,从小到大,比如123,如果初始化为321,那么下一个就没了,已经到最后了。如果你想要获得所有的,那么初始化必须是123.。。。

用法就是zuhe[] ; 数组长度len1,初始化好序列。。。

do {输出zuhe。。。。
} while (next_permutation(zuhe, zuhe + len1));//qu pai lie 


 

全排列递归算法:

所谓全排列,就是将集合中元素的所有排列情况依次输出。比如{1、2、3}的全排列为:123、132、213、231、312、321,共6种,满足计算公式N!(N为集合中元素个数,不重复)。

当元素不重复时,全排列采用递归思想较容易实现,它的递归公式推导步骤类似:
1、要求得123的全排列,只需求得:1并上23的全排列(1 23, 1 32),2并上13的全排列(2 13, 2 31),3并上12的全排列(3 12 321)。
2、对于23的全排列,只需求得2并上3的全排列,3并上2的全排列。步骤1中13、12的全排列也类似。
3、对于3的全排列或者2的全排列,就是它们的本身。递归结束。

递归实现不重复元素全排列算法的实现代码(C++)如下:

//交换a和bvoid Swap(int *a, int *b){    int t = *a;    *a = *b;    *b = t;} //全排列函数。list:待排元素列表,start:起始位置下标,end:最后一个有效元素的下一个下标。void Permutation(int start, int end, int list[]){    int i;    if (start >= end) //递归结束,打印当前这次全排列结果,返回。    {        for (i = 0; i < end; i++)        {            printf("%d ", list[i]);        }        printf("\n");        return;    }    //对于给定的list[start...end],要使区间中每一个元素都有放在第一位的机会,    //然后开始递归调用自身,得到list[start+1...end]的全排列。    for (i = start; i < end; i++)    {       Swap(&list[i], &list[start]); //交换元素,使每一个元素都有放在第一位的机会。        Permutation(start+1, end, list); //递归调用        Swap(&list[i], &list[start]); //恢复原始的list,不影响下次递归调用。    }}


 

上述程序的调用方法为:

#include <iostream>using namespace std; int main(){    int a[] = {1, 2, 3};    Permutation(0, 2, a);    return 0;}


 

STL的实现方法:

当待排元素列表含有重复项时,上述算法就需要改进,其中一种方法可以是维护一个存放不重复排列的集合,每次新生成1个排列,如果集合中不存在这个排列,则插入排列,否则,放弃。

要实现含重复元素的全排列算法,可以参考STL中next_premutation()函数的实现方法(在algorithm.h中声明)。该函数会将列表中元素按字典序(wiki)给出全排列中的下一个排列,它的实现算法为:
令当前排列为P(0)P(1)P(2)...P(n-1)P(n)。则求它下一个排列的过程为,
1、从后往前遍历,找到第一个P(i)>P(i-1)的元素,记录下标i。比如排列1、5、2、4、3中满足条件的元素为4,记下它的下标i = 3,因为P(i)是4,P(i-1)是2,满足P(i)>P(i-1)。如果找不到这样的i,则表示该序列已经是字典序中的最后一个序列,结束算法。
2、从后往前遍历,找到第一个P(j)>P(i-1)的数,记录下标k。还是上面这个例子,P(i-1)为2,从后往前第一个大于P(i-1)是P(4)=3,因此记录下j=4。
3、互换P(i-1)和P(j),得到新序列1、5、3、4、2。
4、将P[i...n]间的元素逆置,返回序列。上述例子中为逆置4和2,得到最终的序列1、5、3、2、4。

用比较通俗的例子解释一下上述步骤:
假设现在有一个序列4、6、5、3、2、1,要求得字典序的下一个序列。首先,从后往前找到第一个i,使得P(i)>P(i-1),明显这里i是1,P(i)=6,这个意思是,在6之后的元素,都是按值递减的,否则第一步求i的时候也不会找到第2个元素6才满足条件。现在知道,从i开始到最后,其实是字典序里的最大序列了(一直按值递减)。第二步,拿出i的前一个元素P(i-1)=4,将它与原序列从后往前第一个大于它的元素交换位置,这里这个与4交换的元素是5,这样序列就变成了5、6、4、3、2、1,至此,最高位升了一级(4->5),接着要把低位的从最大变成最小(就像199之后是200,最高为从1变成2后,要把低位从最大99变成最小00),这里的低位是最大序列6、4、3、2、1,变成最小序列只需逆置即可,变成1、2、3、4、6,原序列变为5、1、2、3、4、6,即为所求。

实现代码如下:

/** *如果存在当前序列在字典序中的下一个排列,则返回true, *否则返回false。  */bool next_premutation(int list[], int length){    int i, j;     //步骤1:得到i。    for (i = length - 1; i > 0; i--)    {        if (list[i] > list[i-1])        {            break; //记下下标i。        }    }    if (i <= 0)     {        //表示当前排列已经是字典序中的最后一个序列,没有下一个了。        return false;    }     //步骤2:得到j。    for (j = length - 1; j > 0 ; j--)    {        if (list[j] > list[i-1])        {            break; //记下下标j。        }    }     //步骤3:互换list[i-1]和list[j]。    int temp = list[i-1];    list[i-1] = list[j];    list[j] = temp;     //步骤4:逆置list[i...n]。    int start, end;    for (start = i, end = length-1; start < end; start++, end--)    {        int temp = list[start];        list[start] = list[end];        list[end] = temp;    }     return true;}


采用这种方法要获得一个集合的全排列,可按下面方法调用(和stl函数next_permutation()的调用方法基本一致):

#include <iostream>
using namespace std;

int main()
{
    int list[] = {1, 2, 3, 4, 5};
    do
    {
        for (int i = 0; i < 5; i++)
        {
            printf("%d ", list[i]);
        }
        printf("\n");
    }while (next_premutation(list, 5));
    return 0;
}


 


 

 

原创粉丝点击