子集生成算法:给定一个集合,枚举所有可能的子集

news2024/11/18 11:20:34

给定一个集合,枚举所有可能的子集。

(为简单起见,本文讨论的集合中没有重复元素)

1、方法一:增量构造法

第一种思路是一次选出一个元素放到集合中,程序如下:

void print_subset(int n, int *A, int cur) {

    for (int i = 0; i < cur; i++) printf("%d ", A[i]);

    printf("\n");

    int s = cur ? A[cur - 1] + 1 : 0; //确定当前元素的最小可能值

    for (int i = s; i < n; i++) {
        A[cur] = i;
        print_subset(n, A, cur + 1); //递归构造子集
    }
}

由于 A 中的元素个数不确定,每次递归调用都要输出当前集合。另外,递归边界也不需要显式确定——如果无法继续添加元素,自然就不会再递归了。

该代码用到了定序的技巧:规定集合 A 中所有元素的编号从小到大排列,就不会把集合 {1, 2} 按照 {1, 2} 和 {2, 1} 输出两次了。

提示:在枚举子集的增量法中,需要使用定序的技巧,避免同一个集合枚举两次。

这棵解答树上有 1024 个节点:每个可能的 A 都对应一个结点,而 n n n 元素集合恰好有 2 n 2 ^n 2n 个子集,210 = 1024。

代码

// {0~n-1}的所有子集:增量构造法
#include<cstdio>
using namespace std;

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

int A[10];
int main() {
  int n;
  scanf("%d", &n);
  print_subset(n, A, 0);
  return 0;
}

2、方法二:位向量法

第二种思路是构造一个位向量 B[i],而不是直接构造子集 A 本身,其中 B[i] = 1,当且仅当 i 在子集 A 中。递归实现如下:

void print_subset(int n, int *B, int cur) {
    if (cur == n) {
        for (int i = 0; i < cur; i++) {
            if (B[i]) printf("%d ", i); //打印当前集合
        }
        printf("\n");
        return ;
    }

    B[cur] = 1; //选第cur个元素
    print_subset(n, B, cur + 1);

    B[cur] = 0; //不选第cur个元素
    print_subset(n, B, cur + 1);
}

必须当 “所有元素是否选择” 全部确定完毕后才是一个完整的子集,因此当 if (cur == n) 成立时才输出。

现在的解答树上有 2047 个结点,比刚才的方法略多,因为所有部分解(不完整解)也对应着解答树上的结点。

提示:在枚举子集的位向量法中,解答树的节点数略多,但在多数情况下仍然够快。

这是一棵 n + 1 n+1 n+1 层的二叉树(cur 的范围0 ~ n),第0层有1个结点,第1层有2个结点,第2层有4个结点,第3层有8个结点,…,第 i i i 层有 2 i 2^i 2i 个结点,总数为 1 + 2 + 4 + 8 + . . . + 2 n = 2 n + 1 − 1 1 + 2 + 4 + 8 + ... + 2^n = 2^{n+1} - 1 1+2+4+8+...+2n=2n+11,和实验结果一致。

下图为这棵解答树。
在这里插入图片描述

代码

// {0~n-1}的所有子集:位向量法
#include<cstdio>
using namespace std;

void print_subset(int n, int* B, int cur) {
  if(cur == n) {
    for(int i = 0; i < cur; i++)
      if(B[i]) printf("%d ", i); // 打印当前集合
    printf("\n");
    return;
  }
  B[cur] = 1; // 选第cur个元素
  print_subset(n, B, cur+1);
  B[cur] = 0; // 不选第cur个元素
  print_subset(n, B, cur+1);
}

int B[10];
int main() {
  int n;
  scanf("%d", &n);
  print_subset(5, B, 0);
  return 0;
}

3、方法三:二进制法

还可以用二进制来表示 {0,1, 2,…,n - 1} 的子集 S:从右往左第 i i i 位(各位从0开始编号)表示元素 i i i 是否在集合 S 中。下图展示了二进制 0100011000110111是如何表示集合{0,1,2,4,5,9,10,14} 的。

在这里插入图片描述

注意:为了处理方便,最右边的位总是对应元素0,而不是元素1。

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

表示出集合后,还要对集合进行操作。常见的集合运算都可以用位运算符简单实现。最常见的二元运算符是与(&)、或(|)、非(!),它们和对应的逻辑运算非常相似,如下表所示。

在这里插入图片描述
“异或(XOR)”运算符“^",其规则是 “如果 A 和 B 不相同,在 A ^ B = 1,否则为0”。异或运算最重要的性质就是“开关性”——异或两次以后相当于没有异或,即 A^B^B = A。另外,与、或和异或都满足交换律:A&B = B&AA|B = B|AA^B = B^A

与逻辑运算符不同的是,位运算符(bitwise operator)是逐位进行的——两个 32 位整数的"按位与" 相当于 32 对 0/1 值之间的运算。下表比欧式了二进制数10110(十进制22)和01100(十进制12)之间的按位与、按位或、按位异或的值,以及对应的集合运算的含义。

在这里插入图片描述

可见,A&B、A|B 和 A^B 分别对应集合的交、并和对称差。另外,空集为0,全集{0, 1, 2, …, n − 1 n-1 n1} 的二进制为 n n n 个 1,即十进制 2 n − 1 2^n - 1 2n1为了方便,往往在程序中把全集定义为 ALL_BITS = (1 << n) - 1,则 A 的补集就是ALL_BITS^A 当然,直接用整数减法ALL_BITS - A 也可以,但速度比位运算 “^” 慢。

提示:当用二进制表示子集时,位运算中的按位与、或、异或对应集合的交、并和对称差。

如此,就可以用下面的程序输出子集 S 对应的各个元素:

void  print_subset(int n, int s) { //打印 {0, 1, 2, ..., n-1} 的子集S
    for (int i = 0; i < n; i++) {
        if (s & (1 << i)) printf("%d ", i); //利用了C语言“非0值都为真”的规定
    }
    printf("\n");
}

而枚举子集和枚举整数一样简单:

for (int i = 0; i < (1 << n); i++) { //枚举各子集所对应的编码0, 1, 2, ..., 2^n - 1
    print_subset(n, i);
}

代码

// {0~n-1}的所有子集:二进制法
#include<cstdio>
using namespace std;

void print_subset(int n, int s) {  // 打印{0, 1, 2, ..., n-1}的子集S
  for(int i = 0; i < n; i++)
    if(s&(1<<i)) printf("%d ", i); // 这里利用了C语言“非0值都为真”的规定
  printf("\n");
}

int main() {
  int n;
  scanf("%d", &n);
  for(int i = 0; i < (1<<n); i++)  // 枚举各子集所对应的编码 0, 1, 2, ..., 2^n-1
    print_subset(n, i);
  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1142187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++系列之list的模拟实现

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; list的节点类 template struct list_Node { public: list_Node* _prev; list_…

Tomcat服务部署和优化

目录 一、Tomcat&#xff1a; 1、Tomcat作用&#xff1a; 2、Tomcat的核心组件&#xff1a; 3、servlet作用&#xff1a; 4、Tomcat的核心功能&#xff1a; 二、tomcat配置 一、Tomcat&#xff1a; 是一个开源的web应用服务器&#xff0c;nginx主要处理静态页面&#xff…

不再受害:如何预防和应对.mallab勒索病毒攻击

导言&#xff1a; 我们的数据成了我们的珍宝&#xff0c;但也成了黑客们追逐的目标。其中&#xff0c;.mallab勒索病毒就是一个充满阴谋和神秘的数字威胁&#xff0c;它采用高度复杂的方法将您的数据锁在数字牢笼中。本文91数据恢复将深入探讨.mallab勒索病毒的起源、工作方式…

【RabbitMQ 实战】12 镜像队列

一、镜像队列的概念 RabbitMQ的镜像队列是将消息副本存储在一组节点上&#xff0c;以提高可用性和可靠性。镜像队列将队列中的消息复制到一个或多个其他节点上&#xff0c;并使这些节点上的队列保持同步。当一个节点失败时&#xff0c;其他节点上的队列不受影响&#xff0c;因…

视频转换器WinX HD Video Converter mac中文特点介绍

WinX HD Video Converter mac是一款功能强大的视频转换器&#xff0c;它可以将各种不同格式的视频文件转换为其他视频格式&#xff0c;以便用户在各种设备上进行播放。WinX HD Video Converter是一个功能强大、易于使用的视频转换器&#xff0c;适用于各种类型的用户&#xff0…

可图性判断(图论)

如图所示&#xff1a; 1.去arr[i]首元素&#xff0c; 后面arr[i]个元素减一 2.排序&#xff0c;以此类推 3.最后如果出现负数则不可图 4.最后元素为0&#xff0c;则可图 问题 L: Degree Sequence of Graph G代码如下&#xff1a;

C#版字节跳动SDK - SKIT.FlurlHttpClient.ByteDance

前言 在我们日常开发工作中对接第三方开放平台&#xff0c;找一款封装完善且全面的SDK能够大大的简化我们的开发难度和提高工作效率。今天给大家推荐一款C#开源、功能完善的字节跳动SDK&#xff1a;SKIT.FlurlHttpClient.ByteDance。 项目官方介绍 可能是全网唯一的 C# 版字节…

基于nodejs+vue全国公考岗位及报考人数分析

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

iOS的应用生命周期

在iOS的原生开发中&#xff0c;我们需要特别关注两个东西&#xff1a;AppDelegate和ViewController。我们主要的编码工作就是在AppDelegate和ViewControlle这两个类中进行的。它们的类图如下图所示&#xff1a; AppDelegate是应用程序委托对象&#xff0c;它继承了UIResponder类…

抖音上怎么挂小程序?制作小程序挂载抖音视频

公司企业商家现在已经把抖音作为营销的渠道之一&#xff0c;目前抖音支持短视频挂载小程序&#xff0c;可方便做营销。以下给大家分享这一操作流程。 一、申请自主挂载能力 首先需要在抖音开放平台官网注册一个抖音小程序账号&#xff0c;然后申请短视频自主挂载能力。 二、搭…

Kubernetes技术与架构-存储 2

在Kubernetes集群中&#xff0c;一块持久化存储空间是可以被回收再利用&#xff0c;简称PV&#xff0c;即PersistentVolume&#xff0c;Pod实例需要使用PV的时候&#xff0c;可以使用PVC定义申请PV存储资源&#xff0c;PVC是PersistentVolumeClaim的简称&#xff0c;PV的申请分…

Git(SourceTree)变基操作使用

文章目录 一、变基的使用场景二、Source Tree上的变基操作1. 准备两个分支dev1和master2. 切换到dev1中&#xff0c;并选中master中提交的代码3. 鼠标右键&#xff0c;选择变基&#xff0c;弹出对话框选择确定。 变基就是rebase操作 一、变基的使用场景 假设分支a和分支b 在分…

Day12力扣打卡

打卡记录 找出满足差值条件的下标 II&#xff08;双指针维护最大最小&#xff09; 链接 采用双指针保留间隔 indexDifference 进行遍历&#xff0c;求出慢指针对应一路遍历过来的最大值和最小值。 class Solution { public:vector<int> findIndices(vector<int>…

【排序】js简单实现前端数组排序,多字段数组对象排序,字符串排序,数字排序等

数组对象排序&#xff08;多字段排序&#xff09; 排序前&#xff1a; 排序后&#xff1a; data() {return {list: [{ks: 外科,child_ks: 泌尿外科,xz: 外科一组,doctor: 小明,num: 18,num2: 19,num3: 20},{ks: 中医科,child_ks: 中医男科,xz: 外科一组,doctor: 小红,num: …

当生成式AI遇到业务流程管理,大语言模型正在变革BPM

生成式AI对各领域有很大影响&#xff0c;一个方面在于它改变了很多固有业务的工作流。 工作流&#xff08;Workflow&#xff09;是业务流程的一种实现方式&#xff0c;一个业务流程往往包含多个工作流范式以及相关的数据、组织和系统。 因此&#xff0c;提及工作流必然离不开业…

(el-Table)操作(不使用 ts):Element-plus 中 Table 多选框的样式等的调整

Ⅰ、Element-plus 提供的 Table 表格组件与想要目标情况的对比&#xff1a; 1、Element-plus 提供 Table 组件情况&#xff1a; 其一、Element-ui 自提供的 Table 代码情况为(示例的代码)&#xff1a; // Element-plus 自提供的代码&#xff1a; // 此时是使用了 ts 语言环境…

Java练习题2020-2

"统计1到N的整数中,除了1和自身之外&#xff0c;至少还能被两个数整除的数的个数 输入说明&#xff1a;整数 N(N<10000)&#xff1b; 输出说明&#xff1a;符合条件的数的个数 输入样例&#xff1a;10 输出样例&#xff1a;3 (说明&#xff1a;样例中符合条件的3个数是…

一个比较特别的串口工具

这是08年写的一个 并网带电池逆变器 的通讯工具&#xff0c;和普通的串口调试器相比&#xff0c;多了一个【脚本】功能。能够通过【脚本】完成通讯测试。 PC发给DSP的01命令 01 10 1B 00 CF A3 00 00 90 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 电…

开源移动核心网Magma架构设计启示

Magma是由Meta Connectivity开发并开源给Linux基金会的开源移动核心网项目&#xff0c;目的是构建灵活、低成本、可扩展的移动核心网。本文介绍了Magma架构设计的基本理念和取舍。原文: Implications of the Magma Architecture: Interoperability, Scale and Resilience 简介 …

Steger算法实现结构光光条中心提取(python版本)

Steger算法原理 对结构光进行光条中心提取时,Steger算法是以Hessian矩阵为基础的。它的基础步骤如下所示: 从Hessian矩阵中求出线激光条纹的法线方向在光条纹法线方向上将其灰度分布按照泰勒多项式展开,求取的极大值即为光条在该法线方向上的亚像素坐标。对于二维离散图像来…