算法之排列和组合算法
2013-01-02 21:19
204 查看
http://dongxicheng.org/structure/permutation-combination/
1. 前言
本文介绍了常用的排列组合算法,包括全排列算法,全组合算法,m个数选n个组合算法等。
2. 排列算法
常见的排列算法有:
(A)字典序法
(B)递增进位制数法
(C)递减进位制数法
(D)邻位对换法
(E)递归法
介绍常用的两种:
(1) 字典序法
对给定的字符集中的字符规定了一个先后关系,在此基础上按照顺序依次产生每个排列。
[例]字符集{1,2,3},较小的数字较先,这样按字典序生成的全排列是:123,132,213,231,312,321。
生成给定全排列的下一个排列 所谓一个的下一个就是这一个与下一个之间没有字典顺序中相邻的字符串。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
算法思想:
设P是[1,n]的一个全排列。
P=P1P2…Pn=P1P2…Pj-1PjPj+1…Pk-1PkPk+1…Pn , j=max{i|Pi<Pi+1}, k=max{i|Pi>Pj} ,对换Pj,Pk,将Pj+1…Pk-1PjPk+1…Pn翻转, P’= P1P2…Pj-1PkPn…Pk+1PjPk-1…Pj+1即P的下一个
例子:839647521的下一个排列.
从最右开始,找到第一个比右边小的数字4(因为4<7,而7>5>2>1),再从最右开始,找到4右边比4大的数字5(因为4>2>1而4<5),交换4、5,此时5右边为7421,倒置为1247,即得下一个排列:839651247.用此方法写出全排列的非递归算法如下
该方法支持数据重复,且在C++ STL中被采用。
以下是STL的方法
(2) 递归法
设一组数p = {r1, r2, r3, … ,rn}, 全排列为perm(p),pn = p – {rn}。则perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), … , rnperm(pn)。当n = 1时perm(p} = r1。
如:求{1, 2, 3, 4, 5}的全排列
1、首先看最后两个数4, 5。 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列。
由于一个数的全排列就是其本身,从而得到以上结果。
2、再看后三个数3, 4, 5。它们的全排列为3 4 5、3 5 4、 4 3 5、 4 5 3、 5 3 4、 5 4 3 六组数。
即以3开头的和4,5的全排列的组合、以4开头的和3,5的全排列的组合和以5开头的和3,4的全排列的组合.
3. 组合算法
3.1 全组合
在此介绍二进制转化法,即,将每个组合与一个二进制数对应起来,枚举二进制的同时,枚举每个组合。如字符串:abcde
00000 <– –> null
00001<– –> e
00010 <– –> d
… …
11111 <– –> abcde
3.2 从n中选m个数
(1) 递归
a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
b. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
下面是递归方法的实现:
(2) 01转换法
本程序的思路是开一个数组,其下标表示1到n个数,数组元素的值为1表示其代表的数被选中,为0则没选中。
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。
然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为“01”组合,同时将其左边的所有“1”全部移动到数组的最左端。
当第一个“1”移动到数组的n-m的位置,即n个“1”全部移动到最右端时,就得到了最后一个组合。
例如求5中选3的组合:
4. 参考资料
(1) http://www.cnblogs.com/nokiaguy/archive/2008/05/11/1191914.html
(2) http://comic.sjtu.edu.cn/thucs/GD_jsj_025y/text/chapter01/sec05/default.htm
(3) http://blog.csdn.net/sharpdew/archive/2006/05/25/755074.aspx
(4) http://xiaomage.blog.51cto.com/293990/74094
组合:
1. 前言
本文介绍了常用的排列组合算法,包括全排列算法,全组合算法,m个数选n个组合算法等。
2. 排列算法
常见的排列算法有:
(A)字典序法
(B)递增进位制数法
(C)递减进位制数法
(D)邻位对换法
(E)递归法
介绍常用的两种:
(1) 字典序法
对给定的字符集中的字符规定了一个先后关系,在此基础上按照顺序依次产生每个排列。
[例]字符集{1,2,3},较小的数字较先,这样按字典序生成的全排列是:123,132,213,231,312,321。
生成给定全排列的下一个排列 所谓一个的下一个就是这一个与下一个之间没有字典顺序中相邻的字符串。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
算法思想:
设P是[1,n]的一个全排列。
P=P1P2…Pn=P1P2…Pj-1PjPj+1…Pk-1PkPk+1…Pn , j=max{i|Pi<Pi+1}, k=max{i|Pi>Pj} ,对换Pj,Pk,将Pj+1…Pk-1PjPk+1…Pn翻转, P’= P1P2…Pj-1PkPn…Pk+1PjPk-1…Pj+1即P的下一个
例子:839647521的下一个排列.
从最右开始,找到第一个比右边小的数字4(因为4<7,而7>5>2>1),再从最右开始,找到4右边比4大的数字5(因为4>2>1而4<5),交换4、5,此时5右边为7421,倒置为1247,即得下一个排列:839651247.用此方法写出全排列的非递归算法如下
该方法支持数据重复,且在C++ STL中被采用。
以下是STL的方法
bool perm_1(int *first,int *last) { if(first==last) return false;//空范围 int* i=first; ++i; if(i==last) return false;//只有一个元素 i=last;//i指向尾端 --i; for(;;) { int* ii=i; --i; if(*i<*ii)//如果前一个元素小于后一个元素 { int* j=last; while(!(*i<*--j));//由尾端往前找,直到遇到比*i最大的元素 iter_swap(i,j);//交换i和j reverse(ii,last);//将ii之后的元素全部逆向重拍 return true; } if(i==first) {//进行至最前面 reverse(first,last); return false; } } } void test_11() { int ia[]={1,2,3}; for(int i=0;i<sizeof(ia)/sizeof(int);i++) cout<<ia[i]<<" "; cout<<endl; while(perm_1(ia,ia+sizeof(ia)/sizeof(int))) { for(int i=0;i<sizeof(ia)/sizeof(int);i++) cout<<ia[i]<<" "; cout<<endl; } } int main() { test_11(); return 0; }
(2) 递归法
设一组数p = {r1, r2, r3, … ,rn}, 全排列为perm(p),pn = p – {rn}。则perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), … , rnperm(pn)。当n = 1时perm(p} = r1。
如:求{1, 2, 3, 4, 5}的全排列
1、首先看最后两个数4, 5。 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列。
由于一个数的全排列就是其本身,从而得到以上结果。
2、再看后三个数3, 4, 5。它们的全排列为3 4 5、3 5 4、 4 3 5、 4 5 3、 5 3 4、 5 4 3 六组数。
即以3开头的和4,5的全排列的组合、以4开头的和3,5的全排列的组合和以5开头的和3,4的全排列的组合.
#include <stdio.h> int n = 0; void swap(int *a, int *b) { int m; m = *a; *a = *b; *b = m; } void perm(int list[], int k, int m) { int i; if(k > m) { for(i = 0; i <= m; i++) printf("%d ", list[i]); printf("\n"); n++; } else { for(i = k; i <= m; i++) { swap(&list[k], &list[i]); perm(list, k + 1, m); swap(&list[k], &list[i]); } } } int main() { int list[] = {1, 2, 3, 4, 5}; perm(list, 0, 4); printf("total:%d\n", n); return 0; }
3. 组合算法
3.1 全组合
在此介绍二进制转化法,即,将每个组合与一个二进制数对应起来,枚举二进制的同时,枚举每个组合。如字符串:abcde
00000 <– –> null
00001<– –> e
00010 <– –> d
… …
11111 <– –> abcde
3.2 从n中选m个数
(1) 递归
a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
b. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
下面是递归方法的实现:
/// 求从数组a[1..n]中任选m个元素的所有组合。 /// a[1..n]表示候选集,n为候选集大小,n>=m>0。 /// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标), /// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。 void combine( int a[], int n, int m, int b[], const int M ) { for(int i=n; i>=m; i--) // 注意这里的循环范围 { b[m-1] = i - 1; if (m > 1) combine(a,i-1,m-1,b,M); else // m == 1, 输出一个组合 { for(int j=M-1; j>=0; j--) cout << a[b[j]] << " "; cout << endl; } } }
(2) 01转换法
本程序的思路是开一个数组,其下标表示1到n个数,数组元素的值为1表示其代表的数被选中,为0则没选中。
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。
然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为“01”组合,同时将其左边的所有“1”全部移动到数组的最左端。
当第一个“1”移动到数组的n-m的位置,即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
4. 参考资料
(1) http://www.cnblogs.com/nokiaguy/archive/2008/05/11/1191914.html
(2) http://comic.sjtu.edu.cn/thucs/GD_jsj_025y/text/chapter01/sec05/default.htm
(3) http://blog.csdn.net/sharpdew/archive/2006/05/25/755074.aspx
(4) http://xiaomage.blog.51cto.com/293990/74094
组合:
void com(int list[],int idx,int len,std::vector<int> col) { if(col.size()==3) { vector<int> t=col; vector<int>::iterator iter=t.begin(); for(;iter!=t.end();iter++) { cout<<*iter<<" "; } cout<<endl; } else { for(int i=idx;i<len;i++) { col.push_back(list[i]); com(list,i+1,len,col); col.pop_back(); } } } int main() { std::vector<int> col; int list[]={1,2,3,4}; com(list,0,4,col); return 0; }
相关文章推荐
- 排列组合数C(m,n)的O(n)算法
- 字符串的全排列和组合算法
- 字符串全排列和组合算法
- 高效率的排列组合算法
- 算法基础:排列组合问题-全排列(Golang实现)
- [经典算法] 排列组合-N元素集合的所有子集(二)
- 字符串的全排列和组合算法
- C#实现排列组合算法
- 算法研究之排列组合公式
- 排列组合算法
- STL算法 -------- 排列组合
- 算法之排列算法与组合算法详解
- 排列组合算法之二: 01转换法_java改变后的c++改进版
- 算法_3 : 组合数学:排列组合
- 类似stl中排列算法的组合算法
- java 高效率的排列组合算法(java实现)
- 算法练习:排列组合之全排列
- 排列组合算法
- 字符串的全排列和组合算法
- 排列组合算法