IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> <位图(bitset)和布隆过滤器(BloomFilter)>——《C++高阶》 -> 正文阅读

[C++知识库]<位图(bitset)和布隆过滤器(BloomFilter)>——《C++高阶》

目录

1. 哈希的应用

1.1 位图

1.1.1 位图概念

1.1.2 位图的实现

1.1.3 位图的应用

1.2 布隆过滤器

1.2.1 布隆过滤器提出

1.2.2布隆过滤器概念

?1.2.3 布隆过滤器的插入

1.2.4 布隆过滤器的查找

1.2.5 布隆过滤器删除

1.2.6 布隆过滤器优点

1.2.7 布隆过滤器缺陷

2. 海量数据处理类题目解析

2.1 哈希切割

2.2 位图应用

2.3 布隆过滤器

3.位图与布隆过滤器完整源码:

3.1位图模拟实现源码:

3.2?布隆过滤器模拟实现源码:

3.3?附用的unordered_set、unordered_map、OpenHash:

3.4 test.cpp:?

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?——By 作者:新晓·故知


1. 哈希的应用

1.1 位图

1.1.1 位图概念

1. 面试题

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

这40亿个数中。【腾讯】

分析:不能使用set/unordered_set,因为内存不够。也不能使用外排序+二分查找,因为不能很好的支持快速随机访问!

1. 遍历,时间复杂度O(N)

2. 排序(O(NlogN)),利用二分查找: logN

3. 位图解决

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一

个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0

代表不存在。比如:

直接定址法哈希——用一个比特位标识映射值在不在!

2. 位图概念

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

来判断某个数据存不存在的。

1.1.2 位图的实现

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;
};

1.1.3 位图的应用

1. 快速查找某个数据是否在一个集合中

2. 排序 + 去重

3. 求两个集合的交集、并集等

4. 操作系统中磁盘块标记

1.2 布隆过滤器

1.2.1 布隆过滤器提出

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

1. 用哈希表存储用户记录,缺点:浪费空间

2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。

3. 将哈希与位图结合,即布隆过滤器

1.2.2布隆过滤器概念

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

?1.2.3 布隆过滤器的插入

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;
 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;
};

1.2.4 布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特

位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。

1.2.5 布隆过滤器删除

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

比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

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

缺陷:

1. 无法确认元素是否真正在布隆过滤器中

2. 存在计数回绕

1.2.6 布隆过滤器优点

1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关

2. 哈希函数相互之间没有关系,方便硬件并行运算

3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势

5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

1.2.7 布隆过滤器缺陷

1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)

2. 不能获取元素本身

3. 一般情况下不能从布隆过滤器中删除元素

4. 如果采用计数方式删除,可能会存在计数回绕问题

2. 海量数据处理类题目解析

2.1 哈希切割

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

与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

2.2 位图应用

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

分析:使用两个比特位,标识3种状态。出现0次:00? 出现1次:01? 出现2次及以上:10

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

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

注:放在位图找交集的意义是先去重。

?位图的优点:

1.节省空间

2.处理快速

缺点:只能针对整型映射

2.3 布隆过滤器

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

精确算法和近似算法

2. 如何扩展BloomFilter使得它支持删除元素的操作

布隆过滤器映射的位数越多,冲突的概率越小

布隆过滤器的底层就是一个位图
布隆过滤器支持删除,但删除会影响其他位值,因此会付出很大的代价。删除的方式是计数。

?

3.位图与布隆过滤器完整源码:

因为涉及到附用unordered_set、unordered_map,所以这里给出完整的源码。

3.1位图模拟实现源码:

bitset:

位图1:

#pragma once
#include <vector>
//位图1
namespace my
{
	// N个比特位的位图  10  16
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			// +1保证足够比特位,最多浪费8个
			_bits.resize(N / 8 + 1, 0);
		}

		//x映射的位标记成1
		void set(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

		void reset(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

		bool test(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

	private:
		std::vector<char> _bits;
		//vector<int> _bits;
	};

	template<size_t N>
	class two_bitset
	{
	public:
		void set(size_t x)
		{
			int in1 = _bs1.test(x);
			int in2 = _bs2.test(x);
			if (in1 == 0 && in2 == 0)
			{
				_bs2.set(x);
			}
			else if (in1 == 0 && in2 == 1)
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
		}

		bool is_once(size_t x)
		{
			return _bs1.test(x) == 0 && _bs2.test(x) == 1;
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
}

?位图2:

#pragma once
#include <vector>
//位图2
namespace my
{
	// N个比特位的位图  10  16
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			// +1保证足够比特位,最多浪费8个
			_bits.resize(N / 8 + 1, 0);
		}

		//x映射的位标记成1
		void set(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

		void reset(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

		bool test(size_t x)
		{
			// x映射的比特位在第几个char对象
			size_t i = x / 8;

			// x在char第几个比特位
			size_t j = x % 8;

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

	private:
		std::vector<char> _bits;
		//vector<int> _bits;
	};

	template<size_t N>
	class two_bitset
	{
	public:
		void set(size_t x)
		{
			int in1 = _bs1.test(x);
			int in2 = _bs2.test(x);
			//00
			if (in1 == 0 && in2 == 0)
			{
				_bs2.set(x);  //01
			}
			//01
			else if (in1 == 0 && in2 == 1)
			{
				_bs1.set(x);  //10
				_bs2.reset(x);
			}
			//10
			else if (in1 == 1 && in2 == 0)
			{
				_bs2.set(x);  //11
			}
		}
		bool is_once(size_t x)
		{
			return _bs1.test(x) == 0 && _bs2.test(x) == 1;
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
}

3.2?布隆过滤器模拟实现源码:

BloomFilter:

3.3?附用的unordered_set、unordered_map、OpenHash:

unordered_set:

#include"OpenHT.h"

namespace my
{
	template<class K, class HashFunc = DefaultHash<K>>
	class unordered_set
	{
		//内部类
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}
		//bool insert(const K& key)
		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		Bucket::HashTable<K, K, SetKeyOfT,HashFunc> _ht;
	};
	
}

unordered_map:

#include"OpenHT.h"

namespace my
{
	template<class K, class V, class HashFunc = DefaultHash<K>>
	class unordered_map
	{
		//内部类
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
	public:
		//定义map的迭代器
		typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> ::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}
		//bool insert(const pair<K, V>& kv)
		pair<iterator,bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);

		}
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
		V& operator[](const K& key)
		{
			//pair<iterator, bool> ret = insert(make_pair(key, V()));
			auto ret = insert(make_pair(key, V()));
			return ret.first->second;  //first是迭代器,而map里面访问second要用->,其实这里有两个->,但为了可读性
		}
	private:
		Bucket::HashTable<K, pair<K, V>, MapKeyOfT,HashFunc> _ht;
	};
}

OpenHash:

#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<unordered_map>
#include<unordered_set>
using namespace std;

//仿函数
template<class K>
struct DefaultHash        //1.普通类直接强转
{
	size_t operator()(const K& key)
	{
		return(size_t)key; //支持取模,强转为整数
	}
};
template<>
struct DefaultHash<string>     //String类特化
{
	size_t operator()(const string& key)
	{
		//4.BKDR法
		size_t hash = 0;
		for (auto ch : key)
		{
			hash = hash * 131 + ch;
		}
		return hash;
	}
};
namespace Bucket
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable;

	template<class K, class T, class KeyOfT, class HashFunc>
	class __HTIterator   //单向迭代器
	{
		typedef HashNode<T> Node;
		typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
	public:
		Node* _node;
		HashTable<K, T, KeyOfT, HashFunc>* _pht;
		__HTIterator(){}   //默认哈希迭代器构造函数
		__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			,_pht(pht)
		{}

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				HashFunc hf;
				size_t hashi = hf(kot(_node->_data))%_pht->_tables.size();
				++hashi;
				//找下一个不为空的桶
				for (; hashi < _pht->_tables.size(); ++hashi)
				{
					if (_pht->_tables[hashi])
					{
						_node = _pht->_tables[hashi];
						break;
					}
				}

				// 没有找到不为空的桶,用nullptr去做end标识
				if (hashi == _pht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

		T& operator*()
		{
			return _node->_data;
		}

		T* operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};

	//unordered_set--->HashTable<K, K, SetKeyOfT> _ht;
	//unordered_map--->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable
	{
		template<class K, class T, class KeyOfT, class HashFunc>
		friend class __HTIterator;
		typedef HashNode<T> Node;
	public:
		typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;
		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return iterator(cur, this);
				}
			}

			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}
		//手动实现Node的析构函数
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		
		///
		HashTable() = default;  //强制生成默认构造函数

		链表桶的深拷贝
		//HashTable(const HashTable<K, V> ht)
		//{
		//	int sz = ht._tables.size();
		//	for (int i = 0; i < sz; i++)
		//	{
		//		Node* cur = ht._tables[i];
		//		while (cur)
		//		{
		//			this->Insert(cur->_kv);
		//			cur = cur->_next;
		//		}
		//	}
		//	_n = ht._n;

		//}
		链表桶的赋值重载
		//HashTable<K, V>& operator=(HashTable<K, V> ht)
		//{
		//	//现代写法
		//	ht._tablle.swap(_tables);
		//	_n = ht._n;
		//}
		///
		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			static const size_t primeList[PRIMECOUNT] =
			{
				53ul, 97ul, 193ul, 389ul, 769ul,
				1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
				49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
				1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
				50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
				1610612741ul, 3221225473ul, 4294967291ul
			};

			// 获取比prime大那一个素数
			size_t i = 0;
			for (; i < PRIMECOUNT; ++i)
			{
				if (primeList[i] > prime)
					return primeList[i];
			}

			return primeList[i];
		}
		//bool Insert(const T& data)
		pair<iterator,bool> Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;
			//if (Find(kot(data)))  //使用仿函数,不知道data是set还是map
			//if (Find(kot(data))!=end())  //使用仿函数,不知道data是set还是map

			iterator pos = Find(kot(data));
			if(pos!=end())
			{
				//return false;
				return make_pair(pos,false);
			}

			//负载因子(这里设置为1)  扩容
			if (_tables.size() == _n)
			{
				//写法2:扩容
				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				//写法3:
				size_t newSize = GetNextPrime(_tables.size());
				if (newSize != _tables.size())
				{
					vector<Node*> newTable;
					newTable.resize(newSize, nullptr);

					for (size_t i = 0; i < _tables.size(); ++i)
					{
						Node* cur = _tables[i];
						while (cur)
						{
							size_t hashi = hf(kot(cur->_data)) % newSize;  //hf转换为整型,kot()取k
							cur->_next = newTable[hashi];
							newTable[hashi] = cur;
							cur = cur->_next;
						}
						_tables[i] = nullptr;
					}
					newTable.swap(_tables);

				}
			}
			size_t hashi = hf(kot(data)); //两层仿函数调用
			hashi %= _tables.size();
			//头插到对应的桶即可
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			//return true;
			return make_pair(iterator(newnode,this),false);
		}
		//Node* Find(const K& key)
		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				//return nullptr;
				return iterator(nullptr,this);

			}
			
			KeyOfT kot;
			//写法1:有名对象
			HashFunc hf;
			size_t hashi = hf(key);

			//写法2:匿名对象
			//size_t hashi = HashFunc()(key);

			hashi %= _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					//return cur;
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			//return nullptr;  //效率高,是因为直接返回了指针
			return iterator(nullptr, this);
		}
		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}
			KeyOfT kot;

			//写法1:有名对象
			HashFunc hf;
			size_t hashi = hf(key);
			hashi %= _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
					
				}
				cur = cur->_next;
			}
			return false;
		}
	private:
		//指针数组
		//没有使用list,是因为list是双向链表,成本大 vector<list<>> _tables; 但不用手动写析构函数
		vector<Node*> _tables;    
		//开散列采用挂起,如果新表扩容,那么当旧表释放,vector会将自己的释放,
		//但是挂在vector的结点Node* 不会自动释放,因为Node* 是内置类型,需要手动释放。
		size_t _n = 0;
	};
}

3.4 test.cpp:

#include"OpenHT.h"
#include"UnorderedSet.h"
#include"UnorderedMap.h"
#include"BitSet.h"
#include"BloomFilter.h"
//
//1.库中的UnorderedSet、UnorderedMap测试
void test_std_UnorderedSet()
{
	std::unordered_set<int> s;
	s.insert(9);
	s.insert(7);
	s.insert(2);
	s.insert(1);
	s.insert(6);
	s.insert(5);
	//1.迭代器遍历
	//unordered_set<int>::iterator it = s.begin();
	//auto it = s.begin();
	//写法2:
	std::unordered_set<int>::iterator it;
	it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//2.范围for遍历
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}
void test_std_UnorderedMap()
{
	std::unordered_map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("left", "左边"));
	dict.insert(make_pair("left", "剩余"));
	dict["string"];
	dict["left"] = "剩余";
	dict["string"] = "字符串";
	//迭代器遍历
	//std::unordered_map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << " " << it->second << endl;
		++it;
	}
	cout << endl;
	//范围for遍历
	for (auto& kv : dict)
	{
		cout << kv.first << " " << kv.second << endl;
	}
}
//
//2.模拟实现UnorderedSet、UnorderedMap测试
void test_my_UnorderedSet()
{
	my::unordered_set<int> s;
	s.insert(9);
	s.insert(7);
	s.insert(2);
	s.insert(1);
	s.insert(6);
	s.insert(5);
	//1.迭代器遍历
	//my::unordered_set<int>::iterator it = s.begin();
	//auto it = s.begin();
	//写法2:
	my::unordered_set<int>::iterator it;
	it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//2.范围for遍历
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}
void test_my_UnorderedMap()
{
	my::unordered_map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("left", "左边"));
	dict.insert(make_pair("left", "剩余"));
	dict["string"];
	dict["left"] = "剩余";
	dict["string"] = "字符串";
	//迭代器遍历
	//my::unordered_map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << " " << it->second << endl;
		++it;
	}
	cout << endl;
	//范围for遍历
	for (auto& kv : dict)
	{
		cout << kv.first << " " << kv.second << endl;
	}
}
//
//3.模拟实现bitset测试
void test_my_bit_set()
{
	my::bitset<100> bs;
	bs.set(18);
	bs.set(19);

	cout << bs.test(18) << endl;
	cout << bs.test(19) << endl;
	cout << bs.test(20) << endl;


	bs.reset(18);
	bs.reset(18);

	cout << bs.test(18) << endl;
	cout << bs.test(19) << endl;
	cout << bs.test(20) << endl;

	//my::bitset<INT_MAX> bigBS;
	my::bitset<0xFFFFFFFF> bigBS;
	//my::bitset<-1> bigBS;



	//bit_set<2^32-1> bigBS; // pow
}

//4.模拟实现two_bitset
void test_my_two_bitset()
{
	int a[] = { 4, 3, 2, 4, 5, 2, 2, 4, 7, 8, 9, 2, 1,3,7 };
	my::two_bitset<10> tbs;
	for (auto e : a)
	{
		tbs.set(e);
	}

	for (size_t i = 0; i < 10; ++i)
	{
		if (tbs.is_once(i))
		{
			cout << i << " ";
		}
	}
	cout << endl;
}

//5.模拟实现BloomFilter测试
void test_my_BloomFilter1()
{
	//插入10个数据
	//写法1:43是计算的值(具体参见文档)
	//my::BloomFilter<string, 43, my::BKDRHash, my::APHash, my::DJBHash> bf;
	//写法2:43是计算的值(具体参见文档),因为模板给了缺省值,因此这里只传第一个数据类型即可
	my::BloomFilter<43> bf;

	string a[] = { "铭记历史","勿忘国耻","振兴中华","吾辈自强","1931.09.18","九*一八事变","2022.09.18"};
	for (auto e : a)
	{
		cout << e <<" ";
	}
	cout << endl;
	
	for (auto& e : a)
	{
		bf.Set(e);
	}
	for (auto& e : a)
	{
		cout << bf.Test(e) << endl;
	}
	cout << bf.Test("缅怀先烈") << endl;
	cout << bf.Test("吾辈自强") << endl;
	cout << bf.Test("砥砺前行") << endl;
}
//6.模拟实现布隆过滤器测试对比误判率
void test_my_BloomFilter2()
{
		srand(time(0));
		//给不同的值,观察误判率
		//一般来说,空间越大,冲突率越小
		const size_t N = 10000;
	  /*const size_t N = 60000;
		const size_t N = 100000;*/
	  /*my::BloomFilter<5 * N> bf;
		my::BloomFilter<6 * N> bf;*/
		my::BloomFilter<8 * N> bf;



		std::vector<std::string> v1;
		std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";

		for (size_t i = 0; i < N; ++i)
		{
			v1.push_back(url + std::to_string(1234 + i));
		}

		for (auto& str : v1)
		{
			bf.Set(str);
		}
		//打印判断的key的bool返回值(1或0)
		/*for (auto& str : v1)
		{
		cout << bf.Test(str) << endl;
		}
		cout << endl << endl;*/
		//给一个相似的字符串
		std::vector<std::string> v2;
		for (size_t i = 0; i < N; ++i)
		{
			std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
			url += std::to_string(999999 + i);
			v2.push_back(url);
		}
		//判断并统计。如果在,就是误判
		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";
			url += std::to_string(rand());
			v3.push_back(url);
		}

		size_t n3 = 0;
		for (auto& str : v3)
		{
			if (bf.Test(str))
			{
				++n3;
			}
		}
		cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}
int main()
{
	//test_std_UnorderedSet();
	//test_std_UnorderedMap();

	//test_my_UnorderedSet();
	//test_my_UnorderedMap();

	//test_my_bit_set();
	//test_my_two_bitset();

	//test_my_BloomFilter1();
	test_my_BloomFilter2();

	return 0;
}

后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?——By 作者:新晓·故知

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:08:17  更:2022-09-21 00:08:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 12:09:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码