枚举子集的3种方式 -- C++描述

要求:

  给定一个集合,枚举所有可能的子集。此处的集合是不包含重复元素的。

Method0: 增量构造法

  思路:每次选取一个元素至集合中,为了避免枚举重复的集合,此处要采用定序技巧 -- 除了第一个元素,每次选取必须要比集合中的前一个元素要大!

  

// A 为原集合;
// B 为子集,每次调用函数即会打印一次
// cur 为子集元素个数
void print_subset0(int *A, int *B, int N, int cur) {
    for(int i=0; i<cur; i++) {
        printf("%5d", B[i]);
    }
    printf("\n");
    if( cur < N ) {
        for( int i=0; i<N; i++ ) {
            if( !cur || A[i] > B[cur-1] ) {
                B[cur] = A[i];
                print_subset0(A, B, N, cur+1);
            }
        }
    }
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};
    printf("Method0:\n");
    int B[Length] = {0};
    print_subset0(A, B, Length, 0);
    printf("\n");
    return 0;
}

  采用的是递归调用,但此处不需要return语句,因为当没有元素可用于枚举时,就不会调用函数,也就是不会继续递归。

   此函数输出的子集中是包含空集的,如果不想用空集,则需判断 cur 是否为0,不为0才打印子集

   测试样例的输出结果(包含空集):

    

Method1: 位向量法

  思路:1个容量为N的集合,每个位置0~N-1,对于每个子集,要么被选中,要么没被选中。枚举每一个位置的状态,可得到各种子集。

  

// A 为原集合;
// A 为原集合
// used为当前A中每个位置的元素的状态(选中或未被选中)
// cur代表现在枚举A[cur]的状态
void print_subset1(int *A, int *used, int N, int cur) {
    if( cur == N ) {
        for(int i=0; i<N; i++) {
            if( used[i] ) {
                printf("%5d", A[i]);
            }
        }
        printf("\n");
        return ;
    }
    used[cur] = 0;
    print_subset1(A, used, N, cur+1);
    used[cur] = 1;
    print_subset1(A, used, N, cur+1);
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};

    printf("Method1:\n");
    int B[Length] = {0};
    print_subset1(A, B, Length, 0);
    printf("\n");

    return 0;
}

  同样是递归枚举,这里需要用return终止递归,终止条件就是cur == N即枚举了一种子集,然后输出  

  此函数的输出是包含空集的,如果不想要空集,则需要判断used函数是否全为0,如果全为0,则不输出

  样例输出(包含空集):

    

Method10: 二进制法

  类似于位向量法,同样也是枚举各个位置的状态,但这次用二进制表示,二进制长度为N,与原集合大小相同。二进制的第 i 位代表原集合中的第 i 位是否被选中,枚举各种情况。集合大小为N,就是2的N次种方式。

  

void print_subset10(int *A, int N, int seq) {
    for(int i=0; i<N; i++) {
        if( seq & (1<<i) ) {
            printf("%5d", A[i]);
        }
    }
    printf("\n");
}

int main() {
    int Length = 3;
    int A[Length] = {1, 3, 2};
    printf("Method10:\n");
    for(int i=0; i<(1<<Length); i++) {
        print_subset10(A, Length, i);
    }
    printf("\n");
    return 0;
}

  这种方式很好写,也很好记,但问题是,因为函数中的形参seq是int型的,所以N最大也就只能32,如果long long,那N也只能最大64,再超过64,就需要用大数或其它表示方式表示了。

   如果不想要空集,可以将main函数中的 i 从1枚举起。

  样例输出结果(包含空集):

    

参考资料: 《算法竞赛入门经典(第2版)》

时间: 2024-11-12 10:08:49

枚举子集的3种方式 -- C++描述的相关文章

Java Enum枚举 遍历判断 四种方式(包括 Lambda 表达式过滤)

package com.miracle.luna.lambda; import java.util.Arrays; /** * @Author Miracle Luna * @Date 2019/6/9 23:40 * @Version 1.0 */ public enum AlarmGrade { ATTENTION("attention", "提示"), WARNING("warning","警告"), SERIOUS(&

求子集的三种方式的总结

求自己总共有三种方式: 增量构造 位向量 二进制 首先假设集合A中有n个元素,而且是非重集,一个下标唯一对应一个元素,那么求A的子集就变成了求0~n-1的子集.这个思想对于所有的三种方式都是通用的. 第一种增量构造法的思想是,每一次都从0~n-1中挑出一个元素来,每挑一次,就是一个集合.然后再挑元素进入这个集合,但是这次挑选元素的时候,必须比之前的那个元素大. 下面是代码实现: //假设后一个非可重集合P,里面的元素各不相同,现在要从中挑选出它的所有子集来 //这个问题可以转换成挑选出P数组的下

Java Array数组 遍历 四种方式(包含 Lambda 表达式遍历)

package com.hello; import java.util.Arrays; /** * @Author Miracle Luna * @Date 2019/6/9 23:33 * @Version 1.0 */public class ArrayLambda { public static void main(String[] args) { Integer[] items = { 1, 2, 3 }; // 普通for循环遍历 System.out.println("第一种方式:普

ASP.NET Core 四种方式绑定枚举值

原文:ASP.NET Core 四种方式绑定枚举值 前言 本节我们来讲讲在ASP.NET Core MVC又为我们提供了哪些方便,之前我们探讨过在ASP.NET MVC中下拉框绑定方式,这节我们来再来重点看看枚举绑定的方式,充分实现你所能想到的场景,满满的干货,你值得拥有. 探讨枚举绑定方式# 我们首先给出要绑定的枚举类. public enum Language { JavaScript, Java, C, Python, SQL, Oracle } 枚举绑定方式一(@Html.DropDow

JavaScript基础——面向对象的程序设计(一)创建对象的几种方式总结

简介 面向对象(Object-Oriented, OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.前面提到过,ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同. ECMA-262把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名字都映射到一个值.正因为这样(以及其他将要讨论的原因),我们可以把E

Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去.因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态.然后等待消费者消费了商品,然后消费者通知生产者队列有空间了.同样地,当

Java中创建对象的5种方式 &amp;&amp;new关键字和newInstance()方法的区别

转载:http://www.kuqin.com/shuoit/20160719/352659.html 用最简单的描述来区分new关键字和newInstance()方法的区别:newInstance: 弱类型.低效率.只能调用无参构造.new: 强类型.相对高效.能调用任何public构造. newInstance( )是一个方法,而new是一个关键字,其次,Class下的newInstance()的使用有局限,因为它生成对象只能调用无参的构造函数,而使用new关键字生成对象没有这个限制.Cla

Spring实现AOP的4种方式(转)

转自:http://blog.csdn.net/udbnny/article/details/5870076 Spring实现AOP的4种方式 先了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时.异常被抛出时等等.3.切入点(Pointcut)通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的

创建控制器的3种方式、深入了解view的创建和加载顺序

转载自:http://blog.csdn.net/weisubao/article/details/41012243 (1)创建控制器的3种方式 [objc] view plaincopy - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window=[[UIWindow alloc]initWithFrame:[