全排列
来自http://dongxicheng.org/structure/permutation-combination/
参考http://blog.csdn.net/whoismickey/article/details/3907334
http://www.cnblogs.com/wonderKK/archive/2012/04/05/2433694.html
http://www.cnblogs.com/nokiaguy/archive/2008/05/11/1191914.html
1全排列算法
(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),839647521
再从最右开始,找到4右边比4大的数字5(因为4>2>1而4<5), (839647521) 交换4、5, (839657421)
此时5右边为7421,倒置为1247,即得下一个排列:839651247.用此方法写出全排列的非递归算法如下
该方法支持数据重复,且在C++ STL中被采用。
#include <iostream> using namespace std; static int count = 1; template <typename T>void printarray(T prem[],int num) { for (int i = 0; i < num;i++) { cout<<prem[i]<<"*"; } cout<<endl; } template <typename T> void CalcAllPermutation(T perm[],int num) { if (num < 1) { return; } while (true) { int i; for (i = num - 2; i >=0; --i)//从右到左寻找第一个比右边小的数字perm[i] { if (perm[i] < perm[i+1])break; } if(i < 0)break;//已找到所有的排列 int k; for(k = num - 1; k > i;--k) { if(perm[k] > perm[i])break;//从右到左找到比第一个比perm[i]的数字大的数 } swap(perm[i],perm[k]);//交换这两个数 reverse(perm+i+1,perm+num);//转置后边的数 count++; cout<<count<<":"; printarray(perm,num); } } int main() { char a[4] = {'a','b','c','d'}; printarray(a,4); CalcAllPermutation(a,4); system("pause"); 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]);//依次将1,2,3...n个元素交换到第一个位置 perm(list, k + 1, m);//递归进行全排列 swap(&list[k], &list[i]);//由于将第i个元素换到了第一个位置,这里进行依次还原,以便继续进行递归 } } } int main() { int list[] = {1, 2, 3, 4, 5}; perm(list, 0, 4); printf("total:%d\n", n); return 0; }
2:组合算法
在此介绍二进制转化法,即,将每个组合与一个二进制数对应起来,枚举二进制的同时,枚举每个组合。如字符串:abcde
00000 <– –> null
00001<– –> e
00010 <– –> d
… …
11111 <– –> abcde
n个数的组合为2^n -1 个
#include <iostream> #include <bitset> int main() { char a[5] = {'a','b','c'}; for (int i = 0; i<(1<<3);i++) { bitset<3> bits(i); cout<<i<<":"; for (size_t m=0; m < bits.size();++m) { if (bits.test(m)) { cout<<a[m]; } } cout<<endl; } system("pause"); return 0; }
从n中选m个的算法
#include <iostream> #include <bitset> int main() { char a[5] = {'a','b','c','d','e'}; for (int i = 0; i<(1<<5);i++) { bitset<5> bits(i); if(bits.count() == 3) { for (size_t m=0; m < bits.size();++m) { if (bits.test(m)) { cout<<a[m]; } } cout<<endl; } } system("pause"); return 0; }