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

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

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

比如有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和b
void 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;
}


 


 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值