【C++】哈希的应用

位图

腾讯面试题:给40亿个不重复的无符号整数,没排过序,给一个无符号整数,如何快速判断一个数是否在40亿个数中。

  • 遍历??时间复杂度为O(N)
  • set使用红黑树??

40亿个整数等于160亿byte,1G等于1024MB等于1024*1024kb等于1024*1024*1024byte,换算过来1G等于10亿byte,那么160亿byte大约等于16G。这种情况也仅仅只是使用顺序表来存储,如果使用容器(红黑树),那么内存消耗会更大。

可以利用哈希的直接定址法,如果需要确定的值有42亿个值,可以通过2^32bit位置来确定对应值是否存在,1个字节有8个bit位也就是可以确定2^3,此时42亿个数据需要2^29字节,即500MB的内存空间即可。

通过在顺序表中保存int的值,一个int有4个字节也就是代表32个值,如果需要存储例如80,只需要用80 / 32 = 2,也就是80在下标为2的int值中的第80%32的bit位置。

【科普】计算机底层存储可能是按照大端机,也可能是小端机存储方式。

cpp 复制代码
#pragma once
#include <vector>
#include <iostream>

using namespace std;

template<size_t N>
class BitSet
{
public:
	BitSet()
	{
		_a.resize(N / 32 + 1);
	}

	// 将x映射的值标记成1
	void set(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;
		_a[i] |= (1 << j);
	}

	// 将x映射的值标记成0
	void reset(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;
		_a[i] &= ~(1 << j);
	}
	
	// 检查一个值在不在
	bool Test(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;
		return _a[i] & (1 << j);
	}

private:
	vector<int> _a;
};

库里面实现的位图bitset

位图相关问题:

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

【解释】

方法1:改造位图,将两个bit位表示,00表示没有出现,01表示出现一次,10表示出现2次及2次以上。

方法2:使用库中位图结构,设计两个位图,表示一个整数。

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

【解释】设计两个位图结构,对应的位置进行&。对应位置都为1,那么这个值就是交集。

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

【解释】设计两个位图结构,00表示没有出现,01表示出现一次,10表示出现2次,11表示2次以上。

布隆过滤器

引入布隆过滤器

在使用抖音、新闻等客户端看内容时,会不停地推荐新的内容,而在每次推荐时都会进行去重,去掉已经看过的内容。此时的问题是,这些客户端推荐系统是如何实现推送去重的?

【解释】使用服务器记录用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录中进行筛选,过滤掉那些已经存在的记录。

++问题:那么如何快速查找?++

【目前已知的方式】

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

布隆过滤器的概念

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

将一个字符串使用多种方式映射到不同的位置上,只有当一个字符串进行查询时,每一个方式都可以对应上即说明这个字符串是对应的字符串,但是也会出现误判的情况,但是此时的误判率大大降低。

  • 此时字符串存在:误判率较低
  • 此时字符串不存在:准确
cpp 复制代码
#pragma once
#include <iostream>
#include <bitset>

using namespace std;

struct BKDPHash
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash = hash * 131 + ch;
		}
		return hash;
	}
};

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

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

		return hash;
	}
};

template<size_t N, 
	class K = string, 
	class Hash1 = BKDPHash, 
	class Hash2 = APHash, 
	class Hash3 = DJBHash>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		size_t hash2 = Hash2()(key) % N;
		size_t hash3 = Hash3()(key) % N;

		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

	// 布隆过滤器一般不支持reset

	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		size_t hash2 = Hash2()(key) % N;
		size_t hash3 = Hash3()(key) % N;

		if (_bs.test(hash1) == false) return false;
		if (_bs.test(hash2) == false) return false;
		if (_bs.test(hash3) == false) return false;

		return true; // 存在误判
	}


private:
	bitset<N> _bs;
};

应用场景:

1.不需要精确的场景,例如快速判断昵称是否注册过(容忍误判)。如果需要精确的场景,使用布隆过滤器当作筛板,如果不在直接返回,如果存在进一步到数据库查找并精准判断。

布隆过滤器的场景问题:

1.给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法?

【答】近似算法使用布隆过滤器,精确算法使用哈希切分。

2.如何扩展BlookFilter使得其支持删除元素的操作?

【答】多个位标识一个值,使用引用计数。

相关推荐
血小板要健康2 小时前
如何计算时间复杂度(上)
java·数据结构·算法
古城小栈2 小时前
Rust Vec与HashMap全功能解析:定义、使用与进阶技巧
算法·rust
wWYy.2 小时前
详解哈希表
数据结构·算法·散列表
无望__wsk2 小时前
Python第一次作业
开发语言·python·算法
Lips6112 小时前
2026.1.25力扣刷题笔记
笔记·算法·leetcode
源代码•宸3 小时前
Leetcode—746. 使用最小花费爬楼梯【简单】
后端·算法·leetcode·职场和发展·golang·记忆化搜索·动规
南 阳3 小时前
Python从入门到精通day16
开发语言·python·算法
沉默-_-3 小时前
力扣hot100-子串(C++)
c++·学习·算法·leetcode·子串
jiaguangqingpanda3 小时前
Day29-20260125
java·数据结构·算法