(使用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("] ");
    }
}
相关推荐
浊酒南街几秒前
决策树(理论知识1)
算法·决策树·机器学习
雨中rain6 分钟前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程8 分钟前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
oneouto8 分钟前
selenium学习笔记(二)
笔记·学习·selenium
学术头条13 分钟前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
sealaugh3213 分钟前
aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
笔记·学习·aws
炭烤玛卡巴卡40 分钟前
学习postman工具使用
学习·测试工具·postman
Schwertlilien1 小时前
图像处理-Ch4-频率域处理
算法
北国无红豆1 小时前
【CAN总线】STM32的CAN外设
c语言·stm32·嵌入式硬件
IT猿手1 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解TP1-TP10及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·深度学习·算法·机器学习·matlab·多目标算法