深入探索位图技术:原理及应用

news2024/11/25 16:49:36

文章目录

  • 一、引言
  • 二、位图(Bitset)基础知识
    • 1、位图的概念
    • 2、位图的表示
    • 3、位图操作
  • 三、位图的应用场景
    • 1、数据查找与存储
    • 2、数据去重与排序
  • 四、位图的实现

一、引言

位图,以其高效、简洁的特性在数据处理、存储和检索等多个领域发挥着举足轻重的作用。通过二进制位的操作,位图能够实现对大量数据的快速访问、修改和查询,从而在众多实际应用场景中展现出其独特的优势。

随着大数据时代的来临,数据量呈现爆炸式增长,如何高效地处理这些数据成为了一个亟待解决的问题。位图技术以其对空间的高效利用和快速的位操作能力,成为了解决这一问题的有力工具。无论是在数据库系统、缓存机制、网络安全,还是在图形处理、用户权限管理等领域,位图都展现出了其不可或缺的价值。


二、位图(Bitset)基础知识

1、位图的概念

  • 位图及其基本组成单元(位)

位图(Bitmap)是一种基于二进制位的数据结构,用于高效地存储、检索和管理数据。其基本组成单元是位(Bit),即二进制数中的一个数字,其值只能是0或1。位图通常用于表示一组数据或某种状态的存在与否,其中每个数据项或状态都对应于位图中的一个或多个位。

在C++中,std::bitset是标准库提供的一种数据结构,用于表示固定大小的位序列,即一个固定长度的二进制序列。它提供了一组方法来进行位操作,例如设置、清除、翻转和测试位等。

std::bitset的大小在编译时确定,因此它是一种静态大小的位集合。你可以使用std::bitset来表示和处理布尔值序列,每个位可以代表一个布尔值(true或false)。而且可以单独访问每个位位置:例如,对于名为foo的位图,表达式foo[3]访问其第四个位,就像常规数组访问其元素一样。

在这里插入图片描述

  • 区分位图与像素图的概念

虽然位图在图形处理中也常被提及,但在此上下文中,我们需要明确区分两种不同类型的“位图”:一种是数据结构中的位图,如上所述,它主要用于数据的高效存储和检索;另一种是图形学中的位图,也被称为像素图,它是由像素(Pixel)组成的图像,每个像素都有其特定的颜色和位置。这两种位图在原理和应用上有本质的区别。数据结构中的位图关注的是数据的二进制表示和存储效率,而图形学中的位图则关注的是图像的视觉呈现和细节表现。

2、位图的表示

  • 使用二进制表示位图

位图,从数据结构的角度来看,是一种利用二进制位来标记数据存在性的高效方式。在二进制位图中,每一个二进制位(0或1)代表一个数据元素的状态:存在或不存在。通常,我们使用一个整数数组来实现位图,数组中的每个元素(如int或long类型)都由多个二进制位组成。

在这里插入图片描述

例如,如果我们使用一个32位的整数(int)来表示位图,那么每个整数可以表示32个不同的数据元素。当我们要标记第n个数据元素是否存在时,我们只需要找到对应的整数(通过n除以32得到整数索引),然后在该整数中找到对应的二进制位(通过n对32取余得到位索引),并将其设置为0或1。

  • 位图中位的编址和访问方式

在位图中,位的编址是基于其所在的整数和整数内的位位置来确定的。具体来说,如果我们有一个整数数组bitmap[],那么第n个数据元素对应的位在bitmap[n/32]的第(n%32)位上。

访问位图中的位通常涉及两个步骤:首先找到对应的整数,然后找到该整数中的对应位。这可以通过位运算来实现。例如,要设置第n位为1,我们可以使用以下代码:

int index = n / 32;      			// 计算整数索引
int position = n % 32;   			// 计算位索引
bitmap[index] |= (1 << position); 	// 设置对应位为1

同样地,要读取第n位的值,我们可以使用以下代码:

int index = n / 32;      							// 计算整数索引
int position = n % 32;   							// 计算位索引
int bitValue = (bitmap[index] >> position) & 1;  	// 读取对应位的值

这里的|=&=是位运算中的赋值操作符,分别表示按位或后赋值和按位与后赋值。<<>>是位移操作符,分别表示左移和右移。通过这些位运算操作,我们可以高效地访问和修改位图中的任意一位。

3、位图操作

在使用std::bitset时,常见的位操作包括设置位(setting a bit)、清除位(clearing a bit)、检测位(testing a bit)、翻转位(flipping a bit)等。

  1. 设置位(Setting a Bit)

要将std::bitset中的特定位设置为1,可以使用下标操作符[]配合赋值操作。

std::bitset<8> b; // 创建一个8位的bitset,所有位初始化为0
b[3] = 1;         // 设置第4位为1(bitset从右向左计数,从0开始)
  1. 清除位(Clearing a Bit)

要将std::bitset中的特定位清除(设置为0),同样可以使用下标操作符[]配合赋值操作。

std::bitset<8> b(0b11111111); // 创建一个8位的bitset,所有位初始化为1
b[3] = 0;                     // 清除第4位(设置为0)
  1. 检测位(Testing a Bit)

要检测std::bitset中的特定位是否为1,可以使用下标操作符[],这将返回一个可以转换为bool的类型。

std::bitset<8> b(0b10101010); 	// 创建一个8位的bitset
if (b[3])                  		// 检测第4位是否为1
    std::cout << "Bit 3 is set." << std::endl;
else
    std::cout << "Bit 3 is not set." << std::endl;
cout << b << endl;
// Bit 3 is set.
//10101010
  1. 翻转位(Flipping a Bit)

要翻转std::bitset中的特定位(从0变为1,或从1变为0),可以使用成员函数flip()

std::bitset<8> b(0b10101010); 	// 创建一个8位的bitset
b.flip(3);                   	// 翻转第4位
  1. 其他操作

std::bitset还提供了其他有用的成员函数,如:

  • size():返回bitset的大小(位数)。
  • count():返回bitset中设置为1的位数。
  • any():如果bitset中至少有一个位设置为1,则返回true。
  • none():如果bitset中所有位都设置为0,则返回true。
  • all():如果bitset中所有位都设置为1,则返回true。
  • to_ulong()to_ullong():将bitset转换为无符号长整型或无符号长长整型(如果bitset的大小允许)。
  • to_string():将bitset转换为字符串表示形式。
std::bitset<8> b(0b10101010);
std::cout << "Size: " << b.size() << std::endl;       	// 输出bitset的大小
std::cout << "Count: " << b.count() << std::endl;      	// 输出设置为1的位数
std::cout << "Any: " << b.any() << std::endl;          	// 检查是否有位设置为1
std::cout << "None: " << b.none() << std::endl;        	// 检查是否所有位都设置为0
std::cout << "All: " << b.all() << std::endl;          	// 检查是否所有位都设置为1
std::cout << "Value: " << b.to_ulong() << std::endl;   	// 转换为无符号长整型并输出
std::cout << "String: " << b.to_string() << std::endl; 	// 转换为字符串并输出

在这里插入图片描述


三、位图的应用场景

1、数据查找与存储

  • 使用位图实现快速数据查找

位图可以用于快速查找某个元素是否存在。例如,如果有一个包含大量整数的集合,并且整数的范围已知(例如,从1到1亿),那么可以使用一个位图来表示这个集合。每个整数对应位图中的一个位,如果该整数在集合中,则对应的位被设置为1,否则为0。这样,查找某个整数是否存在只需要检查其对应的位即可,时间复杂度为O(1)。

  • 位图在数据存储中的优势

空间效率:相比于存储原始数据,位图可以显著节省存储空间。在上面的例子中,如果使用一个整型数组来存储集合中的元素,每个整数需要4个字节(假设是32位系统),那么存储1亿个整数需要大约400MB的空间。而使用位图,只需要大约12MB的空间(1亿个位,每个位1/8字节)。

  • 使用整型数组:如果每个整数占用4个字节(在32位系统中,一个int通常是32位,即4字节),那么存储1亿个整数将需要大约400MB的空间,计算如下:

    1亿个整数 * 4字节/整数 = 400,000,000字节  
    400,000,000字节 / 1024 / 1024 = 381.47MB (约等于400MB)
    
  • 使用位图:在位图中,每个位占用1/8字节(因为1字节 = 8位)。所以,存储1亿个位将需要大约12MB的空间,计算如下:

    1亿个位 * 1/8字节/位 = 12,500,000字节  
    12,500,000字节 / 1024 / 1024 = 11.92MB (约等于12MB)
    

若给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

步骤 1: 确定位图大小

首先,需要确定无符号整数的位数,这决定了位图的大小。假设我们处理的是32位无符号整数,那么整数的范围是从0到232 - 1。因此,你需要一个位图,其中包含232个位,每个位对应一个可能的整数。

步骤 2: 分配内存

接下来,就需要分配足够的内存来存储这个位图。由于每个位可以是0或1,可以使用位操作来紧凑地存储这些位。在实际应用中,通常会使用一个字节数组(byte array)或位数组(bit array)来表示位图。

对于一个包含232个位的位图,你需要大约512MB的内存(因为232位 / 8位/字节 = 229字节 = 512MB)。

步骤 3: 初始化位图

将位图中的所有位初始化为0,表示没有任何整数被标记为存在。

步骤 4: 填充位图

遍历40亿个无符号整数,对于每个整数,计算它在位图中的索引,并将该索引对应的位设置为1。索引的计算通常是通过整数本身直接转换得到的。例如,对于一个32位整数num,你可以直接将其用作索引(可能需要一些转换来适应字节数组的索引方式)。

步骤 5: 查询整数

当需要查询一个整数是否存在于集合中时,你只需计算该整数在位图中的索引,并检查该索引对应的位是否为1。如果是1,表示该整数存在于集合中;如果是0,表示不存在。

有了上面的思路方法,我们来解决如下问题:

一、给定100亿个整数,我们如何找到只出现一次的整数呢?

由于整数可能有正有负,我们先假设整数的范围是已知的,例如是32位有符号整数。使用一个标准的位图来记录每个整数出现的次数。但需要扩展位图的概念,使得每个整数对应多个位,用以计数。例如,可以使用2个位来表示一个整数出现的次数:00表示0次,01表示1次,10表示2次或更多。

📽 我们可以这样做:

初始化一个大小为2 * INT_MAX + 1的位图(考虑负数和0),每个整数对应2位。

遍历整数集合,对每个整数更新其对应的计数器。

  • 如果对应位是00,则设置为01。
  • 如果是01,则设置为10。
  • 如果是10,则保持不变(因为我们只关心出现1次的)。
    再次遍历位图,找出所有对应位为01的整数。

二、给两个文件,分别有100亿个整数,我们只有1G内存,如何找到?

首先,假设这些整数是32位无符号整数,那么理论上需要4GB的内存来存储一个完整的位图(因为232位 = 4GB)。由于只有1GB的内存可用,因此不能一次性加载所有整数到位图中。

因此我们借助哈希函数,分块处理。

  1. 准备阶段
    • 先设计一个哈希函数。这个函数的任务是,当我们给它一个整数时,它会告诉我们这个整数应该属于哪个“小团队”。想象一下,我们有1000个“小团队”,每个团队负责处理大约1000万个整数。
  2. 处理第一个文件
    • 我们开始遍历第一个文件,对里面的每个整数都用哈希函数“问问路”,看它们应该属于哪个“小团队”。
    • 比如,哈希函数告诉我们某个整数应该属于“团队500”,那我们就把这个整数记录到 “a500” 这个文件里。
    • 这样,我们就把第一个大文件拆分成了1000个小文件,每个文件大约40M,这样我们的1G内存就能轻松应对了。
  3. 处理第二个文件
    • 对第二个文件也如法炮制。还是使用相同的哈希函数,把整数分配到它们各自的“小团队”中。
    • 这次我们把结果记录在b1, b2, …, b1000这一系列文件中。
  4. 找交集
    • 有了这两组“小团队”,我们就可以开始找交集了。
    • 由于我们是用同样的哈希函数来分配整数的,所以,如果两个文件中有共同的整数,那么这些整数一定会出现在同样编号的“小团队”里。
    • 比如,a500和b500里就应该有相同的整数,我们只需要在这两个文件里找交集就行了。
    • 这样,我们就在每个“小团队”内部找交集,由于数据量小了很多,这个任务就变得容易多了。
  5. 汇总成果
    • 最后,我们把所有“小团队”找到的交集汇总起来,就是我们要找的答案了。

2、数据去重与排序

使用位图去除数据集中的重复元素。

基于位图的排序算法通常适用于数据值范围已知且较小的情况。其原理是利用位图来记录每个数据值的出现情况,并根据位图的顺序生成已排序的数组。具体步骤如下:

  1. 初始化位图:创建一个能够覆盖所有数据值的位图,并初始化为0。
  2. 遍历并设置位图:遍历原始数据,对于每个数据值,将其对应的位在位图中设置为1。
  3. 生成排序数组:从位图的最低位开始,依次检查每个位。如果某个位为1,则表示其对应的数据值存在,将其添加到排序数组中。继续这个过程,直到检查完位图的所有位。

四、位图的实现

template<size_t N>
class bitset {
public:
    bitset( ) {
        _bits.resize(N / 32 + 1, 0);
    }
    //	把 x 映射的位 标记成 1
    void set(size_t x) {
        assert(x < N);
        size_t i = x / 32;
        size_t j = x % 32;

        _bits[i] |= (1 << j);
    }
    //	把 x 映射的位 标记成 0
    void reset(size_t x) {
        assert(x < N);
        size_t i = x / 32;
        size_t j = x % 32;

        _bits[i] &= ~(1 << j);
    }

    bool test(size_t x) {
        size_t i = x / 32;
        size_t j = x % 32;
        return _bits[i] & (1 << j);
    }
private:
    vector<int> _bits;
};

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

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

相关文章

JJJ:linux系统中第一个进程

以linux4.19内核linux系统中第一个进程。 执行shell指令 ps -ef 结果如下&#xff1a; xxxxxx-virtual-machine:~$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 20:55 ? 00:00:02 /sbin/init splash root …

Postgresql导出数据和结构后再去另外一个Postgresql数据库中导入失败

参考教程&#xff1a; postgresql 在导入建表sql时 遇到错误 &#xff1a;https://blog.csdn.net/weixin_37706944/article/details/132321731 是因为原表定义了自增字段&#xff0c;解决办法&#xff1a; 解决方法&#xff1a; 执行如下sql后再新建表&#xff0c;就可以了 DR…

【docker】nexus 本地 maven配置

1、这篇文章中说明了如何搭建私服 【docker】搭建Nexus私服-CSDN博客文章浏览阅读2次。4、点击登陆&#xff08;账号&#xff1a;admin 秘密&#xff1a;在容器内 /nexus-data/admin.password 文件中)注意我的端口号是 10002&#xff0c;注意你的端口号。7、设置maven-central…

【数据分析面试】1. 计算年度收入百分比(SQL)

题目 你需要为公司的营收来源生成一份年度报告。计算截止目前为止&#xff0c;在表格中记录的第一年和最后一年所创造的总收入百分比。将百分比四舍五入到两位小数。 示例&#xff1a; 输入&#xff1a; annual_payments 表 列名类型amountINTEGERcreated_atDATETIMEstatusV…

COSMIC 方法 - 需求评估 映射阶段

通用软件模型 原则 - COSMIC 通用软件模型 a) 软件块跨越边界与功能用户交互、并与边界内的持久存储介质进行交互。 b) 被度量软件块的 FUR 能够被映射到唯一的一组功能处理。 c) 每个功能处理由一系列子处理组成 d) 一个子处理可以是一个数据移动或者是一个数据运算。 e) 有四…

[flink 实时流基础] 转换算子

flink学习笔记 数据源读入数据之后&#xff0c;我们就可以使用各种转换算子&#xff0c;将一个或多个DataStream转换为新的DataStream。 文章目录 基本转换算子&#xff08;map/ filter/ flatMap&#xff09;聚合算子&#xff08;Aggregation&#xff09;按键分区&#xff08;…

【Spring MVC】快速学习使用Spring MVC的注解及三层架构

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring MVC】快速学习使用Spring MVC的注解及三层架构 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 Spring Web MVC一: 什么是Spring Web MVC&#xff1…

成绩管理系统|基于springboot成绩管理系统的设计与实现(附项目源码+论文)

基于springboot成绩管理系统的设计与实现 一、摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业设计成绩管…

全局自定义指令实现图片懒加载,vue2通过js和vueuse的useintersectionObserver实现

整体逻辑&#xff1a; 1.使用全局自定义指令创建图片懒加载指令 2.在全局自定义指令中获取图片距离顶部的高度和整个视口的高度 3.实现判断图片是否在视口内的逻辑 一、使用原生js在vue2中实现图片懒加载 1.创建dom元素,v-lazy为自定义指令&#xff0c;在自定义指令传入图片…

瑞吉外卖实战学习--8、人员编辑更新

人员编辑更新 前言1、进入编辑窗口需要先获取用户信息2、通过上篇文章的update的方法来改变数据3、测试效果 前言 1、进入编辑窗口需要先获取用户信息 通过注解PathVariable 来获取路径需要携带的id然后赋值到路径上&#xff0c;再通过id查询用户信息 /*** 通过id查询用户信…

Java毕业设计-基于springboot开发的招聘信息管理系统平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、企业后台管理模块4、用户后台管理模块 四、毕设内容和源代码获取总结 Java毕业设计-基于spri…

黄金涨是商品牛市的领先信号

自2022年11月以来&#xff0c;黄金价格持续上涨&#xff0c;目前已经突破历史新高&#xff0c;历史上黄金上涨&#xff0c;大多是商品全面牛市的领先信号。在2008年Q4、2019年也出现过&#xff0c;黄金比其他商品更强&#xff0c;但随后的2009年和2020年均是商品的全面牛市。同…

【前端】layui前端框架学习笔记

【前端目录贴】 参考视频:LayUI 参考笔记:https://blog.csdn.net/qq_61313896/category_12432291.html 1.介绍 官网&#xff1a;http://layui.apixx.net/index.html 国人16年开发的框架,拿来即用,门槛低 … 2. LayUi的安装及使用 Layui 是一套开源的 Web UI 组件库&#xff0…

【论文阅读】UniLog: Automatic Logging via LLM and In-Context Learning

注 由于其公司的保密政策&#xff0c;本文没有公开源代码&#xff0c;数据是公开的。 文章目录 摘要一、介绍二、背景和动机2.1、日志语句生成2.2、大语言模型2.3、上下文学习&#xff08;In-Context Learning&#xff0c;ICL) 三、UNILOG3.1、模型骨干3.2、提示策略3.2.1、提…

stable diffusion如何下载预处理器?

如何下载预处理器&#xff1f; 具体位置:SD文件>extensions>sd-webui-controlnet>annotator” 把整个文件夹复制到SD的文件夹里面 里面有一个“downloads”文件夹 把这些模型复制到“downloads”文件夹里

YOLOV5训练自己的数据集教程(万字整理,实现0-1)

文章目录 一、YOLOV5下载地址 二、版本及配置说明 三、初步测试 四、制作自己的数据集及转txt格式 1、数据集要求 2、下载labelme 3、安装依赖库 4、labelme操作 五、.json转txt、.xml转txt 六、修改配置文件 1、coco128.yaml->ddjc_parameter.yaml 2、yolov5x.…

PLC的大脑和心脏——CPU及其分类、端子接线图

CPU不断地采集输入信号&#xff0c;执行用户程序&#xff0c;刷新系统的输出。 根据供电方式和输入/输出方式的不同&#xff0c;CPU分为3类&#xff0c;如下图1。 图1 CPU的分类 第1对字母&#xff0c;表示CPU的供电方式&#xff0c;AC&#xff08;Alternating Current&#…

vcf文件可以用excel打开吗?四种解决方案

vcf文件可以用excel打开吗&#xff1f; 当然可以。 一、VCF文件简介 VCF&#xff08;vCard&#xff09;文件是一种用于存储联系人信息的文件格式。它通常包含姓名、电话号码、电子邮件地址、地址等详细信息。VCF文件在多种设备和操作系统中广泛使用&#xff0c;特别是在电子邮…

百度云加速方法「Cheat Engine」

加速网盘下载 相信经常玩游戏的小伙伴都知道「Cheat Engine」这款游戏内存修改器&#xff0c;它除了能对游戏进行内存扫描、调试、反汇编 之外&#xff0c;还能像变速齿轮那样进行本地加速。 这款专注游戏的修改器&#xff0c;被大神发现竟然还能加速百度网盘资源下载&#xf…

MySQL数据库备份策略与实践详解

目录 引言 一、MySQL数据库备份的重要性 &#xff08;一&#xff09;数据丢失的原因 &#xff08;二&#xff09;数据丢失的后果 二、MySQL备份类型 &#xff08;一&#xff09;根据数据库状态 &#xff08;二&#xff09;根据数据的完整性 &#xff08;三&#xff09;…