在暴力法中经常会遇到需要枚举排列和获取所有子集的需要,下面给出几种常用的方法。
使用递归的方式即可生成,注意因为不能有重复的元素,所以在选取元素时需要先判断数组中是否已存在该元素,如果存在则不能进入下一层递归。
public void printPermutation(int n, int A[], int k) {
if(k == n) {
for(int i = 0 ; i < n; i++) {
System.out.print(A[i]);
}
System.out.println("");
return;
}
// 遍历所有取值
for(int i = 1; i <= n ; i++) {
int ok = 1;
for(int j = 0; j < k; j++) {
if(A[j] == i)
ok = 0;
}
if(ok == 1) {
A[k] = i;
printPermutation(n, A, k + 1);
}
}
}
当需要从数组P中选取元素生成排列时,需要考虑重复项的问题,首先,如果还是判断A[0:k]中是否存在P[i]的话,那么当P = {1,1,1}的时候就无法获得结果,因为k是无法等于n的。
考虑用数组A和P中等于P[i]的值的个数,如果A中更少,则包括两种情况:
① A中不存在P[i]
② P[i]在P中有多个,A并没有全部选中
所以这种情况下可以使得A[k] = P[i]
另一方面,考虑P={1,1,2}时,会出现多个112、211等重复的项,这是因为k=0时,选中第一个1和第二个1的情况完全是相同的,因为此时还剩下{1,2},k=1时再次选择1就会出现多个112,其他重复的项同理。那么如何解决?由于P数组的有序的,所以只需要保证P[i]!=P[i-1]即可,这样的话就不会进行多余的递归。
对于P={1,1,2}这个例子,由于P[0]=P[1],所以i=1时不执行,所以k=0时可以选择1或2,进而生成112、121、211这三个排列。
public void printPermutation(int n, int P[], int A[], int k) {
if(k == n) {
for(int i = 0 ; i < n; i++) {
System.out.print(A[i]);
}
System.out.println("");
return;
}
// 遍历所有取值
for(int i = 0; i < n ; i++) {
// 避免生成重复的排列 i=0时也可以进入
if(i == 0 || P[i] != P[i - 1]) {
// 计算数组P和A中P[i]的个数
int c1 = 0, c2 = 0;
for(int j = 0; j < k; j++) if(P[i] == A[j]) c1++;
for(int j = 0; j < n; j++) if(P[i] == P[j]) c2++;
// 如果数组A中更少 说明还可以选取该项
if(c1 < c2) {
A[k] = P[i];
printPermutation2(n, P, A, k + 1);
}
}
}
}
增量构造法在每一层递归中都会输出一个子集,集合的子集总共有2^n个(包括空集),所以解答树的结点为 2^n个
值得一提的在for循环中的起始需要保证比前一个子集的最后一个元素大,从而不会出现{1, 2}和{2, 1}这样重复的子集出现,每个自己的元素都是递增的。
public void printSubset(int n, int A[], int k) {
// 因为是子集 所以不需要k=n的时候再输出
for(int i = 0 ; i < k; i ++) {
System.out.print(A[i]);
}
System.out.println("");
// 这里使得子集必须是有序的 防止了重复子集的出现
int s = (k > 0) ? A[k - 1] + 1 : 0;
for(int i = s; i < n; i++) {
A[k] = i;
printSubset(n, A, k + 1);
}
}
// 调用方式 printSubset(n, A, 0);
位向量法相当于用一个0-1向量表示该子集,比如[1,1,1]表示{0,1,2},当递归深度达到向量长度时输出。
public void printSubset(int n, int B[], int k) {
// 当向量的每一位的状态确定后输出
if(k == n) {
for(int i = 0 ; i < k; i ++) {
if(B[i] == 1)
System.out.print(i);
}
System.out.println("");
return;
}
B[k] = 1;
printSubset2(n, B, k + 1);
B[k] = 0;
printSubset2(n, B, k + 1);
}
二进制法是三种方法中最简单的一种,比如1111B可以表示为子集{0, 1, 2, 3},对应位为1表示选中该元素,这一点和位向量法相同。所以我们可以用一个二进制数来表示一个子集,当元素取0~n-1时,子集对应的二进制数可以取0 ~(1<public void printSubset(int n) {
// 遍历2^n个子集
for(int i = 0; i < (1<<n); i++) {
// 打印结果
for(int j = 0; j < n; j++) {
if((i & (1<<j)) != 0) {
System.out.print(j);
}
}
System.out.println("");
}
}