排列组合
C语言使用标志位实现
#include <iostream> using namespace std; #define MaxN 10 char used[MaxN]; int p[MaxN]; char s[MaxN]; //从n个元素中选r个进行排列 void permute(int pos,const int n,const int r) { int i; /*如果已是第r个元素了,则可打印r个元素的排列 */ if(pos == r) { for(i=0; i<r; i++) cout<<s[p[i]]; cout<<endl; return; } for (i=0; i<n; i++) { if(!used[i]) { /*如果第i个元素未用过*/ /*使用第i个元素,作上已用标记,目的是使以后该元素不可用*/ used[i] = 1; /*保存当前搜索到的第i个元素*/ p[pos] = i; /*递归搜索*/ permute(pos+1,n,r); /*恢复递归前的值,目的是使以后改元素可用*/ used[i] = 0; } } } //从n个元素中选r个进行组合 void combine(int pos,int h,const int n,const int r) { int i; /*如果已选了r个元素了,则打印它们*/ if (pos == r) { for(i=0; i<r; i++) cout<<s[p[i]]; cout<<endl; return; } for(i=h; i<=n-r+pos; i++) /*对于所有未用的元素*/ { if (!used[i]) { /*把它放置在组合中*/ p[pos] = i; /*使用该元素*/ used[i] = 1; /*搜索第i+1个元素*/ combine(pos+1,i+1,n,r); /*恢复递归前的值*/ used[i] = 0; } } } //产生0~2^r-1的二进制序列 void binary_sequence(int pos,const int r) { int i; if(pos == r) { for(i=0; i<r; i++) cout<<p[i]; cout<<endl; return; } p[pos] = 0; binary_sequence(pos+1,r); p[pos] = 1; binary_sequence(pos+1,r); } //利用上面的二进制序列打印字符串的所有组合 //如"abc"输出a、b、c、ab、ac、bc、abc。 void all_combine(int pos,const int r) { int i; if(pos == r) { for(i=0; i<r; i++) { if(p[i]==1) cout<<s[i]; } cout<<endl; return; } p[pos] = 0; all_combine(pos+1,r); p[pos] = 1; all_combine(pos+1,r); } //利用r进制序列打印字符串的所有重复组合 //如"abc"输出aaa、aab、aac、aba、abb、abc、aca、acb、acc...。 void repeative_combine(int pos,const int r) { int i; if(pos == r) { for(i=0; i<r; i++) { cout<<s[p[i]]; } cout<<endl; return; } for(i=0; i<r; ++i) { p[pos] = i; repeative_combine(pos+1,r); } } int main() { strcpy(s,"ABC"); int n = 3; int r = 3; //permute(0,n,r); //combine(0,0,n,r); //binary_sequence(0,r); //cout<<"string: "<<s<<endl; //all_combine(0,r); //repeative_combine(0,r); return 0; }
排列组合算法的递归实现
#include <iostream> using namespace std; template <class Type> void permute(Type a[], int start, int end) { if(start == end) { for(int i = 0; i <= end; ++i) { cout<<a[i]<<" "; } cout<<endl; } else { for(int i = start; i <= end; ++i) { swap(a[i],a[start]); permute(a,start+1,end); swap(a[i],a[start]); } } } template <class Type> void combine(Type a[], bool b[], int start, int end) { if(start > end) { for(int i = 0; i <= end; ++i) { if(b[i]) cout<<a[i]<<" "; } cout<<endl; } else { b[start] = true; combine(a,b,start+1,end); b[start] = false; combine(a,b,start+1,end); } } int main() { int p[3]={1,2,3}; int N = 3; cout<<"permute:"<<endl; permute(p,0,N-1); cout<<"combine:"<<endl; bool b[3]; combine(p,b,0,N-1); return 0; }
排列算法的迭代实现
C++ STL中提供了next_permutation和prev_permutation算法。因为next_permutation和prev_permutation实际上是一样的,因此只描述next_permutation算 法。next_permutation()函数的作用是取下一个排列组合。考虑{a,b,c}的全排 列:abc,acb,bac,bca,cab,cba,以“bac”作为参考,那么next_permutation()所得到的下一个排列组合是 bca,prev_permutation()所得到的前一个排列组合是“acb”,之于“前一个”和“后一个”,是按字典进行排序的。
next_permutation()算法描述:
- 从str的尾端开始逆着寻找相邻的元素,*i和*ii,满足*i<*ii;
- 接着,又从str的尾端开始逆着寻找一元素,*j,满足*i>*j(*i从步骤一中得到);
- swap(*i,*j);
- 将*ii之后(包括*ii)的所有元素逆转。
举个例子,需要找到“01324”的下一个排列,找到*i=2,*ii=4,*j=4,下一个排列即“01342”。再来找到“abfedc”的下一个排列,找到*i=b,*ii=f,*j=c,swap操作过后为“acfedb”,逆转操作过后为“acbdef”
//求阶乘 int factorial(int n) { if(n == 1) return 1; return n*factorial(n-1); } template <class Type> void print(Type a, int n) { for(int i = 0; i < n; ++i) cout<<a[i]<<" "; cout<<endl; } template <class Type> void perm2(Type a, int n) { int i,ii,j; int cnt = 1; print(a,n); int num = factorial(n); // STL <algorithm> next_permutation()函数的核心算法 while(++cnt <= num) { i = n - 2; ii = n - 1; j = ii; while(a[i] >= a[ii]) --i,--ii; //find *i and *ii while(a[i] >= a[j]) --j; //find *j swap(a[i],a[j]); //STL swap reverse(a+ii,a+n); //STL reverse print(a,n); } }
去掉重复的全排列的递归实现
由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如
122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到
221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。
换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:
#include<iostream> using namespace std; #include<assert.h> //在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等 bool IsSwap(char* pBegin , char* pEnd) { char *p; for(p = pBegin ; p < pEnd ; p++) { if(*p == *pEnd) return false; } return true; } void Permutation(char* pStr , char *pBegin) { assert(pStr); if(*pBegin == '\0') { static int num = 1; //局部静态变量,用来统计全排列的个数 printf("第%d个排列\t%s\n",num++,pStr); } else { for(char *pCh = pBegin; *pCh != '\0'; pCh++) //第pBegin个数分别与它后面的数字交换就能得到新的排列 { if(IsSwap(pBegin , pCh)) { swap(*pBegin , *pCh); Permutation(pStr , pBegin + 1); swap(*pBegin , *pCh); } } } } int main(void) { char str[] = "baa"; Permutation(str , str); return 0; }
组合
。C(n,m)==A(n,m)/m! ;C(n,m)=C(n,n-m) 。 (n>=m)
排列组合算法
排列算法:
排列算法采用递归思想很简单。如1,2,3,4我们求其排列。
首先遍历1,2,3,4.
遍历1,剩下2,3,4递归处理
遍历2,剩下1,3,4递归处理
遍历3,剩下1,2,4递归处理
遍历4,剩下1,2,3递归处理
我们的目标是把相应的排列给答应出来,所以这个时候需要一个数组来保存已经处理的数字。但是关于数组的下标在递归的时候是个问题,我们改采取怎样的 方式呢?可以发现这里有一个技巧,用原始数组的长度减去当前待处理的数组的长度就得到元素的数组索引。如刚开时候是1,2,3,4。数组长度等于原始数组 长度,所以1的索引是0。处理1之后,剩下2,3,4,此时数组长度是3,所以我们把2的数组索引定义为4-3=1。以此类推,直到数组的长度为1。
组合算法:
组合的思想和排列一样,只不过组合之间的元素不需要前后前后顺序要求,所以我们需要排除在排列中重复的元素。关于去除的思想,我们在这里可以采取一种简单的方法,我们将组合的元素按顺序排列,保证后面的元素大于前面的元素,这样就可以实现无重复了。
如,我们这样处理:
1,2;
1,3;
1,4;
2,3;
2,4;
3,4;
这样按照从小到大的顺序从而实现了无重复的排列。
关于组合,稍微麻烦的是在处理数组的元素下标,想了好久想到到了一个比较拙劣的办法,实属本人不才。我在函数的参数列表中加了一个参数k,来表示数 组元素下标。当采用递归的时候,说明要产生下一个数组元素,所以k++,递归调用完毕后,我们要依次遍历后面的元素,所以要k--。
实现代码:
一个三个函数按照顺序分别表示的是:,,的算法。
#include #include #include using namespace std; void arrangeN(list &Lis,vector &Vec); void arrangeK(list &Lis,vector &Vec,int nK,int nN); void combineK(list &Lis,vector &Vec,int nK,int nN,int k); void main() { int n1 = 10; list Arra; vector Vec(n1); //for(int i=1;i<=n;i++) // Arra.push_back(i); //arrangeN(Arra,Vec); int n2 = 5; int nk = 4; for(int i=1;i<=n2;i++) Arra.push_back(i); //arrangeK(Arra,Vec,n2,n2); combineK(Arra,Vec,nk,n2,0); } void arrangeN(list &Lis,vector &Vec) { list::iterator iter; int nSize = Lis.size(); int nLength = Vec.size(); for(iter = Lis.begin();iter != Lis.end();iter++) { Vec[nLength-nSize] = *iter; if(nSize == 1) { for(int i=0;i<<vec[i]<<"="" ";="" cout< Lisc(Lis); Lisc.remove(*iter); arrangeN(Lisc,Vec); } } void arrangeK(list &Lis,vector &Vec,int nK,int nN) { list::iterator iter; int nSize = Lis.size(); for(iter = Lis.begin();iter != Lis.end();iter++) { Vec[nN-nSize] = *iter; if(nN-nSize +1 == nK) { for(int i=0;i<<vec[i]<<"="" ";="" cout< Lisc(Lis); Lisc.remove(*iter); arrangeK(Lisc,Vec,nK,nN); } } void combineK(list &Lis,vector &Vec,int nK,int nN,int k) { int nSize = Lis.size(); list::iterator iter; for(iter = Lis.begin();iter != Lis.end();iter++) { if(nN - nSize == 0) k = 0; Vec[k] = *iter; if( k + 1 == nK) { for(int j=0;j<<vec[j]<<"="" ";="" cout< Lisc( ++iter,Lis.end() ); iter--; k++; combineK(Lisc,Vec,nK,nN,k); k--; } } }
测试结果:
4!
A(5,2)
C(5,3)