【C++】哈希的应用 -- 位图
文章目录
- 一、位图的概念
- 二、位图的实现
- 三、库中的 bitset
- 四、位图的应用
- 五、哈希切割
一、位图的概念
我们以一道面试题来引入位图的概念:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中
我们的第一反应可能是将数据进行排序之后进行二分查找,或者将数据放入unordered_map/unordered_set中,然后再进行查找。但是这两种方式看似能够实现,但是实际上是不行的,因为数据量太大了,在内存中存放不下
40亿个整数,每个整数占4个字节,一共160个字节,而1G大约10亿字节,那么我们存储40个整数就大约需要16G,而我们的内存一般只有4个G,如果我们使用排序之后进行二分查找,那么就需要开辟一个16G大小的数组,显然是无法实现的,如果我们使用红黑树或者哈希表的方式,这也是不行的,因为红黑树每个节点需要存放节点的值,三个指针和颜色,每个节点就需要消耗16个字节,而哈希表中每个桶要存放一个指向下一个节点,也有一定的消耗
我们换一个思考的方式,题目中只要判断一个数在不在,并没有其他的要求,所以我们不需要将这些树存储下来,只需要用一个值来对他们进行标记即可,而标记一个数只需要一个比特位即可,如果二进制比特位为1,则表示存在,为0表示不存在
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
二、位图的实现
对于位图,一般我们只需要提供一下三个接口即可:
1.set : 用于将某一数值对应的比特位置为1,即进行标记(插入数据)
2.reset:将某一数值对应的比特位置为0,即删除标记(删除数据)
3.test : 用于测试某一数值对应的比特位是否为1,即查找数据
这里我们需要一个非类型模板参数–N是给定的数据的范围,不是数据的个数,因为C++中最小的数据类型是char,占一个字节的空间,一个字节占8个比特位,可以用于标记8个位置。我们可以用一个vector来进行存储数据,所以我们在构造函数中我们将vector resize到N/8+1即可,加1是因为C++中的除法是整除法,即直接舍弃余数,而我们这里应该采取进1法,即需要多开辟一个空间。此外,我们还可以将vector中的数据的类型定义为int,此时我们开辟空间的时候应该resize到N/32+1
对于三个重要接口的实现,我们使用目标值x/8就可以得到x应该被映射到哪一个下标,即在第几个char的位置,x%8就可以得到x应该被映射到该下标的第几个比特位,然后再将对应的位置置为1或0即可
对于set:我们可以使用或等的方法,找到一个数,这个数的第j个比特位(j为在下标中的第几个位置)为1,其他的位置为0,我们使用1向左移动j为即可,然后再进行或等
对于reset:我们可以使用与等的方法,找到一个数,第j为0,其他位为1,我们只需要将1向左移动j位i,然后再进行按位取反即可,然后再进行与等
对于test:我们知道,在逻辑关系中,0为假,非0为真,那么我们就可以将那个位置的数进行与,注意是与,不是与等,与1左移j为,如果那个位置为1,那么都为0,判断为假,如果那个位置不为0,与之后也不为0,此时转换为bool类型,为真,这里会进行整形提升,但是将一个数从0提升到非0或者从非0提升到0,所以符号我们的要求
代码实现如下:
namespace hdp
{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;};
}
有了位图之后,我们就可以解决上面的面试题了–由于题目中说明了数据是无符号整数,那么我们就可以将N定义为-1(有符号的-1等于无符号的最大值),然后我们只需要将这40亿个数据依次进行set,然后进行test即可
无符号的最大值大约为42亿九千万,也就是需要这么多的比特位来进行标记,计算得大约需要5亿字节,即512M,这是可以在内存中存放得下的
三、库中的 bitset
C++提供了类似于位图的东西–位的集合–bitset,它的功能比我们实现的更加的丰富,但是主要功能还是set,reset和test
四、位图的应用
位图主要运用于一下几个方面:
1.快速查找某个数据是否在一个集合中
2.排序 + 去重
3.求两个集合的交集、并集等
4.操作系统中磁盘块标记
我们来看看下面几道位图应用的题目:
1.给定100亿个整数,设计算法找到只出现一次的整数?
我们发现,使用传统的位图并不能解决这个问题,因为位图只能表示存在和不存在,只能够表示两种状态,这个问题中,就存在多种状态,但是我们可以将上面的问题分为3种状态–没有出现,出现1次,出现一次以上。那么我们就可以使用两个位图结合在一起,使用两个比特位来进行标识,两个比特位最多可以标识4种状态,我们取3种即可:
00:没有出现
01:出现1次
10:出现1次以上
代码实现如下:
template<size_t N>
class twobitset
{
public:void set(size_t x){if (!_bs1.test(x) && !_bs2.test(x)) //00{_bs2.set(x);//01}else if (!_bs1.test(x) && _bs2.test(x)) //01{_bs1.set(x);_bs2.reset(x); //10}}private:bitset<N> _bs1;bitset<N> _bs2;
};
2.1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
这道题和上面求只出现一次的数字的思路一致,这里我们需要将出现0次,1次,2次,2次以上的状态都表示处出来,使用两个标记位即可
template<size_t N>
class twobitset
{
public:void set(size_t x){if (!_bs1.test(x) && !_bs2.test(x)) //00{_bs2.set(x);//01}else if (!_bs1.test(x) && _bs2.test(x)) //01{_bs1.set(x);_bs2.reset(x); //10}else if(_bs1.test(x) && !_bs2.test(x)) // 10{_bs1.set(x);_bs2.set(x); //11}}private:bitset<N> _bs1;bitset<N> _bs2;
};
3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
我们可以将第一个文件的数据全部映射到一个位图中,然后再遍历取出第二个位图中的数据进行test即可,返回true的数据即为交集,但是这样就会得到许多重复的数据,所以最终的结果需要进行去重处理。我们也可以使用两个位图分别进行映射,然后进行遍历,两者进行与运算,为1则为交集的数据
五、哈希切割
对于下面这道题目:
给一个超过100G大小的log fifile, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
和前面我们的题目不同,这道题我们不能使用位图来解决,因为我们不知道相同的ip会出现多少次,所以就无法确定使用多少个比特位来进行标记
100G数据太大内存放不下,我们能不能将这个文件平均分为100分大小的文件,这样每个问题都只有一个G的大小,此时再依次插入map中进行统计次数,这样其实也是不行的,因为在统计下一个小的文件时,我们需要将之前的文件的统计结果即map中的数据进行clear,否则就会因为数据过多导致内存不足的情况,这样就不能够很好的统计出IP出现的次数。
我们可以想办法将相同的IP放入同一个小文件中,即我们可以使用哈希切割的方法–先使用字符哈希函数将IP转换为整数,然后再使用除留余数法将100G文件的IP地址划分到不同的小文件中
size_t Ai = HashFunc(IP) % 100;
经过哈希切割之后,相同的IP一定会被划分到同一个小文件中,因为相同的字符串经过哈希映射之后一定会得到相同的整数,那么模出来的结果也也一定相同,即会在同一个小文件中,但是不同的IP也可能会被划分到同一个文件中,因为会发生哈希冲突,此时文件的大小就可能会操作一个G,并且划分非结果有两种:
1.子文件中有许多相同的IP地址,此时我们可以直接使用map统计这些IP地址的数量(所有相同的IP地址一定会出现在同一个子文件中)
2.子文件中有许不同的IP地址,大多是不重复的,map统计不下,那么此时我们就需要换一个哈希函数,递归再切分
使用map统计,如果是第一种情况,可以统计出来的,不会报错
如果是第二种情况,map的insert插入失败,那是没有内存,相当于new节点失败,new失败会抛异常
最终出现次数最多的那个IP地址会被全部映射到某一个子文件中,我们对子文件使用map进行统计就可以得到其出现的次数
相关文章:

【C++】哈希的应用 -- 位图
文章目录 一、位图的概念二、位图的实现三、库中的 bitset四、位图的应用五、哈希切割 一、位图的概念 我们以一道面试题来引入位图的概念: 给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中 我…...

系列二、IO流原理及流的分类
一、概述 IO是Input、Output的缩写,IO流技术是非常实用的技术,用于处理数据传输,例如读写文件,网络通讯等。在Java程序中,对于数据的输入/输出操作以"流(stream)"的方式进行ÿ…...
【算法教程】排列与组合的实现
数据准备 在讲排列与组合之前,我们先定义数据元素类型Fruit class Fruit{constructor(name,price){this.name namethis.price price} }排列 对N个不同元素进行排序,总共有多少不同的排列方式? Step1: 从N个元素中取1个,共N种…...

uniapp实现简单的九宫格抽奖(附源码)
效果展示 uniapp实现大转盘抽奖 实现步骤: 1.该页面可设置8个奖品,每个奖品可设置中奖机会的权重,如下chance越大,中奖概率越高(大于0) // 示例代码 prizeList: [{id: 1,image: "https://img.alicdn…...

C++设计模式_09_Abstract Factory 抽象工厂
与上篇介绍的Factory Method工厂方法模式一样,Abstract Factory 抽象工厂模式也属于典型的“对象创建模式”模式,解决的问题也极其相似,在理解了Factory Method工厂方法模式的基础上再去理解Abstract Factory 抽象工厂模式就会变得更加容易。…...
一些前端面试思考
回流和重绘 先牢记这句话,回流必将引起重绘,而重绘不一定会引起回流。回流的代价要远大于重绘。 当你给一个元素更换颜色,这样的行为是不会影响页面布局的,DOM树不会变化,但颜色变了,渲染树得重新渲染页面&…...
Spring MVC(上)
1、Spring MVC简介: MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的JavaBean,作用是处理数据 JavaBean分为两类: 一类称为实体类Bean:专…...

ORACLE内存结构
内存体系结构 目录 内存体系结构 2.1自动内存管理 2.2自动SGA内存管理 2.3手动SGA内存管理 2.3.1数据库缓冲区 2.3.1.1保留池 2.3.1.2回收池 2.3.2共享池 2.3.2.1SQL查询结果和函数查询结果 2.3.2.2库缓存 2.3.2.3数据字典缓存 2.3.3大池 2.3.4 …...

excel常用的几个函数
1、MID函数 通常用来返回返回指定字符串中的子串。 函数公式: MID(string, starting_at, extract_length) String(必填):包含要提取字符的文本字符串 starting_at(必填):文本中要提取的第一个字…...

【Bug】【内存相关】偶然发现一个内存溢出Bug复盘
一、问题 跑自动化用例的时候,uat-sg环境,发现SGW经常会返回 502 Bad Gateway响应 二、原因 经过SRE和BE Dev共同排查,502 是从ALB-- > 后端服务 后端服务无法响应导致,ALB会直接给客户端返回502。 服务端:由于c…...

python读写.pptx文件
1、读取PPT import pptx pptpptx.Presentation(rC:\Users\user\Documents\\2.pptx) # ppt.save(rC:\Users\user\Documents\\1.pptx) # slideppt.slides.add_slide(ppt.slide_layouts[1])# 读取所有幻灯片上的文字 for slide in ppt.slides:for shape in slide.shapes:if shape…...

【Godot】【BUG】4.x NavigationAgent 导航不生效
4.2.beta2 试了半天才发现原来默认只对第一个有导航的 TileMap 的第 1 层 生效,而我设置的导航层不是第一层,然后我新建了一个 TileMap 将导航的瓦片设置到这个 TileMap 上了,如图 这样就解决了问题,不用再修改默认设置的东西了&a…...

Rust逆向学习 (1)
文章目录 Hello, Rust Reverse0x01. main函数定位0x02. main函数分析line 1line 2line 3line 4~9 0x03. IDA反汇编0x04. 总结 近年来,Rust语言的热度越来越高,很多人都对Rust优雅的代码和优秀的安全性赞不绝口。对于开发是如此,对于CTF也是如…...
【Golang | reflect】利用反射实现方法的调用
引言 go语言中,如果某个数据类型实现了一系列的方法,如何批量去执行呢,这时候就可以利用反射里的func (v Value) Call(in []Value) []Value 方法。 // Call calls the function v with the input arguments in. // For example, if len(in)…...

Teleport
从官网中获取到的代码如下 App.vue <template><div class"outer"><h3>Tooltips with Vue 3 Teleport</h3><div><MyModal /></div></div> </template> <script setup> import MyModal from "./My…...
flutter与原生 相互通信实战
一、原生和flutter 通信 ios 通信类 CommonUtil.swift import Foundation import Flutterpublic class CommonUtil {public static func emitEvent(channel: FlutterMethodChannel, method: String, type: String, errCode: Int32?, errMsg: String?, data: Any?){safeMa…...

结构光相机原理
结构光相机原理...

ubuntu安装Anaconda
下载 Anaconda 进入 Ubuntu,自己新建下载路径,输入以下命令开始下载 注意,如果不是 x86_64,需要去镜像看对应的版本(https://mirrors.bfsu.edu.cn/anaconda/archive/?CM&OA) wget https://mirrors.…...

【RNA structures】RNA转录的重构和前沿测序技术
文章目录 RNA转录重建1 先简单介绍一下测序相关技术2 Map to Genome Methods2.1 Step1 Mapping reads to the genome2.2 Step2 Deal with spliced reads2.3 Step 3 Resolve individual transcripts and their expression levels 3 Align-de-novo approaches3.1 Step 1: Generat…...

4、Kafka 消费者
5.1 Kafka 消费方式 5.2 Kafka 消费者工作流程 5.2.1 消费者总体工作流程 5.2.2 消费者组原理 Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件,是所有消费者的groupid相同。 • 消费者组内…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...