【数据结构】哈希应用

news2024/11/28 0:31:09

目录

一、位图

1、位图概念

2、位图实现

2.1、位图结构

2.2、比特位置1

2.3、比特位置0

2.4、检测位图中比特位

3、位图例题

3.1、找到只出现一次的整数

3.2、找到两个文件交集

3.3、找到出现次数不超过2次的所有整数

二、布隆过滤器

1、布隆过滤器提出

2、布隆过滤器概念

3、布隆过滤器实现

3.1、布隆过滤器的插入

3.2、布隆过滤器的查找

3.3、布隆过滤器删除

4、布隆过滤器例题

4.1、找到两个存贮query的文件的交集

4.2、哈希切割


一、位图

1、位图概念

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

位图的优点:

  • 速度快
  • 节省空间

位图的缺点:

  • 只能映射整型 ,其他类型如:浮点数、string等等不能存储映射。

2、位图实现

2.1、位图结构

位图类的结构如下:

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N / 8 + 1, 0);
	}

    //将某个比特位置1
	void set(size_t x)
	{}
    
    //将某个比特位置0
	void reset(size_t x)
	{}

    //检查位图中某个比特位是否为1
    bool test(size_t x)
    {}

private:
	vector<char> _bits;
};

2.2、比特位置1

实现代码:

	void set(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

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

 用除法计算 x 映射的位在数组的第 i char 类型内。 用取模计算 x 映射的位在第 i char 类型的第 j 个比特位。然后用按位或运算把指定比特位置1。

 需要注意的是在进行按位或运算时,使用的是 1 左移 j 位,而不是右移。这是因为在我们人类的主观认识上,数位的排列是下面这样的:

 但实际上,在计算机的虚拟层储存逻辑上,数位的保存是这样的:

 我们所说的左移与右移,并不是向左移动或者向右移动,而是向高位移动与向低位移动。因此为了找到目标位置,需要使用左移,而不是右移。

2.3、比特位置0

实现代码:

	void reset()
	{
		size_t i = x / 8;
		size_t j = x % 8;

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

 用除法计算 x 映射的位在数组的第 i char 类型内。 用取模计算 x 映射的位在第 i char 类型的第 j 个比特位。然后用按位非与运算把指定比特位置1。

2.4、检测位图中比特位

实现代码:

	bool test(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

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

3、位图例题

3.1、找到只出现一次的整数

设置状态:出现 0 次,状态是 00 。出现 1 次,状态是 01 。出现 2 次及以上,状态是 10 。

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)
		{
			_bs1.set(x);
			_bs2.reset(x);
		}

		//10
	}

	void Print()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_bs2.test(i))
			{
				cout << i << endl;
			}
		}
	}
public:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

测试结果如下: 

3.2、找到两个文件交集

 方法一:把其中一个文件的值读取到位图中,再读取另一个文件,判断在不在上面的位图中,在就是交集,取出该值,并把对应位图置0。

 方法二:创建两个位图,读取文件1的数据映射到位图1,读取文件2的数据映射到位图2。然后让位图1与位图2按位与。最终结果是交集。

3.3、找到出现次数不超过2次的所有整数

 设置状态:出现 0 次,状态是 00 。出现 1 次,状态是 01 。出现 2 次,状态是 10 。出现 3 次及以上,状态是 11 。

实现代码与 3.1 中类似。

二、布隆过滤器

1、布隆过滤器提出

 我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

  1.  用哈希表存储用户记录,缺点:浪费空间。
  2.  用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
  3.  将哈希与位图结合,即布隆过滤器。

2、布隆过滤器概念

 布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

 布隆过滤器可以降低冲突的概率。一个值映射到一个位置,容易误判,映射到多个位置,就可以降低误判率。

布隆过滤器的优点:

  • 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关。
  • 哈希函数相互之间没有关系,方便硬件并行运算。
  • 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势。
  • 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势。
  • 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能。
  • 使用同一组散列函数的布隆过滤器可以进行交、并、差运算。

 布隆过滤器的缺点:

  • 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)。
  • 不能获取元素本身。
  • 一般情况下不能从布隆过滤器中删除元素。
  • 如果采用计数方式删除,可能会存在计数回绕问题。

3、布隆过滤器实现

3.1、布隆过滤器的插入

 向布隆过滤器中插入:"baidu":

 向布隆过滤器中插入:"tencent": 

 

 实现代码:

struct BKDRHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

struct APHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (long i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

template<size_t N, class K = string, class Hash1 = BKDRHash, class Hash2 = APHash, class Hash3 = DJBHash>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = N * _X;

		size_t hash1 = Hash1()(key) % len;
		_bs.set(hash1);

		size_t hash2 = Hash2()(key) % len;
		_bs.set(hash2);

		size_t hash3 = Hash3()(key) % len;
		_bs.set(hash3);
	}
private:
	static const size_t _X = 4; // 布隆过滤器的长度与数据数量的倍数关系
	bitset<N*_X> _bs;  //这样可以有效的减少不同数据间的冲突
};

3.2、布隆过滤器的查找

 布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。

实现代码:

bool test(const K& key)
{
	size_t len = N * _X;

	size_t hash1 = Hash1()(key) % len;
	if (!_bs.test(hash1))
		return false;

	size_t hash2 = Hash2()(key) % len;
	if (!_bs.test(hash2))
		return false;

	size_t hash3 = Hash3()(key) % len;
	if (!_bs.test(hash3))
		return false;

	return true;

	//依然存在误判,有可能把不在的判断成在
}

 需要注意的是,即使使用了三个哈希函数进行判断,仍然存在误判的可能性。如果判断该数据不存在,则该数据一定不存在。如果判断该数据存在,则该数据有一定的可能性其实不存在。

 因此,布隆过滤器只能运用于能够容忍误判的场景,比如视频推送等等。而对于一些不容忍误判的场景下,布隆过滤器也有相应的解决方法:如果判断出数据存在,就到数据库中进行二次确认,依然存在就返回存在,不存在就返回不存在。

 哈希函数个数,代表一个值映射几个位,哈希函数越多,误判率越低,但是哈希函数越多,平均占的空间就越大。

3.3、布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

  一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。

缺陷:

  1. 无法确认元素是否真正在布隆过滤器中。
  2. 存在计数回绕。

4、布隆过滤器例题

4.1、找到两个存贮query的文件的交集

使用哈希切分,把一个大文件分割成多个小文件,再让小文件之间取交集:

 使用这种方法,因为不是平均切分,可能会出现冲突多,某一个Ai、Bi小文件过大的问题。出现这种问题无非两种情况:

  1. 单个文件中,有某个大量重复的query。
  2. 单个文件中,有大量不同的query。

可以直接使用一个unordered_set/set,依次读取文件query,插入set中:

  1. 如果读取了整个小文件的query,都可以成功插入set,说明是情况一。
  2. 如果读取了整个小文件的query,插入过程中出现抛异常,说明是情况二。换成其他哈希函数,再次分割,再求交集。

说明:set插入key,如果已经有了,返回false。如果内存用完了就会抛bad_alloc异常,剩下的都会成功。

4.2、哈希切割

 给一个超过100G大小的log file,log中存着IP地址,设计算法找到出现次数最多的IP地址。

依然使用哈希切割的方法:

 依次处理每一个小文件,使用unordered_map 或 map 统计ip出现的次数。

  1.  如果统计过程中,没有抛异常则正常统计。统计完一个小文件,记录最多的那一个。clear内存,再统计下一个小文件。
  2.  如果统计过程中,出现抛异常现象,说明单个文件过大,冲突太多。换成其他哈希函数,再次分割。

 建立一个k个数据的小堆,每统计一次,就插入小堆,转换成topK问题,最终可以解决。

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

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

相关文章

javaScript蓝桥杯----商城管理系统

目录 一、介绍二、准备三、目标四、代码五、完成 一、介绍 在商城管理系统中&#xff0c;超级管理员和普通管理员因为权限不同&#xff0c;登录进入后看到的菜单也会是不同的。 本题需要你完成商城管理系统中权限数据的处理。 二、准备 开始答题前&#xff0c;需要先打开本…

2023年,千万不要裸辞....

作为IT行业的大热岗位——软件测试&#xff0c;只要你付出了&#xff0c;就会有回报。说它作为IT热门岗位之一是完全不虚的。可能很多人回说软件测试是吃青春饭的&#xff0c;但放眼望去&#xff0c;哪个工作不是这样的呢&#xff1f;会有哪家公司愿意养一些闲人呢&#xff1f;…

硬件设计电源系列文章-LDO基础知识

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 例如&#xff1a; 本文主要开始讲述电源的发展 整体架构流程 提示&#xff1a;这里可以添加技术整体架构 AC/DC转换基础。为什么需要AC需要DC 技术名词解释 提示&#x…

车载测试很难吗?我靠着这套面试资料拿下了16k车载测试offer!

目录 如何写简历 项目经验 如何准备面试 车载项目的实施 常见面试题 总结&#xff1a; 车载测试通常包含以下三个方面&#xff1a; 系统测试&#xff1a;对整车系统进行测试&#xff0c;如车载电子系统、底盘系统、动力系统等。系统测试主要是评估整车各项性能指标是否达到…

STM32单片机(四)第一节:OLED调试工具

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

深入聊一下机械硬盘的相关内容

本文是《数据存储通识课》合集的一部分,本合集希望通过一系列文章科普数据存储相关技术内容。同时,本系列文章不仅仅是科普,还会进行有深度解析,理论结合实现,从代码实现层面进行剖析​ 介绍存储技术当然要从存储技术最基本的组件磁盘开始介绍了。目前市面上我们见得最多的…

shell脚本基础4——function函数、expect

文章目录 一、function函数1.1 函数的定义使用1.2 函数参数1.2.1 脚本内传参1.2.2 脚本外传参 1.3 引用局部变量1.3.1 区分局部变量1.3.2 全局变量在函数外1.3.3 全局变量在函数体1.3.4 函数体直接输出具体值 二、expect命令2.1 常用命令2.2 安装使用2.3 例一2.4 例二2.5 例三 …

Cmake工具的简单使用

引言 本篇文章讲述如何简单的使用cmake工具构建一个项目&#xff0c;帮助入门的c新手学会如何使用cmake. 我们在Clion新创建一个项目时&#xff0c;会发现&#xff0c;除了main.cpp文件之外&#xff0c;还存在一个build-debug目录和一个CMakelists.txt文件&#xff0c;如图: …

Spring Boot 集成 Redisson 实现分布式锁

Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库&#xff0c;可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面&#xff0c;例如分布式锁、分布式集合、分布式事件发布和订…

Java学习记录

引入包 把jar包复制到lib下面&#xff0c;然后右键add as library 常用方法 读取文件&#xff1a;FileInputStream获得当前路径&#xff1a;System.getProperty(“user.dir”) 快捷键 https://blog.csdn.net/W_317/article/details/114300373 常用的 生成循环&#xff1…

VMware Workstation 16 安装教程

哈喽&#xff0c;大家好。今天一起学习的是VMware Workstation 16的安装&#xff0c;vm虚拟机是小编非常喜欢的生产力软件&#xff0c;小编之前发布的测试教程钧在vm上进行的实验。 VMware Workstation是一款功能强大的桌面虚拟计算机软件&#xff0c;它能够让用户在宿主机操作…

Coggle 30 Days of ML 打卡任务一:两个赛题数据可视化

Coggle 30 Days of ML 打卡任务一&#xff1a;两个赛题数据可视化 任务一&#xff1a;两个赛题数据可视化 难度/分值&#xff1a;低/1 打卡内容&#xff1a; 参赛选手名称&#xff1a;AppleDoctor完成日期&#xff1a;2023.6.6任务完成情况&#xff1a; 使用的编程语言&…

【你真的会斗图嘛?】Python爬虫实战项目——你想要的图都可以爬到(附安装地址)

目录 一、安装知识&#xff08;1&#xff09;Python环境变量&#xff08;2&#xff09;Pycharm开发工具&#xff08;3&#xff09;requests模块1、安装下载好Pycharm之后&#xff0c;找到终端进行下载requests模块2、输入下载语法&#xff1a;3、此次项目还需用到正则&#xff…

【STM32】制作一个bootloader

工作环境&#xff1a;STM32CubeMXKeil 相关环境准备这里就不介绍了。 bootloader是什么 bootloader就是单片机启动时候运行的一段小程序&#xff0c;这段程序负责单片机固件的更新&#xff0c;也就是单片机选择性的自己给自己下载程序。可以更新&#xff0c;可以不更新&…

C++课程设计:电梯控制系统程序设计

目录 电梯控制系统程序设计背景 类结构图 程序设计 程序释义 运行展示 总结与体会 电梯控制系统程序设计背景 电梯控制系统程序设计旨在通过C编程实现电梯的模拟功能&#xff0c;使用户能够选择上行或下行&#xff0c;并输入要进入的楼层&#xff0c;程序将模拟电梯运行过…

死锁细究!

一、死锁的定义&危害 1、死锁是什么 发生在并发中互不想让&#xff1a;当两个&#xff08;或更多&#xff09;线程&#xff08;或进程&#xff09;相互持有对方所需要的资源&#xff0c;又不主动释放&#xff0c;导致所有人都无法继续前进&#xff0c;导致程序陷入无尽的…

学生成绩管理系统(C语言)

学生成绩管理系统 一、实现思路二、代码的实现&#xff08;1&#xff09;构造功能框架&#xff08;2&#xff09;实现各项功能 三、完整的代码四、总结 本篇博客介绍一个关于学生成绩管理系统的C语言代码&#xff0c;包含读取成绩、计算各门课程的总分和平均分、按分数排序、按…

快速实现一个分布式定时器

定时器&#xff08;Timer&#xff09;是一种在业务开发中常用的组件&#xff0c;主要用在执行延时通知任务上。本文以笔者在工作中的实践作为基础&#xff0c;介绍如何使用平时部门最常用的组件快速实现一个业务常用的分布式定时器服务。同时介绍了过程中遇到问题的一些解决方案…

手机越狱:探索自由与风险的边界

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。&#x1f60a; 座右铭&#xff1a;不想…

100天精通Golang(基础入门篇)——第5天: Go语言中的数据类型学习

&#x1f337; 博主 libin9iOak带您 Go to Golang Language.✨ &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &#x1f30a; 《I…