一、生成1~n的排列:
这代码的实现使用了递归的方式!唉,但是关于递归的使用还是不够熟练,理解亦不够深入,顾作此文!
还有就是从算法到程序的实现,觉得还是欠缺很多啊!
/*
Date:2014/11/02
By: VID
Function: 在本程序中实现了两个功能。
1、 输入正整数n,按字典序从小到大的顺序输出1~n的所有排列。列如:
Sample Input
3
Sample Output
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
2、手动录入数组(输入的没有重复元素!!),按字典序从小到大的顺序输出该数组的所有排列。(程序中所有标注方式2的。)列如:
Sample Input
3
2 4 6
Sample Output
2 4 6
2 6 4
4 2 6
4 6 2
6 2 4
6 4 2
*/
#include
using namespace std;
#define N 500
int P[N],A[N];
// 递归…
void print_pumutation(int n,int* A,int cur)
{
int i,j;
if(cur == n) // cur指的是当前的位置!
{
for(i = 0;i>n)
{
/*
方式2:
for(int i = 0;i>P[i];
}
*/
memset(A,0,sizeof(A));
print_pumutation(n,A,0);
}
return 0;
}
二、生成可重集的排列
注:这里的意思是输入中有重复元素,我们输出的各个排列当然是不会重复的。
例如:Sample Input
3
1 1 2
Sample Output
1 1 2
1 2 1
2 1 1
对于这样的问题:
/*
Date:2014/11/02
By: VID
Attention:
对于这样的问题,只需在上一个程序的基础之上做一些修改,
便可得到输入是重复集的全排列。
->如果想用字典序输出,只需加上一个排序的函数即可。
*/
#include
using namespace std;
#define N 500
int P[N],A[N];
// 递归…
void print_pumutation(int n,int P[],int* A,int cur)
{
int i,j;
if(cur == n) // cur指的是当前的位置!
{
for(i = 0;i>n)
{
for(int i = 0;i>P[i];
}
memset(A,0,sizeof(A));
print_pumutation(n,P,A,0);
}
return 0;
}
(2)与上面的刚刚相反,下面的程序输出的每一个排列之中的元素是可以重复的,而元素输入是不能有重复的。
例如:Sample Input
2
1 2
Sample Output
1 1
1 2
2 1
/*
Date:2014/11/02
By: VID
Function:
使用规则:要求输入的元素没有重复元素。
结果 :输出有重复元素的全排列!!
*/
#include
using namespace std;
#define N 500
int P[N],A[N];
void print_pumutation(int n,int* A,int cur)
{
int i,j;
if(cur == n) // cur指的是当前的位置!
{
for(i = 0;i>n)
{
for(int i = 0;i>P[i];
}
memset(A,0,sizeof(A));
print_pumutation(n,A,0);
}
return 0;
}
关于刚刚上面研究的两个问题,还可以使用STL中的库函数next_permutation(生成下一个全排列!)。这个函数可以轻松的解决上面的两个问题。下面举个例子,这个例子来自:点击打开链接
/*
对于这些"lcq", "love", "code", "plmm"字符串按照字典序把他们的全排列输出来。
所以当然,对于字符串都可以输出他们的排列,简单的整形数组就更不在话下,可以很出色的解决
他们的全排列,无论输入是否有重复。
*/
#include
#include
#include
using namespace std;
int main()
{
string word[] = {"lcq", "love", "code", "plmm"};//C++里面带的一个string类
int n = sizeof(word) / sizeof(word[0]);
sort(word, word+n);//排序
do
{
for(int i=0; i
#include
#include
using namespace std;
template
bool my_next_permutation(BidirectionalIterator first, BidirectionalIterator last)
{
if(first == last)//空区间
return false;
BidirectionalIterator i = first;
if(last == ++i)//只有一个元素
return false;
i = last;//i 指向尾端
--i;
for(;;)
{
BidirectionalIterator ii = i;
--i;
if(*i < *ii)//如果前一个元素小于后一个元素
{
BidirectionalIterator j = last;//令j指向尾端
while(!(*i < *--j));//有尾端往前栈、直到遇上比 *i大的元素
iter_swap(i, j);//交换i,j
reverse(ii, last);//将ii之后的元素全部逆向重排
return true;
}
if(i == first)//进行至最前面了
{
reverse(first, last);//全部逆向重排
return false;
}
}
}
int main()
{
char a[] = {'d', 'c', 'a', 'a'};
int n = sizeof(a) / sizeof(a[0]);
sort(a, a+n);
do
{
for(int i=0; i
Description
从集合{1,2,3,...,n}中选取k个数所组成的所有集和。
Input
输入的两个正整数。第一个数为n(1<=n<=20),第二个数为k,(k<=n),两个数之间用空格隔开。
Output
输出含有k个数的所有不相同的集合,输出集合的序列按照字典序输出,每个集合占一行,集合的相邻两个数字用空格隔开。
Sample Input
3 2
Sample Output
1 2
1 3
2 3
(1)、增量构造法:即:一次选出一个元素放到集合中。
#include
#include
#include
using namespace std;
const int MAX = 100;
int cmp(const void *a, const void *b)
{
return *(int*)a - *(int*)b;
}
void fullCombination(int num[], int rcd[], int cur, int begin, int n)
{
int i;
for(i=0; i>n;
while(1)
{
for(i=0; i>a;
num[i] = a;
}
qsort(num, n, sizeof(num[0]), cmp);
fullCombination(num, rcd, 0, 0, n);
}
return 0;
}
/*
INPUT
3
3 2 1
OUTPUT
1
1 2
1 2 3
1 3
2
2 3
3
*/
很显然,递归的边界是集合num[]中没有数的时候。
(2)、位向量法:
第二种方法是构造一个位向量B[i],其中当B[i]==1的时候i元素在子集a[]中,B[i]==0时不在子集a[]中。代码如下:
#include
const int MAX = 100;
void fullCombination(int n, int* B, int cur)
{
if(cur == n)
{
for(int i = 0; i < cur; i++)
{
if(B[i])
printf("%d ", i+1); // 打印当前集合
}
printf("/n");
return;
}
B[cur] = 1; // 选第cur个元素
fullCombination(n, B, cur+1);
B[cur] = 0; // 不选第cur个元素
fullCombination(n, B, cur+1);
}
int main()
{
int B[MAX], n;
while(scanf("%d", &n) != EOF)
fullCombination(n, B, 0);
return 0;
}
/*
INPUT
3
OUTPUT
1 2 3
1 2
1 3
1
2 3
2
3
*/
(三)、二进制法
接下来我要重点介绍的一种方法是利用二进制来表示{1,2,3,……,}的子集S:从右往左用一个整数的二进制表示元素i是不是再集合S中。下面演示了用二进制110110100表示集合{7,6,4,3,1}。
OK,有了这个思想,我们就可以把整数想象为二进制的数,实际上,我们也知道,整数在机器里面都是用0,1表示的,可以这么说,0,1创造了计算机的整个世界。这就是为什么判断一个整数是不是奇数用if(n&1) n为奇数;(奇数用二进制表示末尾一定是1)比用if(1 == n%1) n为奇数;快多了的原因。知道了表示,还要知道怎样操作整数来表示集合,这点发明C语言的人早就为我们想到了。他们分别是&,|, ^.
好了,就看怎样用代码实现吧:
#include
void fullCombination(int n, int s) // 打印{1, 2, ..., n}的子集S
{
for(int i = 0; i < n; i++)
{
if(s&(1<
我比较喜欢第三段代码。所以把第三段代码稍微修改一下就能完成老师的所提的问题了。修改后的代码如下:
#include
int numOfOne(int n)//计算n转换为二进制后1的个数
{
int count = 0;
while(n)
{
if(n&1)
count++;
n>>=1;
}
return count;
}
void fullCombination(int n, int s, int k) // 打印{1, 2, ..., n}的子集S
{
int count=0;
for(int i = 0; i < n; i++)
{
if(s&(1<
3、如果输入n个数,求着n个数构成的所有子集,不允许输出重复项。如
输入
3
1 1 3
输出
1
1 1
1 1 3
1 3
3
#include
#define MAX_N 10
int rcl[MAX_N], num[MAX_N], used[MAX_N];
int m,n;
void unrepeat_combination(int index, int p)
{
int i;
for (i=0; i0)
{
used[i]--;
rcl[index] = num[i];
unrepeat_combination(index+1, i);
used[i]++;
}
}
}
int read_data()
{
if (scanf("%d", &n)== EOF)
{
return 0;
}
int i, j, val;
m = 0;
for (i=0; i
总结:关于枚举排列与子集生成的大部分方法都整理出来了,借鉴了很多资料。希望能给自己梳理一下整个思路,能有所收获!
另外:
1、输入一个数组a[],里面有n(1<=n<=1000)个数,给出数组a[]所有数字的一个全排列,求他按照字典排序,这个全排列是所有排列中的第几个。比如a[5] = {1, 1, 4, 5, 8};1 4 1 5 8是该全排列的第8个。
2、如果不晓得对全排列掌握得怎么样,请到POJ上提交你的代码,注意,请不要使用next_permutation();题目的链接为:
http://poj.org/problem?id=1731
http://poj.org/problem?id=1256
http://poj.org/problem?id=1833
http://poj.org/problem?id=1318
http://poj.org/problem?id=1146
如果你能不使用next_permutation()把这5个题目AC,那么,估计以后面试的时候全排列应该没问题。等不用next_permutation()把那五个题目AC之后,再用next_permutation()爽一把吧。
3、同时考虑如何生成一个规定个数的子集,且子集里的元素可以重复出现。
例如:
INPUT
6
1 2 4 6 7 9
OUTPUT
……
1 1 1 1 2 2
1 1 1 1 1 2
……
像是这种,输入的数据没有重复的,但是每个生成的小的排列内部,比如1 就可以重复使用多次,而且这个输出序列元素个数是这输入元素的个数决定的!
另外还有关于递归的过程梳理,一定要整理出一片博客来!