浅谈全排列与组合的生成
2013-03-23 23:44
447 查看
例:输出从1,2......m,中任取k个数的所有组合。m=5,k=3时有543,542,541,532,531,521,432,431,421,321有C(m,k)个。
法一:枚举方法
n = 5时将输出123,124,125,134,135,145,234,235,245,345。
法二:递归实现
设函数comb(int m,int k)是找出1,2,.....m中任取k个数的所有组合,当组合的第一个数字选定后,其后的数字的就是从余下的m-1个数选k-1个数的组合,问题转化为求comb(m-1, k-1).需要一个全局数组存放结果,将确定的k个数字组合的第一个数存储在a[k]中,求出一个组合后输出a数组中的一个组合。每个组合的第一个数字可以是m,m-1,.....k.函数确定组合的第一个数字后,如果还为确定组合的其余元素,则继续递归,否则输出。
输入m=5,k=3,输出543,542,541,532,531,521,432,431,421,321。
全排列的生成:
最简单的方法就是使用STL<algorithm>中的next_permutation,其返回bool值,不过经常用就啥都忘了。
递归生成1-n的全排列(无重复元素):如abc的全排列有abc,acb,bac,bca,cba,cab,一共3!个。
设对R={r1,r2,......rn}全排列,设Ri = R - {ri},perm(X)表示集合X的全排序,(ri)perm(X)表示X全排序前加前缀ri
全排列的递归定义:设n是集合中元素个数
r n = 1
perm(R)= (r1)perm(R1)
......
(rn)perm(Rn)
如果给定集合{a,b,c,d},所有可能的排列由下列排列组成
1.以a开头的后面跟着(b,c,d)的所有排列
2.以b开头的后面跟着(a,c,d)的所有排列
3.以c开头的后面跟着(a,b,d)的所有排列
4.以d开头的后面跟着(a,b,c)的所有排列
递归线索是“后面跟着。。。的所有排列”,若能够解决n-1个元素集合的排列,就可以解决n个元素集合的排列问题。
处理时将所有数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
执行后输出abc,acb,bac,bca,cba,cab,按照字典序输出。
根据这种思路还有一种方法确定递归函数:(参考刘汝佳《算法竞赛入门经典》)
1已经确定的前缀序列,以便输出
2需要进行全排列的元素集合,以便依次选作第一个元素
伪代码:
void perm(序列A, 集合S)
{
if(S为空)
输出序列A;
else 按照从小到大的顺序依次考虑S的每个元素v
perm(在A的末尾添加v后得到的新序列,S-{v});
}
代码如下:
输出结果123,132,213,231,312,321,改成字母后输出abc,acb,bac,bca,cba,cab。
法一:枚举方法
#include <iostream> #include <cstdio> using namespace std; int main() { int n; cin >> n; for (int x = 1; x <= n - 2; ++x) for (int y = x + 1; y <= n - 1; ++y) for (int z = y + 1; z <= n; ++z) printf("%d %d %d\n", x, y, z); system("pause"); return 0; }
n = 5时将输出123,124,125,134,135,145,234,235,245,345。
法二:递归实现
设函数comb(int m,int k)是找出1,2,.....m中任取k个数的所有组合,当组合的第一个数字选定后,其后的数字的就是从余下的m-1个数选k-1个数的组合,问题转化为求comb(m-1, k-1).需要一个全局数组存放结果,将确定的k个数字组合的第一个数存储在a[k]中,求出一个组合后输出a数组中的一个组合。每个组合的第一个数字可以是m,m-1,.....k.函数确定组合的第一个数字后,如果还为确定组合的其余元素,则继续递归,否则输出。
#include <iostream> #include <cstdio> using namespace std; const int MAX = 10; int a[10]; void comb(int m, int k) { int i, j; for (i = m; i >= k; --i) { a[k] = i; //每个组合的第一个数可以是m,m-1.....k if (k > 1) comb(i - 1, k - 1); //k>1继续递归 else { for (j = a[0]; j > 0; --j) printf("%d", a[j]); printf("\n"); } } } int main() { int m, k; cin >> m >> k; a[0] = k; //a[0]用来表示k comb(m, k); return 0; }
输入m=5,k=3,输出543,542,541,532,531,521,432,431,421,321。
全排列的生成:
最简单的方法就是使用STL<algorithm>中的next_permutation,其返回bool值,不过经常用就啥都忘了。
#include <iostream> #include <cstdio> #include <algorithm> //for sort and next_permutation using namespace std; int main() { int n; scanf("%d", &n); int p ; for (int i = 0; i < n; ++i) scanf("%d", &p[i]); sort(p, p + n); //处理乱序输入,适用于可重复集合 do { for (int i = 0; i < n; ++i) printf("%d ", p[i]); printf("\n"); } while (next_permutation(p, p + n)); //bool next_purmutation(begin, end) system("pause"); return 0; }
递归生成1-n的全排列(无重复元素):如abc的全排列有abc,acb,bac,bca,cba,cab,一共3!个。
设对R={r1,r2,......rn}全排列,设Ri = R - {ri},perm(X)表示集合X的全排序,(ri)perm(X)表示X全排序前加前缀ri
全排列的递归定义:设n是集合中元素个数
r n = 1
perm(R)= (r1)perm(R1)
......
(rn)perm(Rn)
如果给定集合{a,b,c,d},所有可能的排列由下列排列组成
1.以a开头的后面跟着(b,c,d)的所有排列
2.以b开头的后面跟着(a,c,d)的所有排列
3.以c开头的后面跟着(a,b,d)的所有排列
4.以d开头的后面跟着(a,b,c)的所有排列
递归线索是“后面跟着。。。的所有排列”,若能够解决n-1个元素集合的排列,就可以解决n个元素集合的排列问题。
处理时将所有数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
#include <iostream> #include <cstdio> #include <algorithm> //for swap(x,y) using namespace std; void perm(char *a, int i, int n) { if (i == n) //递归出口 { for (int j = 0; j <= n; ++j) printf("%c", a[j]); printf("\n"); } else { for (int j = i; j <= n; ++j) { swap(a[i], a[j]); perm(a, i + 1, n); swap(a[i], a[j]); //第二个swap用于恢复与第一个数交换 } } } int main() { char a[3] = {'a', 'b', 'c'}; perm(a, 0, 2); //初始调用perm(list, 0, n-1) system("pause"); return 0; }
执行后输出abc,acb,bac,bca,cba,cab,按照字典序输出。
根据这种思路还有一种方法确定递归函数:(参考刘汝佳《算法竞赛入门经典》)
1已经确定的前缀序列,以便输出
2需要进行全排列的元素集合,以便依次选作第一个元素
伪代码:
void perm(序列A, 集合S)
{
if(S为空)
输出序列A;
else 按照从小到大的顺序依次考虑S的每个元素v
perm(在A的末尾添加v后得到的新序列,S-{v});
}
代码如下:
#include <iostream> #include <cstdio> using namespace std; void perm(int n, int *a, int cur) { int i, j; if (cur == n) //递归边界 { for (i = 0; i < n; ++i) printf("%d", a[i]); //处理字母时只需要改成printf("%c", a[i]+'a'-1); printf("\n"); } else for (i = 1; i <= n; ++i) //在a[cur]中填各种整数i { bool ok = 1; for (j = 0; j < cur; ++j) if (a[j] == i) ok = 0; //若i在a[0]...a[cur-1]出现过,则不能再选 if (ok) //ok为真说明i没有在序列中出现过 { //就把i添加到序列末尾后递归调用,这样处理就不必保存集合S了 a[cur] = i; perm(n, a, cur + 1); } } } int main() { int a[] = {1, 2, 3}; perm(3, a, 0); //初始调用perm(n, a, 0) system("pause"); return 0; }
输出结果123,132,213,231,312,321,改成字母后输出abc,acb,bac,bca,cba,cab。
相关文章推荐
- 排列组合,子集生成问题,与nyoj 组合数
- 组合数学 + STL --- 利用STL生成全排列
- poj 1146 ID Codes (字符串处理 生成排列组合 生成当前串的下一个字典序排列 【*模板】 )
- 生成组合和排列
- 用递归函数生成排列组合
- 排列组合与回溯算法浅谈
- 一个排列、组合的生成算法
- 递归求解几类排列组合问题(五、生成全子集组合排列)
- 组合序列、排列序列的生成实现
- 随机数与排列组合:生成1亿个随机的不同16位数
- 排列组合算法1:生成全部有序列
- 递归求解几类排列组合问题(六、非重复生成全子集组合排列)
- STL生成的简单的排列组合数值
- 一个排列、组合的生成算法 [zz]
- 递归的思想生成排列组合
- python 生成排列、组合以及选择
- 浅谈c语言中一种典型的排列组合算法
- dfs 生成排列和组合
- 排列组合算法1:生成全部有序列b
- 生成某字符串的所有排列组合