C/C++ 子集生成算法整理

个人笔记,仅供复习

1.概念:给定一个集合,枚举所有可能的子集。

2.常用算法

  • 增量构造法
  • 位向量法
  • 二进制法

3.增量构造法

3.1 思路:一次选出一个元素放到集合中。

3.2 代码实例:

void print_subset(int n,int *A,int cur){
        for(int i = 0;i < cur;i++)   cout << A[i] << " ";//打印当前子集 
        cout << endl;
        int s = cur ? A[cur-1]+1:0;
        for(int i = s;i < n;i++){
                A[cur] = i;
                print_subset(n,A,cur+1);
        }
}

3.3代码解析:

  • 其中cur指的是当前元素的位置。上述代码的作用是读入n,输出{0,1,2,3,…,n-1}的所有子集。第五行中的 s 指的是 当前还可以放入集合A中的最小元素。
  • 上述代码用到了定序的技巧。所谓的定序就是规定集合A中的所有元素按照从小到大排列,这样就不会出现{1,2}和{2,1}这样重复的集合了。
  • 上述代码的作用是输出{0,1,2,3,…,n-1}的所有子集。如果要输出自定义集合的子集,只需要将A[i]当作下标,这样就可以输出自定义的集合了。

4.位向量法

4.1 思路:构造一个位向量B[i],而不是直接构造集合本身,其中B[i] = 1,当且仅当i在子集A中。

4.2 代码实例:

void print_subset(int n,int *B,int cur){
        if(cur == n){
                for(int i = 0;i < cur;i++)
                        if(B[i])        cout << i << " ";
                cout << endl;
                return;
        }
        B[cur] = 1;
        print_subset(n,B,cur+1);
        B[cur] = 0;
        print_subset(n,B,cur+1);
}

5.二进制法

5.1 思路:可以用二进制表示子集,其中从右往左第i位(从0开始编号)表示元素i是否在集合中(1表示在,0表示不在)。

5.2 代码实例:

#include<iostream>
using namespace std;
void print_subset(int n,int s){
        for(int i = 0;i < n;i++)
                if(s&(1<<i))  cout << i << " ";
        cout << endl;
}
int main()
{
        int n;
        cin >> n;
        for(int i = 0;i < (1<<n);i++)
                print_subset(n,i);//枚举各子集对应的编码0,1,2,…,2^n-1.
        return 0;
}

5.3 代码解析:

  • 首先要明白位移运算符(<<)的作用;第五行对1使用位移运算符,(1<<i)指的是将1向左移动i位,右面补0。
  • 若一个集合中有n个元素,那么它的子集有2^n个,而假设每个元素只有存在(1)和不存在(0)两种状态,那么它的子集可以看成不同的二进制串,即每个子集可以代表且仅代表一个不同的十进制数,共2^n个不同的十进制数,这也就说明了每个十进制数代表了一个不同的二进制串,即每个十进制数代表了一个子集,因此可以有上述代码第13行写法。具体关系如下:
十进制数——>二进制串——>从右往左读串,是1则当前位数存在于此集合