当前位置:   article > 正文

【C++】哈希的应用:位图、哈希切分与布隆过滤器_c++ 写print once

c++ 写print once


需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。


 目录

一、位图

1、位图的概念

2、大厂面试题

2.1位图应用(腾讯)

2.2位图应用

3、位图的优缺点

二、哈希切分

三、布隆过滤器

1、布隆过滤器的概念

2、布隆过滤器的应用场景

3、布隆过滤器的删除

4、布隆过滤器的优缺点

5、布隆过滤器面试题

6、布隆过滤器的实现


一、位图

1、位图的概念

        所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来标记某个数据在或不在,它解决不了哪个数据出现次数最多的问题。

2、大厂面试题

2.1位图应用(腾讯)

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

        开一个位图,使用哈希的直接定址法,值是几,就把位图中的比特位标记成1,仅占用512M空间。

        但我们不能按照比特位来开辟空间,所以使用char或int等内置类型进行空间的开辟:

仿写bitset:

  1. #pragma once
  2. #include <iostream>
  3. #include <vector>
  4. namespace jly
  5. {
  6. template <size_t N>
  7. class bitset
  8. {
  9. public:
  10. bitset()
  11. {
  12. _bits.resize(N / 8 + 1, 0);
  13. }
  14. public:
  15. void set(size_t x)//将某个比特位标记为1
  16. {
  17. size_t i = x / 8;//算出x位于哪个字节
  18. size_t j = x % 8;//算出x位于该字节的哪一位
  19. _bits[i] |= (1 << j);
  20. }
  21. void reset(size_t x)//将某个比特位标记为0
  22. {
  23. size_t i = x / 8;
  24. size_t j = x % 8;
  25. _bits[i] &= (~(1 << j));
  26. }
  27. bool test(size_t x)//测试这个值在不在位图中
  28. {
  29. size_t i = x / 8;
  30. size_t j = x % 8;
  31. return _bits[i] & (1 << j);
  32. }
  33. private:
  34. std::vector<char> _bits;
  35. };
  36. void test_bitset()
  37. {
  38. bitset<-1> bs;
  39. }
  40. }

        这是一种又快又省空间的办法,也是面试官最想听到的回答。

        但个人认为如果将题目要求的40亿数字全部录入位图中,等于遍历了一遍40亿个数字,既然都遍历一遍原数据了,那还不如在遍历的时候直接比对呢,对吧,相比之下直接比对数据连512M的位图都不用开。

2.2位图应用

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

        可以认为这里的整数的最大值为unsigned int的最大值,一个整数共有三种状态:00,01,02,分别代表不存在,出现一次,出现两次及以上,代码如下:

  1. template <size_t N>
  2. class twobitset
  3. {
  4. public:
  5. void set(size_t x)
  6. {
  7. if (_bs1.test(x))//01->10
  8. {
  9. _bs1.reset(x);
  10. _bs2.set(x);
  11. }
  12. else if (_bs1.test(x)==false&&_bs2.test(x)==false)//00->01
  13. {
  14. _bs1.set(x);
  15. }
  16. }
  17. void PrintOnce()
  18. {
  19. for (size_t i = 0; i < N; ++i)
  20. {
  21. if (_bs1.test(i) && !_bs2.test(i))
  22. {
  23. std::cout << i << std::endl;
  24. }
  25. }
  26. }
  27. private:
  28. std::bitset<N> _bs1;
  29. std::bitset<N> _bs2;
  30. };
  31. void test()
  32. {
  33. twobitset<100> tbs;
  34. int a[] = { 3,5,6,3,5,8,9,4,3,6,9,4 };
  35. for (auto& e : a)
  36. {
  37. tbs.set(e);
  38. }
  39. tbs.PrintOnce();//打印8
  40. }

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

        和第一问类似,开两个位图,分别将两组数据映射进位图,两个位图对应的比特位均为1即为交集。

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

        同第一问,开两个位图,00代表不存在,01代表出现一次,10代表出现两次,11代表出现两次以上。

3、位图的优缺点

        优点:节省空间,查找速度快

        缺点:要求范围相对集中,范围特别分散的,空间消耗大;位图只对整型使用,浮点数、string等其他类型无法使用。

        如果要判断其他类型,该类型如果可以使用哈希函数转为整型的,可以考虑下布隆过滤器哈(见下文布隆过滤器的介绍)。

二、哈希切分

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

如果Ai冲突桶超过1G怎么办?

1、这个桶冲突的IP很多,大多都是不重复的,map统计不下;

2、这个桶冲突的IP很多,大多都是重复的,map可以统计;

        直接使用map中的insert将每一个冲突桶的元素插入到map中。情况一:如果insert插入失败,说明空间不足,new节点失败,抛出异常。解决方法是换个哈希函数,递归再次对这个冲突桶进行切分。情况二:map可以正常统计。

三、布隆过滤器

1、布隆过滤器的概念

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

        既然这种方法能判断某个元素一定不存在,那么如何降低“误判”(映射为1的概率)的概率,提升准确判定(映射为0的概率)的概率呢?

        解决方法就是对同一个元素使用多组哈希函数进行映射,它能降低误判率,但是增加了空间消耗。使用时需要把控好布隆过滤器的哈希函数的个数布隆过滤器的长度。公式为k=(m/n)*lg2【k:哈希函数的个数;m:布隆过滤器的长度;n:插入元素的个数】(选型可参照本文)

2、布隆过滤器的应用场景

1、不需要一定准确的场景,例如个人网站注册时候的昵称判重,使用布隆过滤器可以判断某个昵称一定没有被使用过,但会误判某些造成冲突但没有被使用的昵称。

2、提高效率。例如客户端查找信息时,先用布隆过滤器筛一下,如果不在,则直接将未查到的信息反馈给客户端;如果布隆过滤器发现查找信息与位图匹配,则将需要查找的信息推送给服务器中的数据库进行精确查找。

3、布隆过滤器的删除

        单纯的布隆过滤器是不支持删除的,因为一个比特位可能被多个元素所映射。如果非要在布隆过滤器中实现reset,那就只能将位图结构修改为计数器结构。数据set时,每被映射一次,计数器加1,reset时,该位计数器-1,直到该位计数器为0。毫无疑问,这种操作所需的空间消耗急剧增加。

4、布隆过滤器的优缺点

优点:

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

缺点:

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

5、布隆过滤器面试题

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

        近似算法:使用布隆过滤器,先将其中一个文件set进布隆过滤器中,再将另一个文件的数据进行比对,可以淘汰一定不是交集的那部分,不过余下的那部分数据中,仍会有非交集的存在。

        精确算法:使用哈希切分,将两个大文件分别切成一个个小文件A0-A99,B0-B99(单个小文件超过1G参照上文哈希切分对于此问题的解决方法);因为使用的是相同的哈希函数,所以交集必定存在于A0和B0,A1和B1这种相同下标的小文件中。可以先将A0存放至哈希表中,B0去重后与哈希表比对,就能够精确得到交集。

6、布隆过滤器的实现

  1. #pragma once
  2. #include <iostream>
  3. #include <bitset>
  4. #include <string>
  5. using namespace std;
  6. struct BKDRHash
  7. {
  8. size_t operator()(const std::string& key)
  9. {
  10. size_t hash = 0;
  11. for (auto& ch : key)
  12. {
  13. hash *= 131;
  14. hash += ch;
  15. }
  16. return hash;
  17. }
  18. };
  19. struct APHash
  20. {
  21. size_t operator()(const std::string& key)
  22. {
  23. unsigned int hash = 0;
  24. int i = 0;
  25. for (auto ch : key)
  26. {
  27. if ((i & 1) == 0)
  28. {
  29. hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
  30. }
  31. else
  32. {
  33. hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
  34. }
  35. ++i;
  36. }
  37. return hash;
  38. }
  39. };
  40. struct DJBHash
  41. {
  42. size_t operator()(const std::string& key)
  43. {
  44. unsigned int hash = 5381;
  45. for (auto ch : key)
  46. {
  47. hash += (hash << 5) + ch;
  48. }
  49. return hash;
  50. }
  51. };
  52. struct JSHash
  53. {
  54. size_t operator()(const std::string& s)
  55. {
  56. size_t hash = 1315423911;
  57. for (auto ch : s)
  58. {
  59. hash ^= ((hash << 5) + ch + (hash >> 2));
  60. }
  61. return hash;
  62. }
  63. };
  64. //N为最大存储的个数,X为存一个值,需要开辟的比特位
  65. template <size_t N,size_t X=5,class K=std::string,
  66. class HashFunc1= BKDRHash,
  67. class HashFunc2= APHash,
  68. class HashFunc3= DJBHash>
  69. class BloomFilter
  70. {
  71. public:
  72. void set(const K& key)
  73. {
  74. size_t hash1 = HashFunc1()(key) % (X * N);
  75. size_t hash2 = HashFunc2()(key) % (X * N);
  76. size_t hash3 = HashFunc3()(key) % (X * N);
  77. _bs.set(hash1);
  78. _bs.set(hash2);
  79. _bs.set(hash3);
  80. }
  81. bool test(const K& key)
  82. {
  83. size_t hash1 = HashFunc1()(key) % (X * N);
  84. size_t hash2 = HashFunc2()(key) % (X * N);
  85. size_t hash3 = HashFunc3()(key) % (X * N);
  86. return _bs.test(hash1) && _bs.test(hash2) && _bs.test(hash3);
  87. }
  88. private:
  89. std::bitset<X*N> _bs;
  90. };
  91. void test_bloomfilter1()
  92. {
  93. // 10:46继续
  94. string str[] = { "a", "s", "d", "w", "a1","1a","白1a","c11a","1a1" };
  95. BloomFilter<10> bf;
  96. for (auto& str : str)
  97. {
  98. bf.set(str);
  99. }
  100. for (auto& s : str)
  101. {
  102. cout << bf.test(s) << endl;
  103. }
  104. cout << endl;
  105. srand((unsigned int)time(0));
  106. for (const auto& s : str)
  107. {
  108. cout << bf.test(s + to_string(rand())) << endl;
  109. }
  110. }
  111. void test_bloomfilter2()
  112. {
  113. srand((unsigned int)time(0));
  114. const size_t N = 100000;
  115. BloomFilter<N> bf;
  116. std::vector<std::string> v1;
  117. std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
  118. for (size_t i = 0; i < N; ++i)
  119. {
  120. v1.push_back(url + std::to_string(i));
  121. }
  122. for (auto& str : v1)
  123. {
  124. bf.set(str);
  125. }
  126. // v2跟v1是相似字符串集,但是不一样
  127. std::vector<std::string> v2;
  128. for (size_t i = 0; i < N; ++i)
  129. {
  130. std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
  131. url += std::to_string(999999 + i);
  132. v2.push_back(url);
  133. }
  134. size_t n2 = 0;
  135. for (auto& str : v2)
  136. {
  137. if (bf.test(str))
  138. {
  139. ++n2;
  140. }
  141. }
  142. cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;
  143. // 不相似字符串集
  144. std::vector<std::string> v3;
  145. for (size_t i = 0; i < N; ++i)
  146. {
  147. string url = "zhihu.com";
  148. url += std::to_string(i + rand());
  149. v3.push_back(url);
  150. }
  151. size_t n3 = 0;
  152. for (auto& str : v3)
  153. {
  154. if (bf.test(str))
  155. {
  156. ++n3;
  157. }
  158. }
  159. cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
  160. }
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号