枚举所有子集的三种算法详解-《算法入门经典》

Posted acmsong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了枚举所有子集的三种算法详解-《算法入门经典》相关的知识,希望对你有一定的参考价值。

方法一:增量构造法

  理解递归必须得理解函数到底是做什么的。

#include<cstdio>

void print_subset(int n,int *a,int cur)  //print_subset的功能是打印集合{0,1,...,n-1}的cur个元素的子集,并且目前a数组中已经存储了要打印的集合 
{
    printf("{ ");
    for (int i=0;i<cur;i++) printf("%d ",a[i]);
    printf("}\n");
    int mi=cur?a[cur-1]+1:0;  //利用字典序避免重复打印 
    for (int i=mi;i<n;i++)  //枚举下一位可以添加什么元素 
    {
        a[cur]=i;
        print_subset(n,a,cur+1);  //递归打印cur+1个元素的子集 
    }
}

int main()
{
    int n;
    int a[10]={};
    scanf("%d",&n);
    print_subset(n,a,0); //初始时a中存储了0个元素的子集,开始递归打印 
    return 0;
}

 

方法二:位向量法

  枚举每一位选或者不选,复杂度比方法一略高但更好理解,因为与输出全排列思路差不多,满n位就输出。

#include<cstdio>

int a[10];

void print_subset(int n,int *b,int cur)  //确定第cur位选或者不选 
{
    if (cur==n)  //如果确定好了n位,打印结果 
    {
        printf("{ ");
        for (int i=0;i<n;i++)
            if (b[i]) printf("%d ",a[i]);
        printf("}\n");
    } 
    else
    {
        b[cur]=1;  //这一位选 
        print_subset(n,b,cur+1);  //递归下一位 
        b[cur]=0;  //这一位不选 
        print_subset(n,b,cur+1);  //递归下一位 
    }
}

int main()
{
    int n;
    int b[10]={};
    scanf("%d",&n);
    for (int i=0;i<n;i++) a[i]=i;
    print_subset(n,b,0);
    return 0;
}

  缺点是输出不是按照字典序。

 

方法三:二进制法

  稍加思考就会发现,方法二其实与二进制是对应的。

#include<cstdio>

int a[10];

void print_subset(int n,int b)  //n位的集合,b状态打印 
{
    printf("{ ");
    for (int i=0;i<n;i++)
        if (b&(1<<i)) printf("%d ",a[i]);
    printf("}\n");
}

void p_s(int n)
{
    for (int i=0;i<(1<<n);i++) print_subset(n,i);  //枚举所有状态 
}

int main()
{
    int n;
    scanf("%d",&n);
    for (int i=0;i<n;i++) a[i]=i;
    p_s(n);
    return 0;
}

  这个方法优点就是代码简单。

 

注意:以上三种方法输出顺序不同。

 

以上是关于枚举所有子集的三种算法详解-《算法入门经典》的主要内容,如果未能解决你的问题,请参考以下文章

白话经典算法系列之一 冒泡排序的三种实现 转

算法基础 —— 枚举

白话经典算法系列之一 冒泡排序的三种实现

白话经典算法系列之二 直接插入排序的三种实现

算法竞赛入门经典7.3子集生成增量构造法位向量法二进制法

算法入门竞赛经典7.2枚举排列