生成子集 (增量构造法 || 二进制法)

① 使用增量构造法可以构造出升序数组arr的不重复子集,并且按字典序排序

#include<bits/stdc++.h>
using namespace std;
int arr[16];
inline void print_subset(int *index, int cur, int n)///cur值这里可以理解为在这个堆栈层子集的集合数
{
    for(int i=0; i<cur; i++) {printf("%d ", arr[index[i]]);} if(cur)puts("");
    int s = cur ? index[cur-1]+1 : 0;///因为index是arr升序序列的下标,这里cur就是当前arr可能最小值的下标
    for(int i=s; i<n; i++){
        index[cur] = i;
        print_subset(index, cur+1, n);
    }
}
int main(void)
{
    int n, index[16];///index数组辅助构造,其值为升序序列的下标,注意是从0开始
    for(int i=0; i<16; i++) index[i] = i;
    while(~scanf("%d", &n)){
        int cur = 0;
        for(int i=0; i<n; i++) arr[i] = i+1;///这里arr的值为1~n的一个序列
        print_subset(index, cur, n);
        puts("");
    }
    return 0;
}
View Code

如果要构造右边这样的排序的话,以输入3为例     

可以在原有的基础上使用一个结构体将每个子集的长度和具体序列用int和string存起来就能通过二级排序构造出来了

#include<bits/stdc++.h>
using namespace std;
int arr[16];
struct item
{
    int len, digit[16];
    string s;
};
bool cmp(const item fir, const item sec)
{
    if(fir.len==sec.len) return fir.s < sec.s;
    return fir.len < sec.len;
}
item ans[1<<16];
int top = 0;
inline void print_subset(int *index, int cur, int n)
{
    //for(int i=0; i<cur; i++) {printf("%d ", cur);printf("%d ", arr[index[i]]);} puts("");
    ans[top].len = cur;
    stringstream temp;
    for(int i=0; i<cur; i++){
        ans[top].digit[i] = arr[index[i]];
    }
    temp<<ans[top].digit;
    temp>>ans[top].s;
    top++;
    temp.clear();
    int s = cur ? index[cur-1]+1 : 0;
    for(int i=s; i<n; i++){
        index[cur] = i;
        print_subset(index, cur+1, n);
    }
}
int main(void)
{
    int n, index[16];
    for(int i=0; i<16; i++) index[i] = i;
    while(~scanf("%d", &n)){
        int cur = 0; top = 0;
        for(int i=0; i<n; i++) arr[i] = i+1;
        print_subset(index, cur, n);
        sort(ans, ans+top, cmp);
        for(int i=0; i<top; i++){
//            if(ans[i].len) printf("%d ", ans[i].len);
            for(int j=0; j<ans[i].len-1; j++){
                printf("%d ", ans[i].digit[j]);
            }
            printf("%d", ans[i].digit[ans[i].len-1]);
            puts("");
        }
        puts("");
    }
    return 0;
}
View Code

 

② 使用二进制代表集合有几个注意的点

1、A&B、A|B和A^B分别对应集合的交、并和对称差

2、如果是n那就一般都是0~n-1的子集,而且对应的二进制位是从右往左

 

3、把全集定义为 ALL_BITS= (1<<n)-1,这个之前碰到过,也就是长度为n的二进制2^n-1就是其全一情况时候的十进制表示,例如长度为3的二进制那2^3-1=7=(111)2

#include<bits/stdc++.h>
using namespace std;
inline void print_subset(int len, int subset)
{
    for(int i=0; i<len; i++){ ///原集合中有多少个元素则对应有多少位,位值为 1 说明子集含有这个元素、否则没有
        if(subset&(1<<i)) printf("%d ", i); ///也可以将这个 i 作为数组下标,枚举数组a[0~n-1]的子集
    }
    if(subset) puts("");
}
int main(void)
{
    int n;
    while(~scanf("%d", &n)){
        for(int i=0; i<(1<<n); i++){ /// 0是空集、(1<<n)是全集、每个 i 代表一个子集
            print_subset(n, i); ///传入原集合大小、表示子集的 i
        }
    }
    return 0;
}
View Code

 

posted @ 2017-07-11 19:51  qwerity  阅读(1012)  评论(0编辑  收藏  举报