哈希的应用——位图

文章目录

  • 前言
  • 1. 面试题思考
  • 2. 位图
    • 2.1 位图的概念
    • 2.2 思路讲解及代码实现
      • 结构定义
      • 构造函数
      • set和reset接口实现
      • set和reset测试观察
      • test接口实现
      • test接口测试
      • 思考
  • 3. 位图的应用
    • 习题1
    • 习题2
    • 习题3
  • 4. 总结
  • 5. 源码
    • 5.1 bitset.h
    • 5.2 Test.c

前言

前面的文章里我们学习了哈希表,并用哈希表模拟实现了STL里面的unordered_map和unordered_set。
那接下来呢我们要再来学习一下哈希的应用——位图和布隆过滤器

这篇文章先来看第一个——位图

1. 面试题思考

首先我们来看一道腾讯曾经考过的面试题,引出我们今天要讨论的问题

问题是这样的:

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

那我们看到这个问题可能会想到这样的思路:

1. 遍历,时间复杂度O(N)
2. 排序+二分查找
3. 利用哈希表或红黑树,就是放到set或unordered_set里面进行查找嘛

那大家思考一下,上面这些方法有没有什么问题?

那这里我们要注意到的是它这里给的是40亿个整数。
那大家先算一下,40亿个整数大概占多少空间?
首先:
1G=1024MB=1024*1024KB=1024*1024*1024byte
约等于10亿byte(字节)
那40亿个整数(一个整型4字节)就约等于16G。

那大约占16G空间的话我们上面的方法还可行吗?

首先最关键的问题是16G的数据可能都不能一次全部放到到内存中,内存可能都不够用。
要遍历的话就得把它们全部放到内存里面啊
然后如果要排序那就是用归并排序了,分开放到一个个的小文件里面,进行归并。
但是文件里面也没法二分查找啊,都不能下标访问。
那你像放到set或unordered_set里面查找也是一样,内存可能不够,哈希表或红黑树还有额外的消耗,因为还要存一些指针啥的,记录颜色啥的。
当然你可以分开每次处理一小部分,但这样效率就不太行了。

所以上面这些思路都不太合适,而且我们这里只是要判断在不在,其实没必要把它们全部存起来。

那像这样的问题用我们接下来要学的位图来解决就比较好。

2. 位图

2.1 位图的概念

所谓位图,就是用一个个比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

那对于上面的那个问题我们其实就是判断在不在嘛,所以我们只需要存储每个值的存放状态(是否存在)即可,那我们就可以用位图来解决。

怎么做呢?

判断一个数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,比如可以用二进制比特位为1代表存在,为0代表不存在

题目说的是40亿个不重复的无符号整数,所以我们可以这样做:

无符号整数的最大值是2^32-1,即4294967295
所以我们开这样一个数组
在这里插入图片描述
下标从0到无符号整型的最大值2^32-1
每个元素的大小呢是1个比特位,因为我们用1个比特位就可以来标识当前位置下标所对应的值存不存在,所以它其实就是一个直接定址法的思想
当然没有类型的大小是1个bit,所以我们可以开成char数组(当然不一定非要用char),那每个位置就是8个比特位。
在这里插入图片描述
0到7映射到第一个char,8到15映射到第二个char,以此类推。
那大家看现在我们标识40亿个整数存不存在用了多大空间?
前面我们算了40亿个整数大约16G,一个整数4字节=32bit,现在相当于缩到了1bit,原来的1/32,那就是大约0.5G即512MB,这个大小肯定是没问题的。

2.2 思路讲解及代码实现

那接下来我们就来详细讲解一下如何使用位图解决上面那道题,并实现相应的代码。

结构定义

首先我们可以先简单定义一下位图的结构:

在这里插入图片描述
那它实际就是一个数组嘛。

构造函数

我们来看一下构造函数应该怎么写?

那我们初始的时候就把vector里面全放成0嘛。
那现在要开N的比特位的空间的话,我们的元素个数应该给多少呢?
🆗,这里给N/8+1是比较合适的
因为我们vector里面放的是char嘛,一个char8个bit,所以N/8,但是可能有余数,所以再+1,多开8个bit,这个确保肯定是够用的。
在这里插入图片描述

set和reset接口实现

然后这里我们首先要实现的两个核心操作叫做set和reset:

在这里插入图片描述
set就是把x映射的那个位置的比特位设置成1,reset就是把它设置成0。

那首先第一个问题我们如何通过x找到它映射的那个比特位?

🆗,那首先第一步我们要找到x映射到第几个char里面,因为我们现在开的是char数组,里面是一个个的char(8bit),所以这里就是去找它映射到第几个8比特位里面。
那要算这个的话我们是不是用x除8就得到了
在这里插入图片描述
比如就这个图我们算8,那就是8/10等于1,所以它就在下标为1(实际是第二个)的那个char里面,这没有问题。

那然后我们就要找它映射的是在这个char里面的哪个bit位上?

那用x%8是不是就行了
在这里插入图片描述

那紧接着,我们找到了这个比特位,如何把它设置成1(set)或者设置成0(reset)呢?

先来看set,把x映射的比特位设置成1,怎么做呢?

🆗,我们给这个位置按位或|上一个1是不是就可以了。
因为按位或的话是有1则1,全0才0嘛。
不论它原来是1还是0,按位或1之后都是1。
当然改变这个位置的同时我们不能影响其它位置
那我们如何做到呢?
我们让1左移j位(实际是向高位移动,只不过一般情况下都是左边为高位,所以如果我们画的图不同,移动方向可能就变了)然后和x映射的这个char进行按位或就行了
这里char和int进行按位或的话会发生整型提升
在这里插入图片描述
在这里插入图片描述
因为1的补码只有最低位一个1,其余位置都是0,所以向高位移动j个位置这个唯一的1就和x映射的那个位置对上了。
所以:
在这里插入图片描述

那reset呢?把x映射的比特位设置成0

很简单,让这个比特位按位与0就行了
按位与是有0则0,全1才1
那我们只需让x映射的这个char跟1左移j位之后再取反(按位取反~)的结果按位与就行了,这样x映射的比特位变成0,其它位置也不受影响(因为其它位置是跟1进行&)。
在这里插入图片描述

set和reset测试观察

我们测试测试,并调式观察一下

首先看一下set:

在这里插入图片描述
我们开一个大小100bit的,然后把10这个位置置成1,就表示插入10这个值,set之后10就存在了
我们来调试观察一下:
在这里插入图片描述
先拿到bs的起始地址
在这里插入图片描述
我们看到现在都是0(现在一列是1个字节,不支持按比特位监视)
在这里插入图片描述
然后我们执行set
在这里插入图片描述
那就是把10这个位置置成1
在这里插入图片描述
我们看一下对不对
在这里插入图片描述
没问题(我们看到监视窗口其实就对应我们后面画的那个图,他也是为了方便我们观察,符合人的习惯),大家可以自己再换一些值测试测试。

那我们再来测试一下reset:

在这里插入图片描述
刚才对10进行了set,现在再对10进行reset,看一下
在这里插入图片描述
在这里插入图片描述
🆗,没问题。

test接口实现

除了这两个呢,还有一个比较核心的接口——test,他其实就是去判断某个值存不存在(它映射的位置是否被设置成了1):

那其实也很简单,让x映射的这个比特位和1进行按位与,如果结果是0,就表明这个比特位是0,如果结果是1,就表明这个比特位是1。
所以让x映射的这个char跟1左移j位之后的结果进行按位与就行了
在这里插入图片描述
结果为0就是假,非0就是真

test接口测试

来测试一下test:

在这里插入图片描述
没问题。

那现在的话我们就可以很好的解决最开始的那个面试题了。

思考

那大家来思考一下:对于上面那个问题40亿个无符号整数,我们的位图应该给多大?

就开40亿个可以吗?
不可以的。
要注意我们不能按个数去开,而是要按照范围去开
就算现在变成10亿个无符号整数,我们也应该开4294967295(即2^32-1,无符号整型最大值)个,因为我们不知道这10亿个整数的取值范围,它可能就包含了最大值,所以我们要确保不论它多大,就可以映射到位图中一个确定的位置上。

那我们如何让位图开这么大空间呢?4294967295这个值也不好记啊?

🆗,我们给一个-1是不是就好了啊。
bitset<-1> bs;
在这里插入图片描述
因为我们的N是无符号整数啊,那-1变成无符号整数不就是整型最大值嘛,就跟那个npos是一样的玩法。
那除了用-1,其实我们还可以这样写:
在这里插入图片描述
字面值是可以这样写的。

那我们可以看一下,给这个值他是不是按我们上面分析的一样开了512MB空间:

在这里插入图片描述
大家看现在是0.4MB
然后我们继续执行一步
在这里插入图片描述
🆗,就变成512.4MB了

最后想告诉大家的是:

我们上面讲的位图其实C++库里面也是提供的有现成的
在这里插入图片描述
我们上面实现的命名风格其实就是跟着库里面走的
比较核心的接口我们都带大家实现了
其它的接口大家用的的时候可以自己查阅文档
在这里插入图片描述

3. 位图的应用

下面我们再来一起看几个位图相关的练习题

习题1

  1. 给定100亿个整数,设计算法找到只出现一次的整数?

大家思考一下,可以怎么解决?

首先这里是100亿个整数欸,我们还开0xFFFFFFFF这么多空间够吗?
当然是够的,虽然有100亿个,但它的范围还是不变的,肯定不会超过整型最大值,只能说明有很多重复值。
那我们肯定还是用位图来解决嘛,这道题我们可以这样做:
这道题让我们找出只出现一次的整数,其实通过两个比特位就可以判断。
在这里插入图片描述
我们只看两位,00就是0次,01就是一次,10就是1次以上,不一定就是两次,因为我们set的时候如果是10就可以不进行操作了,反正set到10的时候就已经超过1次了
所以我们可以给上面实现的位图改造一下,改造成每个位置占两个比特位的位图。
当然也可以不改造,我们还是用上面的位图,我们开两个位图,如果一个整数第一次出现就在第一个位图中把它映射的位置置成1,第二次出现就把它在第二个位图中映射的位置置成1

所以我们可以封装一个twobitset:

在这里插入图片描述

那对于它的set,就应该是这样的:

看当前值在两个位图中映射位置的值,如果是00,就变成01,如果是01,就变成10,如果是10,已经超过1次了,后续就可以不进行任何操作了
在这里插入图片描述

那然后我们可以写一个Print打印所有出现一次的值:

那我们如何找出twoset里面所有只出现一次的值呢?
很简单,我们去遍历找哪个值在第二个位图里面的映射位置是1(那一定是01,因为我们只有00、01、10三种状态),那它就是只出现一次的
在这里插入图片描述

那我们来搞一组数据测试一下:

测试的话我们数据量就不高那么大了
在这里插入图片描述
我们看一下打印的是不是只出现一次的
在这里插入图片描述
🆗,没有问题,大家可以自己再测试。

那我们再来看下一个问题:

习题2

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

首先第一种思路:

我们可以先读取一个文件的值放到内存中,然后再读取第二个文件,依次判断第二个文件里面的值在不在第一个位图里面,在的就是交集。
当然如果这样做的话有一个问题就是我们求出来的交集可能会有重复值出现,而求交集一般是要去重的。
当然我们可以解决一下:
我们读取第二个文件判断在不在位图里面的时候,如果在的话第一次就把它映射的这个位置置成0,这样后续再遇到就不再把它添到交集里面,就达到去重的效果了。

然后另外一种思路是这样的:

把两个文件的数据分别映射到两个位图里面。
然后遍历其中一个文件依次取值,判断如果某个值在两个位图里面映射的位置
都是1,那说明它在两个文件里都存在,就是交集
在这里插入图片描述
或者我们可以直接对两个位图进行按位与,结果中为1的位置对应的下标就是交集

那这两种方法的话:

如果数据量比较小用第一种其实比较好,因为第一种是遍历文件里面的数据去判断,而第二种我们遍历的数据的范围(跟数据量无关),所以如果数据量比较大可以用第二种。
所以要看场景选择合适的方法。

习题3

  1. 位图应用变形:1个文件有100亿个int,我们只有1G内存,设计算法找到出现次数不超过2次的所有整数

这个其实跟习题1有点类似嘛,就是第一题的一个变形:

还是用两个位图,这里我们可以分为4种状态
在这里插入图片描述
那还是两个比特位就够用了
我们最终要找到就是出现1次和2次的

那这个把第一题的代码稍微修改一下就行了

在这里插入图片描述

测试一下:

在这里插入图片描述
没有问题

4. 总结

最后总结一下:
在这里插入图片描述

那它的缺陷有没有办法解决呢?

有的就是我们下一篇文章要学的——布隆过滤器。

那我们下一篇文章在详细介绍

5. 源码

5.1 bitset.h

#pragma once
#include <vector>template <size_t N>
class bitset
{
public:bitset(){_bits.resize(N / 8 + 1, 0);}void set(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] |= (1 << j);}void reset(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] &= ~(1 << j);}bool test(size_t x){size_t i = x / 8;size_t j = x % 8;return _bits[i] & (1 << j);}
private:vector<char> _bits;
};void test1()
{bitset<100> bs;bs.set(10);cout << bs.test(10) << endl;cout << bs.test(15) << endl;bs.reset(10);cout << bs.test(10) << endl;cout << bs.test(15) << endl;
}void test2()
{//bitset<-1> bs;bitset<0xFFFFFFFF> bs;
}template <size_t N>
class twobitset
{
public://找到只出现一次的整数//void set(size_t x)//{//	//00->01//	if (_bs1.test(x) == false//		&& _bs2.test(x) == false)//	{//		_bs2.set(x);//	}//	//01->10//	else if (_bs1.test(x) == false//		&& _bs2.test(x) == true)//	{//		_bs2.reset(x);//		_bs1.set(x);//	}//	//10不用管//}/*void Print(){for (size_t i = 0; i < N; i++){if (_bs2.test(i) == true){cout << i << " ";}}cout << endl;}*///找到出现次数不超过2次的所有整数void set(size_t x){//00->01if (_bs1.test(x) == false&& _bs2.test(x) == false){_bs2.set(x);}//01->10else if (_bs1.test(x) == false&& _bs2.test(x) == true){_bs2.reset(x);_bs1.set(x);}//10->11else if (_bs1.test(x) == true&& _bs2.test(x) == false){_bs2.set(x);}//11不用管}void Print(){for (size_t i = 0; i < N; i++){if ((_bs1.test(i) == true && _bs2.test(i) == false)|| (_bs1.test(i) == false && _bs2.test(i) == true)){cout << i << " ";}}cout << endl;}
private:bitset<N> _bs1;bitset<N> _bs2;
};void test_twobitset()
{int a[] = { 1,1,1,2,3,3,3,3,4,4,5,6,9,12,33,45,45,45,6 };twobitset<100> bs;for (auto e : a){bs.set(e);}bs.Print();
}

5.2 Test.c

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "bitset.h"int main()
{test_twobitset();return 0;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vs+opencv+QT调试程序

2021-09-28vsopencvQT简单的图像处理工程_opencv 用qt还是vs_二两山栀子的博客-CSDN博客 【vsopencvQt搭建简单的图像处理界面】https://www.bilibili.com/video/BV16T411j7XQ?vd_source0aeb782d0b9c2e6b0e0cdea3e2121eba 调试过程一直出现这种问题&#xff0c;后来改DEBUG为…

JAVA宝典----容器(理解记忆)

目录 一、Java Collections框架是什么&#xff1f; 二、什么是迭代器&#xff1f; 三、Iterator与ListIterator有什么区别&#xff1f; 四、ArrayList、Vector和LinkedList有什么区别&#xff1f; 五、HashMap、Hashtable、TreeMap和WeakHashMap有哪些区别&#xff1f; 六…

一辆新能源汽车的诞生之旅:比亚迪常州工厂探营

作为在新能源汽车领域首屈一指的国产品牌&#xff0c;比亚迪近年来可以说是捷报频传&#xff0c;高奏凯歌。 以比亚迪常州工厂为例&#xff0c;据介绍该工厂当初规划设计时定下的生产目标&#xff0c;是年产量能够达到20万辆。然而在2023年上半年&#xff0c;该工厂光是主要销往…

自然语言处理实战项目17-基于多种NLP模型的诈骗电话识别方法研究与应用实战

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目17-基于NLP模型的诈骗电话识别方法研究与应用&#xff0c;相信最近小伙伴都都看过《孤注一掷》这部写实的诈骗电影吧&#xff0c;电影主要围绕跨境网络诈骗展开&#xff0c;电影取材自上万起真…

「网页开发|前端开发|Vue」05 Vue实战:从零到一实现一个网站导航栏

本文主要介绍如何从最开始的草图&#xff0c;通过确定基本结构、修改元素布局、美化外观来实现一个网站导航栏&#xff0c;从而熟悉网页开发的基本流程。同时&#xff0c;我们会把性能、规范性、可维护性方面的代码优化也考虑其中。 文章目录 本系列前文传送门一、场景说明&am…

Mac Homebrew中常用的 Brew 命令

Mac 中常用的 Brew 命令集 Brew&#xff08;Homebrew&#xff09;是一个强大的包管理器&#xff0c;用于在 macOS 上安装、更新和管理各种软件包。它使得在 Mac 上安装开发工具、应用程序和库变得轻松和便捷。本博客将介绍一些在 Mac 中常用的 Brew 命令&#xff0c;以帮助您更…

用通俗易懂的方式讲解大模型分布式训练并行技术:流水线并行

近年来&#xff0c;随着Transformer、MOE 架构的提出&#xff0c;使得深度学习模型轻松突破上万亿规模参数&#xff0c;传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此&#xff0c;我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群&…

【SpringBoot】最基础的项目架构(SpringBoot+Mybatis-plus+lombok+knife4j+hutool)

汝之观览&#xff0c;吾之幸也&#xff01; 从本文开始讲下项目中用到的一些框架和技术&#xff0c;最基本的框架使用的是SpringBoot(2.5.10)Mybatis-plus(3.5.3.2)lombok(1.18.28)knife4j(3.0.3)hutool(5.8.21),可以做到代码自动生成&#xff0c;满足最基本的增删查改。 一、新…

Linux 多进程解决客户端与服务器端通信

写一个服务器端用多进程处理并发&#xff0c;使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程&#xff0c;让子进程去处理这个连接&#xff0c;父进程只用来接受连接。 与多线程相比的不同点&#xff1a;多线程如果其中一个线程操…

小红书笔记爬虫

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

Ubuntu入门05——磁盘管理与备份压缩

1.检查磁盘空间占用情况 2.统计目录或文件所占磁盘空间大小 3.压缩 3.1 zip、unzip和zipinfo 运行时发现上面命令不成功&#xff0c;换成&#xff1a; &#xff08;将文件lkw放入压缩文件lkw01.zip中&#xff09; sudo zip -m lkw01.zip lkw 解压文件&#xff1a; 实操&…

MySQL从入门到精通【实践篇】之使用Sharding-JDBC 分库分表详解

文章目录 0. 前言本文技术组件版本基本介绍 2. 使用和配置&#xff1a;步骤1 引入依赖步骤2 配置数据源和分片策略步骤3 核心代码MybatisPlusConfig 核心配置OrderServiceOrderServiceImplOrderInfoOrderMapperOrderControllerBaseMapper 3. 数据库分片配置在我的demo工程中大家…

高等数学刷题

研究可不可导的两大问题 1.▲x能不能->0(正负均要) 2.函数可不可以不连续&#xff0c;通常用第二个例子 隐藏条件的挖掘 关键在于分析出f(x0)0f(0)

Kubernetes技术--k8s核心技术 configMap

1.概述 configMap最主要的作用是存储一些不加密的数据到/etcd,让pod以变量或者数据卷(volume)挂载到容器。 应用场景:配置文件、存储信息等 2.使用 -1.创建配置文件。 这里我们需要先编写一个配置文件。使用redis,如下所示:

postgresql-类型转换函数

postgresql-类型转换函数 简介CAST 函数to_date函数to_timestampto_charto_number隐式类型转换 简介 类型转换函数用于将数据从一种类型转换为另一种类型。 CAST 函数 CAST ( expr AS data_type )函数用于将 expr 转换为 data_type 数据类型&#xff1b;PostgreSQL 类型转 换…

axios返回几种数据格式? 其中Blob返回时的size是什么意思?

axios返回几种数据格式? 其中Blob返回时的size是什么意思&#xff1f; 1、字符串&#xff08;String&#xff09;&#xff1a;服务器可以返回纯文本或HTML内容&#xff0c;Axios会将其作为字符串返回。 2、JSON&#xff08;JavaScript Object Notation&#xff09;&#xff…

Matlab信号处理1:模拟去除信号噪声

由于工作内容涉及信号系统、信号处理相关知识&#xff0c;本人本硕均为计算机相关专业&#xff0c;专业、研究方向均未涉及信号相关知识&#xff0c;因此需进行系统地学习。之前已将《信号与系统》快速过了一遍&#xff0c;但感觉较抽象且理解较浅显。在此系统地学习如何使用Ma…

RK3568平台开发系列讲解(音视频篇)H264 的编码结构

🚀返回专栏总目录 文章目录 一、H264 的编码结构1.1、帧类型1.2、GOP1.3、Slice沉淀、分享、成长,让自己和他人都能有所收获!😄 📢视频编码的码流结构其实就是指视频经过编码之后得到的二进制数据是怎么组织的,换句话说,就是编码后的码流我们怎么将一帧帧编码后的图像…

vue优化首屏加载时间优化-cdn引入第三方包

前言 为什么要进行首屏加载优化&#xff0c;因为随着我们静态资源和第三方包和代码增加&#xff0c;压缩之后包会越来越大 随着网络的影响&#xff0c;在我们第一输入url请求资源时候&#xff0c;网络阻塞&#xff0c;加载时间长&#xff0c;用户体验不好 仔细观察后就会发现…

肖sir__设计测试用例方法之判定表06_(黑盒测试)

设计测试用例方法之判定表 1、判定表&#xff1a;是一种表达逻辑判断的工具。 2、判定表&#xff1a;包含四部分 1&#xff09;条件桩&#xff08;condition stub&#xff09;:列出问题的 所有条件&#xff08;通常条件次序无关紧要&#xff09;。 2&#xff09;条件项&#x…