位图 —— 哈希思想的产物

news2024/9/23 13:26:11

目录

1.学习位图的前置知识

计算机中数据存储的单位

C++中数据类型的大小

2.位图的讲解

位图的引出

位图的使用

位图的实现

位图完整代码

3.位图的总结 

位图的优缺点

优点

缺点

1.学习位图的前置知识

计算机中数据存储的单位

想要学习位图,首先要明白什么是位。位指的是比特位,是计算机中存储数据的最小单位,只有0和1两种取值。下面介绍一下计算机中数据存储的单位。

计算机中数据存储的单位从小到大一次是:bit(位)-> Byte(字节) -> KB(千字节) ->MB(兆字节) ->GB(吉字节)。

他们之间的换算关系是:
1 Byte = 8 bits
1 KB = 1024 Bytes
1 MB = 1024 KB
1 GB = 1024 MB

当然,计算机中还有更大的存储单位,这里不作解释。

C++中数据类型的大小

在编程语言中,定义数据时,往往需要先声明其数据类型,以便于编译器编译时,根据类型正确的从内存中取出数据。这是怎么做到的呢?其实啊,这都是编程语言的设计者规定好的了;在C++语言中,语言的内置类型有 bool、char、short、int、long、longlong、float、double等。在32位平台下,这些数据类型占用的内存空间大小如下:

  • bool 类型的数据占用一个字节的空间。
  • char 类型的数据占用一个字节的空间。
  • short 类型的数据占用两个字节的空间。
  • int 类型的数据占用4字节的空间。
  • longlong 类型的数据占用8个字节的空间。
  • float 类型的数据占用4个字节的空间。
  • double类型的数据占用8个字节的空间。

位和字节的示意图如下图所示:

2.位图的讲解

位图的引出

因为数据是由类型的,类型又决定了数据占用多大的内存空间,所以我们存储数据的时候,需要根据数据的大小,合理的使用数据类型,才能更好的使用内存资源。而且,在面对一些数据量特别多的情况下,我们也应该考虑使用占用内存空间小的数据类型解决问题,这就是位图的用武之地。比如下面这个问题:

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

你可能会想到以下解法:

解法一:放在数组中,直接暴力地遍历查找。时间复杂度为O(N)。

解法二:要查找一个数?嗯,哦哦哦!我们可以使用二分查找

  • 1.先用一个数组把这四十亿个不重复的、无符号的整数装起来;
  • 2.然后排序,排序可以使用快速排序;
  • 3.排完序之后,再跑一个二分查找;

排序的时间复杂度为O(NlogN),二分查找时间复杂度为logN。

解法三:利用二叉搜索树结构的容器进行查找。

  • 1.把数据装进set中。
  • 2.调用其接口进行查找。

set的底层数据结构是红黑树,是一种接近平衡的二叉搜索树,最多只需要查找数的高度次,数据的查找效率是O(logN)。

上述解法的效率依次提升,你可能会说:“嗯,不错不错,我真牛批”。但是你别忘了,那可是40亿个无符号整数呀,你电脑的内存装不装的下还是个问题呢。 

我们可以讨论一下,这四十亿个不重复无符号整数需要多大的内存空间?

分析:首先,题目说有四十亿个不重复的无符号的整数,但是没有告诉我们具体是哪一种的无符号整数类型;要想有四十亿个不重复的数,该数值的数据类型得支持,下面枚举C++中的无符号整数类型。

  1. unsigned int
    • 字节数:通常为4字节(32位)。
    • 取值范围:从0到2^32 - 1,即0到4,294,967,295。这个范围明显大于四十亿(4,000,000,000),因此unsigned int可以满足要求。
  2. unsigned short
    • 字节数:通常为2字节(16位)。
    • 取值范围:从0到2^16 - 1,即0到65,535。这个范围远小于四十亿,因此unsigned short不能满足要求。
  3. unsigned long
    • 字节数:在大多数现代系统上,unsigned long是4字节(32位),但在某些平台(特别是64位系统)上,它可能是8字节(64位)。
    • 取值范围:如果是4字节,取值范围与unsigned int相同,即0到4,294,967,295;如果是8字节,则取值范围更大,远超过四十亿。无论是哪种情况,unsigned long都能满足要求。
  4. unsigned long long
    • 字节数:通常为8字节(64位)。
    • 取值范围:从0到2^64 - 1,即0到18,446,744,073,709,551,615。这个范围远大于四十亿,因此unsigned long long也能满足要求,但相比unsigned int或4字节的unsigned long,它占用了更多的内存空间。

分析上面的数据类型,我们要想支持40亿个不重复的数,且尽可能的节省空间,我们的最佳选择是unsigned int类型,该类型的数据在32位平台下占用4字节,也就是8个比特位。1G大约为10亿个字节,40亿个无符号整数大约占用160亿个字节,也就是大约16G的内存空间。可以设想,光跑这一个程序就要16G的内存空间,其他程序还怎么玩呀?

位图的使用

所以使用上面的三种解法都不靠谱,这个时候,我们有了计算机中存储单位的前置知识,我们自然而然的就想要用最小的单位 bit 来表示数据的存储了,但是bit只有个两种取值,0和1;这可怎么办呢?

学过逻辑数学的朋友都知道,逻辑电路中,通常使用0和1来表示两种状态,我们这里也是一样的,我们可以用1表示数据存在,用0表示数据不存在;还是这个题目。

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

题目只要求我们判断一个数在不在,在和不在,不就是两种状态吗?哦,感觉有戏。不要忘记,题目中说的是40亿个不重复的数;结合哈希的思想,如果一个数可以映射一个bit位,数据和位置之间不就建立了一 一 映射的关系了吗?所以我们现在的重点是,数据如何与位置之间建立一 一 映射关系?

一个数据映射一个比特位,我们就需要40亿个比特位,大约是4GB,大部分的电脑是能满足这个需求的。因为数据是不重复的,我们完全可以将数据 和 不同的比特位映射起来,具体做法如下:

  • 数据的值是666,就映射第666个比特位,数据的值是888,就映射第888个比特位。
  • 当我们需要判断一个数是否存在时,直接根据元素的值计算出元素所映射的bit位。
  • 通过判断该bit位是否为1,从而确定元素是否存在。

位图的实现

下面讲解位图这种数据结构的实现,相信看完之后,对位图的理解会更上一层楼!

成员变量设计图如下图所示:

我们采用采用一个vector,vector中装的是int类型的数据。

操作数据的成员方法有三个:

  • void set(size_t x):把数据x映射的位标记为1。
  • void reset(size_t x):把数据x映射的位标记为0。
  • bool test(size_t x):判断数据x是否存在。

void set(size_t x)的实现:set函数用来将数据x映射的比特位标记为1。为了实现这个功能,我们分为以下三步。

1.计算出数据x在位图中的第几个整形数据中;

  • 一个整形数据占用4个字节,也就是32个bit位;使用数据x对32做除法,即可计算出该数据在第几个整形数据中。

2.计算出数据x在该整形数据的第几个bit位中;

  • 想要计算出该数据在整形数据中的第几个bit位,我们可以对32进行取余操作。

3.将该bit位置为1。

比如:我们想要将下图中的第i个bit位置为1,我们可以利用1的特性 —— 最低位位1,其他位全为0。进行这两步操作 :

a、将1移到第i个bit位的下方,示意图如下所示:

b、然后让二者进行按位或操作(两个bit位进行按位或操作,有1则1),示意图如下所示:

这样一来,我们就将第i个bit位置为1了。 

set方法代码如下:

		// 将该数据映射的位置设为1 
		void set(size_t x)
		{
			// 比如:25 应该映射在第25个比特位上
			// 1.计算在第几个整形上
			size_t i = x / 32;

			// 2.计算该整形的第几个比特位上
			size_t j = x % 32;

			_bitset[i] |= 1 << j; // 左移是往 高位移,右移是往 低位移
		}

void reset(size_t x)的实现:reset()函数用于将数据x映射的bit位标记为0,为了实现这个功能,我们也可以分为三步走。

1.确定该数据在位图中的第几个整形数据中。

2.确定该数据在整形数据中的第几个bit位中。

3.将该bit位置为0。

前面两步和set()的前两步是一模一样的,具体实现过程参考前面的set方法。所以我们只需要学习如何将第i个bit位置为0。

想要将第i个bit位置为0,我们同样可以借助1来完成,当我们把1移到第i个bit位的下方之后,我们可以通过如下两步操作将第i个bit位置0。

a、将1进行按位取反操作,如下图所示:

b、和第i个bit位进行按位与操作,如下图所示:

reset方法代码如下:

		// 将该数据映射的位置设为0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

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

bool test(size_t x)方法的实现:test方法用于检测第x个bit位是否为1,也就是判读数据x是否存在;要想实现这个功能,我们只需要实现以下三步。

1.确定该数据在位图中的第几个整形数据中。

2.确定该数据在整形数据中的第几个bit位中。

3.取出位图中第i个bit位的值。

没错,这三个接口的前面两步都是一样的,所以我们的目标就是弄懂第三步是怎么实现的。

要想实现第三步,我们还是可以借助1来完成;当我们把1移到位图中第i个bit位的下方之后,我们只需要返回两个bit位进行按位与操作的结果即可,因为C++中用0表示假,非0表示真,因此,0和1可以直接做逻辑条件判断。

如果位图中第i个bit位的值为0,和1进行按位与操作之后,结果还是0;如下图所示:

如果位图中第i个bit位的值为1,和1进行按位与操作之后,结果还是1;如下图所示:

test方法代码如下:

		// 判断该数据是否存在
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			return _bitset[i] & (1 << j);
		}

位图完整代码

附上一份位图的完整代码:


	template<size_t N>// 位图空间开多大,不取决于要处理的数据个数,而是取决于数据的范围
	class BitSet
	{
	public:
		BitSet()
		{
			_bitset.resize(N / 32 + 1); // N是要开的比特位的个数,转换成对应的整形的个数
		}

		// 将该数据映射的位置设为1 
		void set(size_t x)
		{
			// 比如:25 应该映射在第25个比特位上
			// 1.计算在第几个整形上
			size_t i = x / 32;

			// 2.计算该整形的第几个比特位上
			size_t j = x % 32;

			_bitset[i] |= 1 << j; // 左移是往 高位移,右移是往 低位移
		}

		// 将该数据映射的位置设为0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

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

		// 判断该数据是否存在
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			return _bitset[i] & (1 << j);
		}
	private:
		std::vector<int> _bitset;
	};

3.位图的总结 

位图其实就是将计算机中最小的存储单位 bit位 和 哈希 思想结合实现的一种数据结构,用每一位来存放某种状态,适用于海量数据,数据无重复的场景,通常是用来判断某个数据存不存在的。

位图的优缺点

优点

  1. 空间效率高:由于每个元素只需要一个bit来表示,因此位图能够极大地节省存储空间。与使用整数或字符数组相比,位图在表示大量布尔值时具有显著的空间优势。

  2. 查询速度快:位图支持快速的查询操作。由于元素以位的形式存储,通过索引直接访问位数组,可以在O(1)时间复杂度内查询某个元素是否存在。这对于处理大规模数据集合非常有效。

  3. 去重和快速统计:位图可以高效地用于数据去重和统计。在处理大量数据时,可以快速判断一个数据是否已经出现过,并统计出每个元素的频率。

  4. 数据压缩:对于只有两种状态的数据(如布尔值),使用位图可以极大地减少存储空间,实现高效的数据压缩。

缺点

  1. 数据类型限制:位图一般只能处理整形数据,如果内容编号是字符串或其他非整形类型,就无法直接使用位图进行处理。这限制了位图的适用范围。

  2. 功能限制:位图主要用于表示元素是否存在某个集合中,对于需要存储复杂数据或进行复杂查询的应用场景,位图可能不是最佳选择。

  3. 稀疏数据问题:如果元素值范围很大但实际值很稀疏,那么位图可能会浪费大量的空间。因为,位图需要为所有可能的元素值分配空间,即使某些值在数据集中并不存在。

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

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

相关文章

在Windows10系统快速启用telnet功能

打开Windows控制面板 → 程序 → 启用或关闭Windows功能 勾选Telnet客户端 然后确定 启用后windowR 打开运行&#xff0c;输入cmd回车 使用telnet命令回车 可以直接使用telnet命令&#xff08;如果提示telnet是外部命令需要重启电脑&#xff09; 输入 ?/help 可查看帮助 到…

排序算法:

冒泡排序&#xff1a; 从列表的第一个数字开始进行比较&#xff0c;判断该数和下一个数之间的大小关系&#xff0c;如果该数比右边的数大&#xff0c;则交换位置&#xff1b;否则不变。一般一轮可以确定最大的数字&#xff0c;在列表的最后一位。 代码&#xff1a; 注意&…

开源 AI 智能名片 S2B2C 商城小程序在现代商业中的创新与启示

摘要&#xff1a;本文通过分析一种以 9.9 元裙子为代表的独特商业模式&#xff0c;探讨了其背后的现金流、产品和渠道组合策略&#xff0c;以及开源 AI 智能名片 S2B2C 商城小程序在其中可能发挥的作用和带来的启示。 一、引言 在当今竞争激烈的商业环境中&#xff0c;企业不断…

Redis数据结构与连接

1 基本的数据结构 1.1 string string的实现有多种 int&#xff1a;字符串长度小于等于20且能转成整数raw&#xff1a;字符串长度大于44embstr&#xff1a;字符串长度小于等于44 字符串长度小于1M 时&#xff0c;加倍扩容&#xff1b;超过 1M 每次只多扩1M&#xff1b;字符串…

【如何在Mac电脑和示波器之间共享文件】

如何在Mac电脑和示波器&#xff08;Tektronix OSC&#xff09;之间共享文件 Tektronix Lan&#xff1a; Mac Lan&#xff1a; 按下Utility,開始設定&#xff1b; 按下Utility Page,選至I/O&#xff1b; Network Configuration選至Manual,再Set IP Adresses Manually&am…

tailwindcss

什么是Tailwind CSS Tailwind CSS 是一个可定制化的 CSS 框架&#xff0c;最大的特点是功能类优先&#xff0c;和我们知道的bootstrap&#xff0c;element ui&#xff0c;antd&#xff0c;veui等框架一样。将一些CSS样式封装好&#xff0c;用来加速我们开发的一个工具。 简单…

精选算法编程题

一、有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10]输出&#xff1a;[0,1,9,16,100]解释&#xff1a;平方后&am…

JAVAEE初阶第二节——多线程基础(中)

系列文章目录 JAVAEE初阶第二节——多线程基础(中) 多线程基础(中) 多线程带来的的风险-线程安全 (重点)synchronized 关键字volatile 关键字wait 和 notify 文章目录 系列文章目录JAVAEE初阶第二节——多线程基础(中) 多线程基础(中)一.多线程带来的的风险-线程安全 (重点)1…

CSDN字体、颜色设置

目录标题 一、字体设置二、字体颜色设置 一、字体设置 设置文字字体的基本语法如下&#xff1a; <font face"字体名称">显示内容</font>在字体名称部分写入字体的名称&#xff0c;比如常见的&#xff1a;宋体、微软雅黑、黑体、华文行楷、方正姚体、楷…

C++奇迹之旅:深度解析list的模拟实现

文章目录 &#x1f4dd;前言&#x1f320;list节点&#x1f309;list &#x1f320;迭代器的创建&#x1f309;const迭代器 &#x1f320;代码&#x1f6a9;总结 &#x1f4dd;前言 &#x1f320;list节点 我们先建立一个列表里的节点类listnode&#xff0c;用来构造list的节…

【知识】对比Share mem/Pin mem/GPU mem之间的传输速度

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 参考代码 运行结果 参考代码 import torch import time import matplotlib.pyplot as plt# 初始化设备和张量 device torch.device(cuda) dat…

float 或 double 运算的时候会有精度丢失的风险?

《阿里巴巴 Java 开发手册》中提到&#xff1a;“浮点数之间的等值判断&#xff0c;基本数据类型不能用 来比较&#xff0c;包装数据类型不能用 equals 来判断”。“为了避免精度丢失&#xff0c;可以使用 BigDecimal 来进行浮点数的运算”。 浮点数的运算竟然还会有精度丢失…

当AI遇上制药:加速跑向未来的快车道,还是布满荆棘的征途?

01 在全球科技领域&#xff0c;AI的崛起无疑掀起了一场变革的风暴&#xff0c;其影响力已渗透至各行各业&#xff0c;促使各领域积极寻求与AI技术的深度融合&#xff0c;以提升效率、创新产品及优化服务。在医疗健康领域&#xff0c;AI与制药的结合自2007年起航&#xff0c;历…

Servlet 简介+ Cookie和session+过滤器Filter和监听器Listener

目录 1.Servlet 介绍 1.1 什么是Servlet 1.2 Servlet的使用方法 1.3 Servlet接口的继承结构 2.Servlet的生命周期 2.1 servlet生命周期中重要的方法 3.获得前端提交数据 4.中文乱码的解决方案 5.重定向和转发 5.1 重定向 5.2 转发 6. Request对象 7. Response对象…

(南京观海微电子)——半导体制程介绍

半导体的制程&#xff1a; 1. IC 设计&#xff1a; 预先规划芯片的功能&#xff0c;功能包含算术逻辑、记忆功能、 浮点运算、 数据传输&#xff0c;各功能分布在芯片上各区域&#xff0c;并制作所需的电子元件&#xff0c;工程师使用&#xff08;HDL&#xff09;设计电路图&…

opencv之几何变换

文章目录 1 前言2 线性几何变换的主要类型2.1 平移 (Translation)&#xff1a;2.1.1 定义2.1.2代码 2.2 缩放 (Scaling)&#xff1a;2.2.1 定义2.2.2 代码 2.3 旋转 (Rotation)&#xff1a;2.3.1 定义2.3.2 代码 2.4 仿射变换 (Affine Transformation)&#xff1a;2.4.1 定义2.…

Git:版本控制的强大工具与全面解析

Git&#xff0c;作为现代软件开发中不可或缺的版本控制工具&#xff0c;凭借其高效、灵活和分布式的特性&#xff0c;赢得了全球开发者的青睐。无论是个人项目还是大型企业级应用&#xff0c;Git 都能够提供强大的版本管理、分支策略、远程协作等功能。本文将从Git的创建与初始…

【电子数据取证】Android APK静态分析与动态分析

文章关键词&#xff1a;电子数据取证、手机取证、安卓取证、云取证、APK分析 当前手机用户量增长越来越快&#xff0c;尤其是中国&#xff0c;手机用户量已超10亿&#xff0c;即大约75%的中国人拥有自己的手机。正因为手机越来越智能化&#xff0c;携带也方便&#xff0c;因此…

算法day08 链表

4.链表_哔哩哔哩_bilibili 一、判断链表为回文 暴力方式&#xff1a; 从链表头开始将链表每一个元素值依次放入数组中&#xff0c;按下标比较值。 从链表尾开始将链表一半元素值放入stack栈中&#xff1b;每次弹栈比较 弹出的值和 链表值。 快慢指针&#xff1a; 假设有这样一个…

python-Flask搭建简易登录界面

使用Flask框架搭建一个简易的登录界面&#xff0c;登录成功获取token数据 1 搭建简易登录界面 代码如下 from flask import Flask, jsonify from flask import request import time, hashlibapp Flask(__name__)login_html <html> <head> <title>Log…