(使用C语言详解)求一个集合的全部子集(leetcode编程笔记)

原题链接:子集 (Subsets) - 力扣 (LeetCode)

原码于文章末尾会给出。

本文通过位运算,实现题目要求,之后可能更新其他方法,敬请关注......

题目:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 :

输入:{1,3,6,9}
输出:[] [1] [3] [1,3] [6] [1,6] [3,6] [1,3,6] [9] [1,9] [3,9] [1,3,9] [6,9] [1,6,9] [3,6,9] [1,3,6,9]

具体步骤如下:

  1. 首先,我们需要了解什么是集合的子集。集合的子集是指原集合中的元素可以任意个数(包括0个或全部)挑选出来组成的集合。

  2. 对于一个有n个元素的集合,它的子集数量为2^n个。因此,我们需要遍历从0到2^n-1的所有数字,每个数字代表一个子集。

  3. 利用位运算,我们可以很容易地得到一个数字表示的子集。例如,数字1的二进制表示为0001,它表示集合中的第一个元素;数字3的二进制表示为0011,它表示集合中的第一个和第三个元素。

  4. 在遍历所有数字后,我们可以得到所有的子集。每个子集都是一个长度为n的数字序列,表示集合中的元素是否包含在当前子集中。

  5. 最后,我们将所有子集输出即可。
    ++这里比较难的点在于,如何通过位运算得到我们想要的结果。++

下面先将我们代码实现功能的主要函数列出来:

cpp 复制代码
void GetSubSet(int* num, int len, int tag) {
    int tmp;

    for (int i = 0; i < pow(2, len); i++) {
        tmp = tag;
        tag += 1;

        int is_first = 1;
        printf("[");
        for (int j = 0; j < len; j++) {
            if (tmp & 0x1) {
                if (is_first) {
                    printf("%d", num[j]);
                    is_first--;
                }
                else {
                    printf(",%d", num[j]);
                }
            }
            tmp >>= 1;
        }
        printf("] ");
    }
}
  • 函数GetSubSet接受三个参数
  1. int* num:指向整数数组的指针,该数组包含了要生成子集的元素。
  2. int len:整数数组的长度,即数组中元素的个数。
  3. int tag:用于跟踪当前生成子集的标记,初始时它被设置为0。
  • 初始化

函数开始时,tag被设置为0,表示空集。

  • 循环遍历所有子集:

使用一个for循环,循环2^len次,每次循环代表一个子集。


重点来了

  • 位运算:

在每次循环中,使用tmp变量来暂存tag的值,然后通过tag += 1来计算下一个子集的tag

这里实际上是在对tag进行二进制位操作,每次循环将tag的最低位设置为1,

然后通过tmp >>= 1tag右移一位。


关于位运算这里可能理解比较困难,下面我将画图讲解。

刚开始的时候我们设置了输入集合的元素总个数n,这个n会在**GetSubSet函数** 位运算操作中生成n个二进制位。比如我们设置的n=4,那么我们下面就产生4个二进制位。

在遍历第一层第一次for循环时,临时变量tmp=0,即4位二进制都为0

|---|---|---|---|
| 0 | 0 | 0 | 0 |

在遍历第一层第二次for循环时,临时变量tmp=1,即4位二进制变为0001

|---|---|---|---|
| 0 | 0 | 0 | 1 |

以此类推......

此时符合条件tmp & 0x1(意思是:用于检查变量 tmp 的最低位(二进制的第0位)是否为1。)

is_first用于判断是否是第一次进入到这个for循环。

在遍历第一层第二次for循环时,临时变量tmp=1,即4位二进制变为0001,相对应的num[j]=子集元素中的第一个元素------1。

每执行完一次for循环,tmp >>= 1,也就是说tmp=0001会向右移动一位,变为000。

以上就是位运算的原理。


  • 打印子集 :在循环内部,使用另一个for循环来遍历数组num中的每个元素。如果当前tag的二进制表示中的第j位是1,说明元素num[j]应该被包含在子集中。如果这是子集列表中的第一个元素,则直接打印,否则打印逗号和元素。
  • 递归调用:虽然这段代码中没有递归调用,但这个思想可以用来生成子集树,每个子集都可以作为下一个子集的父集。

这个算法的关键在于理解如何使用二进制数来表示和生成子集。每个子集都可以通过改变tag的某一位来得到下一个子集。当tag的某一位被设置为1时,表示对应数组元素被包含在子集中;当该位被设置为0时,表示对应元素不被包含。通过这种方式,我们可以遍历所有可能的子集。

实现代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

#define MAX_SIZE 100

void GetSubSet(int* num, int len, int tag);

int main() {
    int len;
    int num[MAX_SIZE];
    
    while (scanf("%d", &len) && len != 0) {
        for (int i = 0; i < len; i++) {
            scanf("%d", num + i);
        }

        GetSubSet(num, len, 0);
        printf("\n");
    }

    return 0;
}

void GetSubSet(int* num, int len, int tag) {
    int tmp;

    for (int i = 0; i < pow(2, len); i++) {
        tmp = tag;
        tag += 1;

        int is_first = 1;
        printf("[");
        for (int j = 0; j < len; j++) {
            if (tmp & 0x1) {
                if (is_first) {
                    printf("%d", num[j]);
                    is_first--;
                }
                else {
                    printf(",%d", num[j]);
                }
            }
            tmp >>= 1;
        }
        printf("] ");
    }
}
相关推荐
C++忠实粉丝1 分钟前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
古月居GYH13 分钟前
在C++上实现反射用法
java·开发语言·c++
Betty’s Sweet15 分钟前
[C++]:IO流
c++·文件·fstream·sstream·iostream
祁思妙想20 分钟前
10.《滑动窗口篇》---②长度最小的子数组(中等)
leetcode·哈希算法
敲上瘾29 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
福大大架构师每日一题31 分钟前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言
不会写代码的ys35 分钟前
【类与对象】--对象之舞,类之华章,共绘C++之美
c++
兵哥工控38 分钟前
MFC工控项目实例三十二模拟量校正值添加修改删除
c++·mfc
EterNity_TiMe_1 小时前
【论文复现】(CLIP)文本也能和图像配对
python·学习·算法·性能优化·数据分析·clip
长弓聊编程1 小时前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++