子集生成的三种算法
子集生成算法:
给定一个集合,枚举所有可能的子集。暂时讨论没有重复元素的情况。
1 增量构造法
一次选出一个元素放到集合中,和前面不同,由于A中的元素个数不确定,每次递归都要输出当前集合。另外递归边界也不需要显式确定-如无法添加元素,就不会递归了。
注意:定序,规定集合A的所有元素的编号从小到大排列,就不会把集合{1,2}按照{1,2}和{2,1}输两次了
代码:
1 void print_subset(int n,int*A,int cur) 2 { 3 for(int i = 0;i<cur;i++) 4 { 5 cout << A[i] << " "; 6 } 7 cout << endl; 8 int s = cur? A[cur-1]+1 : 0;//确定当前元素最小可能值 9 for(int i = s;i<n;i++) 10 { 11 A[cur] = i; 12 print_subset(n,A,cur+1); 13 } 14 }
将代码稍作修改后就可以输出P中的元素了,因为把1~n的子集放在A内,A就可以当P的子集的元素的索引了
代码:
1 void print_subset_2(int n,int*P,int*A,int cur) 2 { 3 for(int i = 0;i<cur;i++) 4 { 5 cout << P[A[i]] << " "; 6 } 7 cout << endl; 8 int s = cur?A[cur-1]+1:0; 9 for(int i = s;i<n;i++) 10 { 11 A[cur] = i; 12 print_subset_2(n,P,A,cur+1); 13 } 14 }
2 位向量法
构造一个位向量B[i],而不是直接构造子集A本身,其中B[i]=1,当且仅当i在子集A中。
代码:
1 void print_subset_3(int n,int*B,int cur) 2 { 3 if(cur==n) 4 { 5 for(int i = 0;i<n;i++) 6 { 7 if(B[i]) cout << i << " "; 8 } 9 cout << endl; 10 return; 11 } 12 B[cur] = 1; 13 print_subset_3(n,B,cur+1); 14 B[cur] = 0; 15 print_subset_3(n,B,cur+1); 16 }
所有元素判断完是否选择后才是一个完整的子集。现在解答树上有2047个节点
这是一个n+1层二叉树(cur从0到n),第0曾有1个节点,第二层有两个节点(第一位是否选择),...,总数为1+2+...+2^n=2^(n+1)-1。
也可改进成输出P的形式
代码:
1 void print_subset_4(int n,int*P,int*B,int cur) 2 { 3 if(cur==n) 4 { 5 for(int i = 0;i<n;i++) 6 { 7 if(B[i]) cout << P[i] << " "; 8 } 9 cout << endl; 10 return; 11 } 12 B[cur] = 1; 13 print_subset_4(n,P,B,cur+1); 14 B[cur] = 0; 15 print_subset_4(n,P,B,cur+1); 16 }
3 二进制法
思想上与位向量法近似。
代码:
1 void print_subset(int n,int s) 2 { 3 for(int i = 0;i<n;i++) 4 { 5 //cout << s << " " << i << " " << (s&(1<<i)) << endl; 6 if(s&(1<<i)) cout << i << " "; 7 } 8 cout << endl; 9 } 10 void print_subset_5(int n) 11 { 12 for(int i = 0;i<(1<<n);i++) 13 { 14 //cout << i << endl; 15 print_subset(n,i); 16 } 17 }
可能理解起来有点绕,主要是用十进制的二进制表示和十进制的数值本身有点混
举个栗子:
现在n=4
那么在print_permutation_5里面我有循环了
0-15,二进制位从0-1111;它就相当于位向量,提供子集中元素出现的位置
接着对于每一个上面的数来依次与0~3做位移运算后得到的1,2,4,8,(二进制为0001,0010,0100,1000),来按位与,实际上是看0-3这个数在子集中是否出现
由于0-15已经含了对于元素个数为4的集合的所有子集的可能情况,那么与之后就得到该出现的子集元素。
代码汇总:
1 #include <iostream> 2 #define max_n 10005 3 using namespace std; 4 int A[max_n]; 5 int B[max_n]; 6 //增量构造法 7 void print_subset(int n,int*A,int cur) 8 { 9 for(int i = 0;i<cur;i++) 10 { 11 cout << A[i] << " "; 12 } 13 cout << endl; 14 int s = cur? A[cur-1]+1 : 0;//确定当前元素最小可能值 15 for(int i = s;i<n;i++) 16 { 17 A[cur] = i; 18 print_subset(n,A,cur+1); 19 } 20 } 21 void print_subset_2(int n,int*P,int*A,int cur) 22 { 23 for(int i = 0;i<cur;i++) 24 { 25 cout << P[A[i]] << " "; 26 } 27 cout << endl; 28 int s = cur?A[cur-1]+1:0; 29 for(int i = s;i<n;i++) 30 { 31 A[cur] = i; 32 print_subset_2(n,P,A,cur+1); 33 } 34 } 35 //位向量法 36 void print_subset_3(int n,int*B,int cur) 37 { 38 if(cur==n) 39 { 40 for(int i = 0;i<n;i++) 41 { 42 if(B[i]) cout << i << " "; 43 } 44 cout << endl; 45 return; 46 } 47 B[cur] = 1; 48 print_subset_3(n,B,cur+1); 49 B[cur] = 0; 50 print_subset_3(n,B,cur+1); 51 } 52 void print_subset_4(int n,int*P,int*B,int cur) 53 { 54 if(cur==n) 55 { 56 for(int i = 0;i<n;i++) 57 { 58 if(B[i]) cout << P[i] << " "; 59 } 60 cout << endl; 61 return; 62 } 63 B[cur] = 1; 64 print_subset_4(n,P,B,cur+1); 65 B[cur] = 0; 66 print_subset_4(n,P,B,cur+1); 67 } 68 //二进制法 69 void print_subset(int n,int s) 70 { 71 for(int i = 0;i<n;i++) 72 { 73 //cout << s << " " << i << " " << (s&(1<<i)) << endl; 74 if(s&(1<<i)) cout << i << " "; 75 } 76 cout << endl; 77 } 78 void print_subset_5(int n) 79 { 80 for(int i = 0;i<(1<<n);i++) 81 { 82 //cout << i << endl; 83 print_subset(n,i); 84 } 85 } 86 int main() 87 { 88 int P[] = {1,2,3,4}; 89 //print_subset_2(4,P,A,0); 90 //print_subset_3(4,B,0); 91 //print_subset_4(4,P,B,0); 92 print_subset_5(4); 93 return 0; 94 }
我们不只生产博客,也是优质博客链接的搬运工