深入理解位图(Bit - set):概念、实现与应用
目录
引言
一、位图概念
(一)基本原理
(二)适用场景
二、位图的实现(C++ 代码示例)
三、位图应用
1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记
四、总结
引言
在处理海量数据时,如何高效地存储和查询数据是一个关键问题。位图(Bit - set)作为一种非常巧妙的数据结构,在很多场景下能够极大地提升数据处理的效率。今天,我们就来深入探讨一下位图的相关知识。
一、位图概念
(一)基本原理
位图,简单来说,就是用每一位来存放某种状态。它特别适用于海量数据且数据无重复的场景,通常用于判断某个数据是否存在。比如,给定40亿个不重复的无符号整数,要快速判断一个无符号整数是否在这40亿个数中。数据是否存在刚好是两种状态(存在或不存在),此时就可以使用一个二进制比特位来代表数据是否存在的信息。如果二进制比特位为1,代表存在;为0,则代表不存在。
假设我们有一个整数数组 int array[] = {1,3,7,4,12,16,19,13,22,18}; ,我们可以按照一定规则将这些数映射到位图中。例如,将整数的范围划分为若干段,像0 - 7、8 - 15、16 - 23等,每个整数对应位图中的一个比特位。
(二)适用场景
1. 快速查找:在海量数据集中快速判断某个数据是否存在,相比传统的遍历查找(时间复杂度O(N) )或者排序后二分查找(时间复杂度O(logN) ),位图在某些情况下可以实现更高效的查找。
2. 数据去重与统计:在统计一些不重复元素的相关信息时,位图可以帮助我们快速标记已经出现过的元素,方便进行去重和统计数量等操作。
二、位图的实现(C++ 代码示例)
下面是一个简单的位图类 bitset 的C++ 实现代码:
#pragma once
#include<vector>namespace ldg
{template<size_t N>class bitset{public:bitset(){_a.resize(N / 32 + 1);}// x映射的那个标记成1void set(size_t x){size_t i = x / 32;size_t j = x % 32;_a[i] |= (1 << j);}// x映射的那个标记成0void reset(size_t x){size_t i = x / 32;size_t j = x % 32;_a[i] &= (~(1 << j));}bool test(size_t x){size_t i = x / 32;size_t j = x % 32;return _a[i] & (1 << j);}private:vector<int> _a;};template<size_t N>class twobitset{public:void set(size_t x){// 00 -> 01if (!_bs1.test(x) && !_bs2.test(x)){_bs2.set(x);} // 01 -> 10else if (!_bs1.test(x) && _bs2.test(x)){_bs1.set(x);_bs2.reset(x);}// 本身10代表出现2次及以上,就不变了}bool is_once(size_t x){return !_bs1.test(x) && _bs2.test(x);}private:bitset<N> _bs1;bitset<N> _bs2;};
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<unordered_set>
#include<unordered_map>
#include<set>
using namespace std;#include"BitSet.h"//int main()
//{
// bit::bitset<1000> bs;
// bs.set(1);
// bs.set(10);
// bs.set(100);
//
// cout << bs.test(1) << endl;
// cout << bs.test(10) << endl;
// cout << bs.test(100) << endl;
// cout << bs.test(999) << endl<<endl;
//
// bs.set(999);
// bs.reset(10);
//
// cout << bs.test(1) << endl;
// cout << bs.test(10) << endl;
// cout << bs.test(100) << endl;
// cout << bs.test(999) << endl << endl;
//
// getchar();
//
// bit::bitset<-1> bs1;
// //bit::bitset<0xffffffff> bs2;
//
// getchar();
//
// return 0;
//}//int main()
//{
// int a[] = { 1,2,3,3,4,4,4,4,4,2,3,6,3,1,5,5,8,9};
// bit::twobitset<10> tbs;
// for (auto e : a)
// {
// tbs.set(e);
// }
//
// for (auto e : a)
// {
// if (tbs.is_once(e))
// {
// cout << e << " ";
// }
// }
// cout << endl;
//}int main()
{int a1[] = { 1,2,3,3,4,4,4,4,4,2,3,6,3,1,5,5,8,9 };int a2[] = { 8,4,8,4,1,1,1,1 };ldg::bitset<10> bs1;ldg::bitset<10> bs2;// 去重for (auto e : a1){bs1.set(e);}// 去重for (auto e : a2){bs2.set(e);}for (int i = 0; i < 10; i++){if (bs1.test(i) && bs2.test(i)){cout << i << " ";}}cout << endl;
}
代码解释
1. 构造函数: bitset(size_t bitCount) 根据传入的比特位总数 bitCount 来初始化位图。通过 (bitCount >> 5) + 1 计算出需要的 int 类型数组的大小,因为一个 int 有32位,右移5位(相当于除以32),并加1是为了保证能容纳所有比特位。
2. set 函数:将指定的比特位 which 置为1。先判断 which 是否越界,然后通过位运算计算出该比特位在 _bit 数组中的索引 index 和在该 int 中的位置 pos ,最后使用 |= 操作将对应位置置1。
3. reset 函数:将指定的比特位 which 置为0。同样先判断越界,计算索引和位置后,使用 &= 和按位取反操作将对应位置清零。
4. test 函数:检测指定比特位 which 是否为1。判断越界后,计算索引和位置,通过与运算判断对应位置是否为1。
5. size 函数:返回位图中比特位的总个数。
6. Count 函数:统计位图中为1的比特位的个数。通过一个预先定义好的 bitCnttable 数组来快速统计每个字节中1的个数,遍历 _bit 数组进行累加。
三、位图应用
1. 快速查找某个数据是否在一个集合中
看对应bit位是1是0
#include <vector>
#include <iostream>class bitset {
public:bitset(size_t bitCount) : _bit((bitCount >> 5) + 1), _bitCount(bitCount) {}void set(size_t which) {if (which > _bitCount) return;size_t index = (which >> 5);size_t pos = which % 32;_bit[index] |= (1 << pos);}bool test(size_t which) const {if (which > _bitCount) return false;size_t index = (which >> 5);size_t pos = which % 32;return _bit[index] & (1 << pos);}private:std::vector<int> _bit;size_t _bitCount;
};int main() {// 假设集合元素的范围最大到100bitset setBits(100);// 向集合中添加元素int elements[] = {10, 20, 30, 40};for (int element : elements) {setBits.set(element);}// 查找元素int target = 30;if (setBits.test(target)) {std::cout << target << " is in the set." << std::endl;} else {std::cout << target << " is not in the set." << std::endl;}return 0;
}
解释:先创建一个 bitset 对象,指定其大小涵盖集合元素可能的范围。通过 set 函数将集合中的元素对应比特位置1 ,之后使用 test 函数传入要查找的元素,根据返回值判断元素是否在集合中。
2. 排序 + 去重
#include <vector>
#include <iostream>
#include <algorithm>class bitset {
public:bitset(size_t bitCount) : _bit((bitCount >> 5) + 1), _bitCount(bitCount) {}void set(size_t which) {if (which > _bitCount) return;size_t index = (which >> 5);size_t pos = which % 32;_bit[index] |= (1 << pos);}bool test(size_t which) const {if (which > _bitCount) return false;size_t index = (which >> 5);size_t pos = which % 32;return _bit[index] & (1 << pos);}private:std::vector<int> _bit;size_t _bitCount;
};int main() {// 假设集合元素的范围最大到100bitset setBits(100);// 原始数组,包含重复元素int arr[] = {10, 20, 10, 30, 20, 40};for (int element : arr) {setBits.set(element);}std::vector<int> sortedUnique;for (size_t i = 0; i < setBits.size(); ++i) {if (setBits.test(i)) {sortedUnique.push_back(i);}}std::sort(sortedUnique.begin(), sortedUnique.end());for (int num : sortedUnique) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
解释:利用 bitset ,将数组元素对应比特位置1 ,实现去重。之后遍历 bitset ,将为1的比特位对应的整数添加到新的 vector 中,最后对这个 vector 进行排序,得到去重且有序的序列。
3. 求两个集合的交集、并集等
#include <vector>
#include <iostream>class bitset {
public:bitset(size_t bitCount) : _bit((bitCount >> 5) + 1), _bitCount(bitCount) {}void set(size_t which) {if (which > _bitCount) return;size_t index = (which >> 5);size_t pos = which % 32;_bit[index] |= (1 << pos);}bool test(size_t which) const {if (which > _bitCount) return false;size_t index = (which >> 5);size_t pos = which % 32;return _bit[index] & (1 << pos);}bitset intersection(const bitset& other) const {if (_bitCount!= other._bitCount) {std::cerr << "Bitsets must have the same size for intersection." << std::endl;return bitset(0);}bitset result(_bitCount);for (size_t i = 0; i < _bit.size(); ++i) {result._bit[i] = _bit[i] & other._bit[i];}return result;}bitset unionSet(const bitset& other) const {if (_bitCount!= other._bitCount) {std::cerr << "Bitsets must have the same size for union." << std::endl;return bitset(0);}bitset result(_bitCount);for (size_t i = 0; i < _bit.size(); ++i) {result._bit[i] = _bit[i] | other._bit[i];}return result;}private:std::vector<int> _bit;size_t _bitCount;
};int main() {// 假设集合元素的范围最大到100bitset setBits1(100);bitset setBits2(100);int elements1[] = {10, 20, 30};int elements2[] = {20, 30, 40};for (int element : elements1) {setBits1.set(element);}for (int element : elements2) {setBits2.set(element);}bitset intersectionResult = setBits1.intersection(setBits2);bitset unionResult = setBits1.unionSet(setBits2);std::cout << "Intersection: ";for (size_t i = 0; i < intersectionResult.size(); ++i) {if (intersectionResult.test(i)) {std::cout << i << " ";}}std::cout << std::endl;std::cout << "Union: ";for (size_t i = 0; i < unionResult.size(); ++i) {if (unionResult.test(i)) {std::cout << i << " ";}}std::cout << std::endl;return 0;
}
解释:先分别创建表示两个集合的 bitset 对象,将集合元素对应比特位置1 。通过 intersection 函数实现按位与操作得到交集位图,通过 unionSet 函数实现按位或操作得到并集位图,最后遍历位图输出交集和并集中的元素。
4. 操作系统中磁盘块标记
#include <vector>
#include <iostream>class DiskBlockBitset {
public:DiskBlockBitset(size_t blockCount) : _bit((blockCount >> 5) + 1), _blockCount(blockCount) {}void markUsed(size_t blockIndex) {if (blockIndex > _blockCount) return;size_t index = (blockIndex >> 5);size_t pos = blockIndex % 32;_bit[index] |= (1 << pos);}void markFree(size_t blockIndex) {if (blockIndex > _blockCount) return;size_t index = (blockIndex >> 5);size_t pos = blockIndex % 32;_bit[index] &= ~(1 << pos);}bool isBlockUsed(size_t blockIndex) const {if (blockIndex > _blockCount) return false;size_t index = (blockIndex >> 5);size_t pos = blockIndex % 32;return _bit[index] & (1 << pos);}private:std::vector<int> _bit;size_t _blockCount;
};int main() {// 假设磁盘块总数为100DiskBlockBitset diskBlocks(100);// 标记一些磁盘块为已使用int usedBlocks[] = {10, 20, 30};for (int block : usedBlocks) {diskBlocks.markUsed(block);}// 检查某个磁盘块状态int checkBlock = 20;if (diskBlocks.isBlockUsed(checkBlock)) {std::cout << "Disk block " << checkBlock << " is used." << std::endl;} else {std::cout << "Disk block " << checkBlock << " is free." << std::endl;}// 释放一个磁盘块diskBlocks.markFree(20);if (diskBlocks.isBlockUsed(checkBlock)) {std::cout << "Disk block " << checkBlock << " is still used." << std::endl;} else {std::cout << "Disk block " << checkBlock << " is now free." << std::endl;}return 0;
}
解释:定义 DiskBlockBitset 类模拟操作系统中磁盘块标记。通过 markUsed 函数将指定磁盘块对应比特位置1表示已使用, markFree 函数将对应比特位置0表示空闲, isBlockUsed 函数用于检测磁盘块是否被使用。
四、总结
位图作为一种高效的数据结构,在处理海量数据的存在性判断、数据统计等方面有着广泛的应用。通过合理利用位图的特性,我们可以在很多场景下优化程序的性能,减少时间和空间复杂度。希望通过这篇博客,大家能对位图有更深入的理解,并在实际编程中灵活运用。
相关文章:

深入理解位图(Bit - set):概念、实现与应用
目录 引言 一、位图概念 (一)基本原理 (二)适用场景 二、位图的实现(C 代码示例) 三、位图应用 1. 快速查找某个数据是否在一个集合中 2. 排序 去重 3. 求两个集合的交集、并集等 4. 操作系…...

猫番阅读APP:丰富资源,优质体验,满足你的阅读需求
猫番阅读APP是一款专为书籍爱好者设计的移动阅读应用,致力于提供丰富的阅读体验和多样化的书籍资源。它不仅涵盖了小说、非虚构、杂志等多个领域的电子书,还提供了个性化推荐、书架管理、离线下载等功能,满足不同读者的阅读需求。无论是通勤路…...
Java文件读写程序
1.引言 在日常的软件开发中,文件操作是常见的功能之一。不仅要了解如何读写文件,更要知道如何安全地操作文件以避免程序崩溃或数据丢失。这篇文章将深入分析一个简单的 Java 文件读写程序 Top.java,包括其基本实现、潜在问题以及改进建议&am…...
深入解析Java事件监听机制与应用
Java事件监听机制详解 一、事件监听模型组成 事件源(Event Source) 产生事件的对象(如按钮、文本框等组件) 事件对象(Event Object) 封装事件信息的对象(如ActionEvent包含事件源信息…...

MetaMask安装及使用-使用水龙头获取测试币的坑?
常见的异常有: 1.unable to request drip, please try again later. 2.You must hold at least 1 LINK on Ethereum Mainnet to request native tokens. 3.The address provided does not have sufficient historical activity or balance on the Ethereum Mainne…...

AI:OpenAI论坛分享—《AI重塑未来:技术、经济与战略》
AI:OpenAI论坛分享—《AI重塑未来:技术、经济与战略》 导读:2025年4月24日,OpenAI论坛全面探讨了 AI 的发展趋势、技术范式、地缘政治影响以及对经济和社会的广泛影响。强调了 AI 的通用性、可扩展性和高级推理能力,以…...

Linux配置vimplus
配置vimplus CentOS的配置方案很简单,但是Ubuntu的解决方案网上也很多但是有效的很少,尤其是22和24的解决方案,在此我整理了一下我遇到的问题解决方法 CentOS7 一键配置VimForCPP 基本上不会有什么特别难解决的报错 sudo yum install vims…...

服务端HttpServletRequest、HttpServletResponse、HttpSession
一、概述 在JavaWeb 开发中,获取客户端传递的参数至关重要。http请求是客户端向服务端发起数据传输协议,主要包含包含请求行、请求头、空行和请求体四个部分,在这四部分中分别携带客户端传递到服务端的数据。常见的http请求方式有get、post、…...

实验九视图索引
设计性实验 1. 创建视图V_A包括学号,姓名,性别,课程号,课程名、成绩; 一个语句把学号103 课程号3-105 的姓名改为陆君茹1,性别为女 ,然后查看学生表的信息变化,再把上述数据改为原…...

git 本地提交后修改注释
dos命令行进入目录,idea可以点击Terminal 进入命令行 git commit --amend -m "修改内容"...

面向具身智能的视觉-语言-动作模型(VLA)综述
具身智能被广泛认为是通用人工智能(AGI)的关键要素,因为它涉及控制具身智能体在物理世界中执行任务。在大语言模型和视觉语言模型成功的基础上,一种新的多模态模型——视觉语言动作模型(VLA)已经出现&#…...
Thrust库中的Gather和Scatter操作
Thrust库中的Gather和Scatter操作 Thrust是CUDA提供的一个类似于C STL的并行算法库,其中包含两个重要的数据操作:gather(聚集)和scatter(散开)。 Gather操作 Gather操作从一个源数组中按照指定的索引收集元素到目标数组中。 函数原型: t…...

计算机发展的历程
计算机系统的概述 一, 计算机系统的定义 计算机系统的概念 计算机系统 硬件 软件 硬件的概念 计算机的实体, 如主机, 外设等 计算机系统的物理基础 决定了计算机系统的天花板瓶颈 软件的概念 由具有各类特殊功能的程序组成 决定了把硬件的性能发挥到什么程度 软件的分类…...

深度学习驱动下的目标检测技术:原理、算法与应用创新(三)
五、基于深度学习的目标检测代码实现 5.1 开发环境搭建 开发基于深度学习的目标检测项目,首先需要搭建合适的开发环境,确保所需的工具和库能够正常运行。以下将详细介绍 Python、PyTorch 等关键开发工具和库的安装与配置过程。 Python 是一种广泛应用于…...
Python爬虫实战:研究 RPC 远程调用机制,实现逆向解密
1. 引言 在网络爬虫技术的实际应用中,目标网站通常采用各种加密手段保护其数据传输和业务逻辑。这些加密机制给爬虫开发带来了巨大挑战,传统的爬虫技术往往难以应对复杂的加密算法。逆向解密作为一种应对策略,旨在通过分析和破解目标网站的加密机制,获取原始数据。 然而,…...
[学习] RTKLib详解:qzslex.c、rcvraw.c与solution.c
RTKLib详解:qzslex.c、rcvraw.c与solution.c 本文是 RTKLlib详解 系列文章的一篇,目前该系列文章还在持续总结写作中,以发表的如下,有兴趣的可以翻阅。 [学习] RTKlib详解:功能、工具与源码结构解析 [学习]RTKLib详解…...

jenkins流水线常规配置教程!
Jenkins流水线是在工作中实现CI/CD常用的工具。以下是一些我在工作和学习中总结出来常用的一些流水线配置:变量需要加双引号括起来 "${main}" 一 引用无账号的凭据 使用变量方式引用,这种方式只适合只由密码,没有用户名的凭证。例…...
Java中序列化和反序列化的理解
基本概念 序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,而反序列化(Deserialization)则是将这种形式重新转换为对象的过程。 核心作用 持久化存储:将对象状态保存到文件或数据库中 网络传输:在网络间传递对象…...

基于OpenCV的SIFT特征和FLANN匹配器的指纹认证
文章目录 引言一、概述二、代码解析1. 图像显示函数2. 核心认证函数2.1 创建SIFT特征提取器2.2 检测关键点和计算描述符(源图像)2.3 检测关键点和计算描述符(模板图像)2.4 创建FLANN匹配器2.5 使用K近邻匹配 3. 匹配点筛选4. 认证…...
零基础学Java——第十一章:实战项目 - 桌面应用开发(JavaFX入门)
第十一章:实战项目 - 桌面应用开发(JavaFX入门) 欢迎来到我们实战项目的桌面应用开发部分!在前面的章节中,我们可能已经接触了Swing。现在,我们将目光投向JavaFX,一个更现代、功能更丰富的用于…...
Milvus 视角看主流嵌入式模型(Embeddings)
嵌入是一种机器学习概念,用于将数据映射到高维空间,其中语义相似的数据被紧密排列在一起。嵌入模型通常是 BERT 或其他 Transformer 系列的深度神经网络,它能够有效地用一系列数字(称为向量)来表示文本、图像和其他数据…...

leetcode:58. 最后一个单词的长度(python3解法)
难度:简单 给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1: 输入:s "Hello World"…...

虹科应用 | 探索PCAN卡与医疗机器人的革命性结合
随着医疗技术的不断进步,医疗机器人在提高手术精度、减少感染风险以及提升患者护理质量方面发挥着越来越重要的作用。医疗机器人的精确操作依赖于稳定且高效的数据通信系统,虹科提供的PCAN四通道mini PCIe转CAN FD卡,正是为了满足这一需求而设…...

entity线段材质设置
在cesium中,我们可以改变其entity线段材质,这里以直线为例. 首先我们先创建一条直线 const redLine viewer.entities.add({polyline: {positions: Cesium.Cartesian3.fromDegreesArray([-75,35,-125,35,]),width: 5,material:material, 保存后可看到在地图上创建了一条线段…...

[STM32] 5-1 时钟树(上)
文章目录 前言5-1 时钟树(上)时钟树的基本介绍时钟树的基本结构大树和小树频率运算简介计数器和分频STM32内部结构树的结构于关键节点SYSCLK(System Clock) 系统时钟 72M maxHCLK(AHB Clock) AHB时钟 36M maxPLCK(APB1 Clock) APB1时钟 36M maxPLCK2(APB…...

【Linux网络与网络编程】12.NAT技术内网穿透代理服务
1. NAT技术 之前我们说到过 IPv4 协议中IP 地址数量不充足的问题可以使用 NAT 技术来解决。还提到过本地主机向公网中的一个服务器发起了一个网络请求,服务器是怎么将应答返回到该本地主机呢?(如何进行内网转发?) 这就…...
【HTTPS基础概念与原理】TLS握手过程详解
以下是 TLS握手过程的详细拆解,涵盖客户端与服务器之间的关键交互步骤,包括ClientHello、ServerHello、证书验证、密钥交换等核心阶段,并对比TLS 1.2与TLS 1.3的差异: 一、TLS握手的核心目标 协商协议版本:确定双方支…...

从辅助到协作:GitHub Copilot的进化之路
如果说现代程序员的标配工具除了VS Code、Stack Overflow之外,还有谁能入选,那一定是GitHub Copilot。从2021年首次亮相,到如今深度集成进开发者日常流程,这个“AI编程助手”已经不只是写几行自动补全代码的小帮手了,而…...

Linux运行时的参数、命令、网络、磁盘参数和日志监控
一、监控 1. free 功能:用于查看系统内存使用情况,包括物理内存总量、已用内存、空闲内存、缓冲区(buffer)和缓存(cache)占用,以及交换内存(swap)的使用与剩余情况。常…...

鸿蒙页面布局入门
本文以仿猫眼电影M站首页布局为案例,展示ArkUI在实际开发中的应用。内容包括案例效果及相关知识点,深入解析布局框架以及头部、脚部、内容区域的构建思路与代码实现,最后提供完整代码和教程资源,助力你强化实践能力。 1. 案例效果…...