news 2026/6/9 20:03:26

32.C++进阶:哈希的应用|位图|布隆过滤器|海量数据面试题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
32.C++进阶:哈希的应用|位图|布隆过滤器|海量数据面试题

位图

位图概念
  1. 面试题
    给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】

    1. 遍历,时间复杂度O(N)
    2. 排序(O(NlogN)),利用二分查找: logN
    3. 位图解决
      数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:
  2. 位图概念
    所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图的实现
namespace bit { template<size_t N> class bitset { public: bitset(size_t bitCount) : _bit((bitCount>>5)+1), _bitCount(bitCount) {} // 将which比特位置1 void set(size_t which) { if(which > _bitCount) return; size_t index = (which >> 5); size_t pos = which % 32; _bit[index] |= (1 << pos); } // 将which比特位置0 void reset(size_t which) { if(which > _bitCount) return; size_t index = (which >> 5); size_t pos = which % 32; _bit[index] &= ~(1 << pos); } // 检测位图中which是否为1 bool test(size_t which) { if(which > _bitCount) return false; size_t index = (which >> 5); size_t pos = which % 32; return _bit[index] & (1<<pos); } // 获取位图中比特位的总个数 size_t size()const{ return _bitCount;} // 位图中比特为1的个数 size_t Count()const { int bitCnttable[256] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; size_t size = _bit.size(); size_t count = 0; for(size_t i = 0; i < size; ++i) { int value = _bit[i]; int j = 0; while(j < sizeof(_bit[0])) { unsigned char c = value; count += bitCntTable[c]; ++j; value >>= 8; } } return count; } private: vector<int> _bit; size_t _bitCount; }; void test_bitset() { bitset<100> bs1; bs1.set(50); bs1.set(30); bs1.set(90); for (size_t i = 0; i < 100; i++) { if (bs1.test(i)) { cout << i << "->" << "在" << endl; } else { cout << i << "->" << "不在" << endl; } } bs1.reset(90); bs1.set(91); cout << endl << endl; for (size_t i = 0; i < 100; i++) { if (bs1.test(i)) { cout << i << "->" << "在" << endl; } else { cout << i << "->" << "不在" << endl; } } bitset<-1> bs2; bitset<UINT_MAX> bs3; bitset<0xffffffff> bs4; } template<size_t N> class two_bit_set { public: void set(size_t x) { // 00 -> 01 if (_bs1.test(x) == false && _bs2.test(x) == false) { _bs2.set(x); } else if (_bs1.test(x) == false && _bs2.test(x) == true) { // 01 -> 10 _bs1.set(x); _bs2.reset(x); } } //int test(size_t x) //{ // if (_bs1.test(x) == false // && _bs2.test(x) == false) // { // return 0; // } // else if (_bs1.test(x) == false // && _bs2.test(x) == true) // { // return 1; // } // else // { // return 2; // 2次及以上 // } //} bool test(size_t x) { if (_bs1.test(x) == false && _bs2.test(x) == true) { return true; } return false; } private: bitset<N> _bs1; bitset<N> _bs2; }; void test_bitset2() { int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 }; two_bit_set<100> bs; for (auto e : a) { bs.set(e); } for (size_t i = 0; i < 100; i++) { //cout << i << "->" << bs.test(i) << endl; if (bs.test(i)) { cout << i << endl; } } } void test_bitset3() { int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 }; int a2[] = { 5,3,5,99,6,99,33,66}; bitset<100> bs1; bitset<100> bs2; for (auto e : a1) { bs1.set(e); } for (auto e : a2) { bs2.set(e); } for (size_t i = 0; i < 100; i++) { if (bs1.test(i) && bs2.test(i)) { cout << i << endl; } } } }
位图的应用
  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

布隆过滤器

布隆过滤器提出

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

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

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

布隆过滤器的插入

向布隆过滤器中插入:“baidu”

struct BKDRHash { size_t operator()(const string& s) { // BKDR size_t value = 0; for (auto ch : s) { value *= 31; value += ch; } return value; } }; 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; } }; struct DJBHash { size_t operator()(const string& s) { size_t hash = 5381; for (auto ch : s) { hash += (hash << 5) + ch; } return hash; } }; template<size_t N, size_t X = 5, class K = string, class HashFunc1 = BKDRHash, class HashFunc2 = APHash, class HashFunc3 = DJBHash> class BloomFilter { public: void Set(const K& key) { size_t len = X*N; //仿函数的匿名对象调用operator对象 size_t index1 = HashFunc1()(key) % len; size_t index2 = HashFunc2()(key) % len; size_t index3 = HashFunc3()(key) % len; /* cout << index1 << endl; cout << index2 << endl; cout << index3 << endl<<endl;*/ _bs.set(index1); _bs.set(index2); _bs.set(index3); } bool Test(const K& key) { size_t len = X*N; size_t index1 = HashFunc1()(key) % len; if (_bs.test(index1) == false) return false; size_t index2 = HashFunc2()(key) % len; if (_bs.test(index2) == false) return false; size_t index3 = HashFunc3()(key) % len; if (_bs.test(index3) == false) return false; return true; // 存在误判的 } // 不支持删除,删除可能会影响其他值。 void Reset(const K& key); private: bitset<X*N> _bs; };

使用库里的bitset避免开在对象栈帧里

#pragma once #include<bitset> #include<string> struct HashFuncBKDR { // BKDR size_t operator()(const string& s) { size_t hash = 0; for (auto ch : s) { hash *= 131; hash += ch; } return hash; } }; struct HashFuncAP { // AP size_t operator()(const string& s) { size_t hash = 0; for (size_t 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; } }; struct HashFuncDJB { // DJB size_t operator()(const string& s) { size_t hash = 5381; for (auto ch : s) { hash = hash * 33 ^ ch; } return hash; } }; template<size_t N, class K = string, class Hash1 = HashFuncBKDR, class Hash2 = HashFuncAP, class Hash3 = HashFuncDJB> class BloomFilter { public: void Set(const K& key) { size_t hash1 = Hash1()(key) % M; size_t hash2 = Hash2()(key) % M; size_t hash3 = Hash3()(key) % M; _bs->set(hash1); _bs->set(hash2); _bs->set(hash3); } bool Test(const K& key) { size_t hash1 = Hash1()(key) % M; if (_bs->test(hash1) == false) return false; size_t hash2 = Hash2()(key) % M; if (_bs->test(hash2) == false) return false; size_t hash3 = Hash3()(key) % M; if (_bs->test(hash3) == false) return false; return true; // 存在误判(有可能3个位都是跟别人冲突的,所以误判) } private: static const size_t M = 10 * N; //bit::bitset<M> _bs; std::bitset<M>* _bs = new std::bitset<M>; }; void TestBloomFilter1() { string strs[] = { "百度","字节","腾讯" }; BloomFilter<10> bf; for (auto& s : strs) { bf.Set(s); } for (auto& s : strs) { cout << bf.Test(s) << endl; } for (auto& s : strs) { cout << bf.Test(s+'a') << endl; } cout << bf.Test("摆渡") << endl; cout << bf.Test("百渡") << endl; } void TestBloomFilter2() { srand(time(0)); const size_t N = 10000000; BloomFilter<N> bf; std::vector<std::string> v1; //std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html"; //std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535"; std::string url = "猪八戒"; for (size_t i = 0; i < N; ++i) { v1.push_back(url + std::to_string(i)); } for (auto& str : v1) { bf.Set(str); } // v2跟v1是相似字符串集(前缀一样),但是后缀不一样 std::vector<std::string> v2; for (size_t i = 0; i < N; ++i) { std::string urlstr = url; urlstr += std::to_string(9999999 + i); v2.push_back(urlstr); } size_t n2 = 0; for (auto& str : v2) { if (bf.Test(str)) // 误判 { ++n2; } } cout << "相似字符串误判率:" << (double)n2 / (double)N << endl; // 不相似字符串集 前缀后缀都不一样 std::vector<std::string> v3; for (size_t i = 0; i < N; ++i) { //string url = "zhihu.com"; string url = "孙悟空"; url += std::to_string(i + rand()); v3.push_back(url); } size_t n3 = 0; for (auto& str : v3) { if (bf.Test(str)) { ++n3; } } cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl; }
布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。

布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:

  1. 无法确认元素是否真正在布隆过滤器中
  2. 存在计数回绕
布隆过滤器优点
  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
布隆过滤器缺陷
  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

海量数据面试题

哈希切割

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

依次读取每个ip,i=HashFunc(ip)%100每个ip就进入Ai小文件。那么相同的ip就进入相同小文件
依次使用map<string, int> countMap统计每个文件ip出现的次数。
(如果map抛异常,爆了,那么说明冲突很多,小文件很大,换哈希函数,二次切分处理)

位图应用
  1. 给定100亿个整数,设计算法找到只出现一次的整数?
  2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
  3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
布隆过滤器
  1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法

Ai的query放set<string> seta
Bi的query放set<string> setbseta和setb找交集即可
query插入set里面的时候,抛异常
代表query太多,且重复不多
极端情况某个文件冲突很多
导致Ai域者Bi太大了,比如超过1G
换个哈希函数,对这个Ai和Bi文件再哈希切分
Ai和Bi小文件想象桶一样,重复和冲突的query都是进入相同的桶某个小文件太大,有两种可能,第一:相同的太多,这种读出来插入set去重了,不会影响 第二:冲突的太多,读出来插入set时会抛异常,需要二次处理,再换哈希函数,二次哈希切分
2. 如何扩展BloomFilter使得它支持删除元素的操作
每个位置改成多个位的引用计数就可以支持。比如一个映射位置给8个bit标记,但是这样空间消耗就大了

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 18:08:50

Qwen All-in-One日志系统:请求追踪与调试信息记录

Qwen All-in-One日志系统&#xff1a;请求追踪与调试信息记录 1. 为什么需要专为All-in-One设计的日志系统&#xff1f; 你有没有遇到过这样的情况&#xff1a; 刚部署好一个轻量级AI服务&#xff0c;界面点几下确实能跑通——输入“今天心情真好”&#xff0c;它秒回“&…

作者头像 李华
网站建设 2026/6/7 6:56:11

LlamaGen与NewBie-image-Exp0.1对比评测:谁更适合中小企业部署?

LlamaGen与NewBie-image-Exp0.1对比评测&#xff1a;谁更适合中小企业部署&#xff1f; 中小企业在选择AI图像生成方案时&#xff0c;往往面临一个现实困境&#xff1a;既要效果够好、能产出可用的商业素材&#xff0c;又不能陷入复杂的环境配置、漫长的调试周期和高昂的硬件投…

作者头像 李华
网站建设 2026/6/7 11:20:49

Open-AutoGLM进阶玩法:定时任务自动化实战

Open-AutoGLM进阶玩法&#xff1a;定时任务自动化实战 1. 为什么需要定时任务&#xff1f;——从“手动执行”到“自动值守” 你有没有过这样的经历&#xff1a; 每天早上8点要打开新闻App刷头条&#xff0c;结果赖床忘了&#xff1b;想蹲某款限量球鞋的秒杀&#xff0c;却总…

作者头像 李华
网站建设 2026/6/7 11:27:33

NewBie-image-Exp0.1社交应用案例:头像自动生成系统搭建教程

NewBie-image-Exp0.1社交应用案例&#xff1a;头像自动生成系统搭建教程 你是不是经常为社交平台换头像发愁&#xff1f;想用动漫风格但又不会画、不会PS&#xff0c;找人定制又贵又慢&#xff1f;今天这篇教程&#xff0c;就带你用一个预装好的AI镜像&#xff0c;从零开始搭起…

作者头像 李华
网站建设 2026/6/9 19:46:42

深入了解大数据领域数据可视化的底层逻辑

深入了解大数据领域数据可视化的底层逻辑:从“画图”到“翻译”的认知革命 1. 引入:为什么你做的可视化总被说“看不懂”? 凌晨三点,你盯着屏幕上的Excel表格——12个Sheet、300万行用户行为数据、27个维度的指标(PV、UV、转化率、复购率…),老板的要求很简单:“明天…

作者头像 李华
网站建设 2026/6/9 19:46:40

小白必看:用YOLOE镜像快速搭建实时检测系统

小白必看&#xff1a;用YOLOE镜像快速搭建实时检测系统 你有没有遇到过这样的场景&#xff1a;刚拿到一台新服务器&#xff0c;想马上跑通一个目标检测模型&#xff0c;结果卡在环境配置上——CUDA版本不对、PyTorch和torchvision不兼容、CLIP库编译失败、Gradio启动报错……折…

作者头像 李华