算法竞赛入门经典第七章暴力求解法——子集生成

上一节我整理了一下全排列的生成算法,有两个方法,我建议使用c++中algorithm的next_permutation函数(下一个排列),那么这一节我依然按照紫书的框架顺序整理子集生成的三个算法——1.增量构造法。2。位向量法。3.二进制法。提前说一句,二进制法是真的神奇,当时理解挺长时间的,后来看懂了,惊呼一句“哇!神奇”可见算法真的是每天给我惊喜,虽然虐死我这个弱渣,但是每天看见今天的自己比昨天的自己有进步,还是觉得有收获的。
ok,废话不多说,开始整理。


-增量构造法

思路:每次选择一个元素放到集合A中,使用递归

#include
using namespace std;
void print_subset(int n,int* A,int cur)
{
    for(int i=0;iprintf("%d ",A[i]);//打印当前集合
    printf("\n");
    int s = cur?A[cur-1]+1:0;//确定当前元素的最小可能值
    for(int i=s;i1);//递归构造子集
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    int A[20];
    print_subset(n,A,0);
    return 0;
}

这个方法用到了“定序”的技巧,避免同一个集合枚举两次。其实这个算法很自然,但是当时理解这个递归还是花了一点时间的,后来我分析递归,就直接一步步走,然后模拟递归过程,实在不行就画个图,比如树,这样分析起来会更加直观,虽然浪费点时间,但是理解透彻最重要。


-2.位向量法

这个算法是构造一个B数组作为位向量

#include
using namespace std;

int B[100];
void print_subset(int n,int *B,int cur)
{
    if(cur==n){
        for(int i=0;iif(B[i]) printf("%d ",i);
        printf("\n");
        return;
    }
    B[cur]=1;
    print_subset(n,B,cur+1);//选第cur个元素 
    B[cur]=0;
    print_subset(n,B,cur+1);//不选第cur个元素 
}
int main()
{
    int n;
    scanf("%d",&n);
    print_subset(n,B,0);
    return 0;
}   

假设n=2,我们模拟一下这个递归过程。
1.首先cur=0,cur!=n,则不输出,刚开始选cur即B[0]=1
2.然后递归cur+1(cur=1),不输出,再选cur即B[1]=1
3.然后递归cur+1(cur=2),等于n,则输出,此时B[0~1]=1,那么输出0,1,这时候return;
4.返回到cur=1(即第2步)的时候,然后不选cur即B[1]=0
5.然后递归cur+1(cur=2),因为B[1]=0,则1不输出,只输出0,这时候return
6.返回到cur=0(即1步)的时候,然后不选cur,B[0]=0,递归cur+1(cur=1),再选cur,B[1]=1,那么递归后输出1,这时候return;
7.返回到cur=1时,然后不选cur,即B[0~1]都为0,所以都不输出
最终结果为{0,1}、{0}、{1}。这个过程是不是很复杂啊!


-3.二进制法

不按照书上的讲法来整理,我按照我自己的理解方法来整理,而按照书上的讲解,我还想了好长时间,为什么二进制可以表示子集呢??

我们首先举一个例子:
当n等于3时候即0~2的子集为{0},{1},{0,1},{2},{0,2},{1,2}{0,1,2}。
ok,那我们看一下0~2^3-1即0~7的二进制依次表示为0,01,10,11,100,101,110,111。
好,我们对应一下(注意0这个二进制对应的是空集)
01->{0}
10->{1}
11->{0,1}
100->{2}
101->{0,2}
110->{1,2}
111->{0,1,2}
观察一下,不难看出其中的玄妙,所以说我为什么感叹算法的神奇了吧。也就是为什么2^n就是n的子集数了,因为刚好每一个二进制数就可以表示一个子集嘛~interesting!!
实现代码如下

#include
using namespace std;
void print_subset(int n,int s)
{
    for(int i=0;iif(s&(1<printf("%d ",i);//二进制的位运算  
    }
    printf("\n");
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<(1<return 0;
 } 

不难看出这个方法使得枚举子集和枚举整数一样十分的简单。good!
注意位运算是如何运算的。以后会专门开一个专栏把这些细节部分记录整理下来的,因为这些运算符使得一个算法能够变得那么神奇,所以也很重要。

你可能感兴趣的:(算法竞赛入门经典(紫书))