C++之模拟实现<unordered_set/map>及位图和布隆过滤器

🌈前言

本篇文章学习C++STL< unordered_map和unordered_set >的模拟实现!!!


🚁1、哈希表的改造

  1. 模板参数列表的改造
// 哈希桶节点
template <typename T>
struct HashNode
{
public:
	HashNode(const T& data)
		: _data(data)
		, _next(nullptr)
	{}
public:
	T _data;
	HashNode<T>* _next;
};

// Unordered_set -> HashTable<K, Key, KeyOfT, hashF>
// Unordered_map -> HashTable<K, pair<K, V>, KeyOfT, hashF>

template <typename K, typename T, typename KeyOfT, typename hashF>
class HashTable
{};

模板参数解析:

  • K:关键码(Key)的类型

  • T:不同的容器,T的类型就不相同。如果是unordered_map,T代表一个键值对,如果是unordered_set,T代表一个关键码(Key)

  • KeyOfT:因为V的类型不同,通过value取key的方式就不同,详细见unordered_map/set的实现

  • hashF: 哈希仿函数,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模


  1. 增加迭代器操作

迭代器:

  • 哈希桶的迭代器是单向的,因为它的底层是一个链表,只有指向下一个节点的指针

  • 迭代器需要访问哈希桶中的私有成员,需要将迭代器设置成友元类

  • 迭代器需要定义在哈希桶前面,但是迭代器需要访问哈希桶的私有成员,因为程序是从当前位置向上开始找哈希桶在不在,所有我们需要前置声明一下哈希桶类

namespace BUCKetHash
{
	// 前置声明,迭代器需要访问哈希桶类的私有成员
	template <typename K, typename T, typename KeyOfT, typename hashF>
	class HashTable;

	// 迭代器
	template <typename K, typename T, typename KeyOfT, typename hashF>
	class HTIterator
	{
	private:
		typedef HashNode<T> Node;					  // 哈希表节点
		typedef HashTable<K, T, KeyOfT, hashF> HT;	  // 哈希表
		typedef HTIterator<K, T, KeyOfT, hashF> Self; // 迭代器本身
	public:
		HTIterator(Node* _node, HT* _ht)
			: node(_node)
			, ht(_ht)
		{}

		HTIterator(const HTIterator& hti)
			: node(hti.node)
			, ht(hti.ht)
		{}

		Self& operator++()
		{
			// 如果传过来的节点指针不为空,则直接跳到下一个节点
			if (node->_next != nullptr)
			{
				node = node->_next;
			}
			// 当前节点为空,找下一个桶
			else
			{
				KeyOfT kot;
				hashF hf;

				// 计算这个节点映射到桶的位置
				size_t hashi = hf(kot(node->_data));
				hashi %= ht->_tables.size();
				
				++hashi; // 当前桶已经遍历完,需要对hashi进行自增

				// 遍历判断其他哈希桶是否为空,不为空则返回这个桶的指针
				for (; hashi < ht->_tables.size(); ++hashi)
				{
					if (ht->_tables[hashi] != nullptr)
					{
						node = ht->_tables[hashi];
						break;
					}
				}
				// 映射位置等于有效数据时,说明已经越界(走到尾的情况)
				if (hashi == ht->_tables.size())
				{
					node = nullptr;
				}
			}
			return *this;
		}
		
		// 后置++,先使用后自增
		Self operator++(int)
		{
			Self tmp(this);
			++(*this);
			return tmp;
		}

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

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

		bool operator!=(const HTIterator& hti) const
		{
			return node != hti.node;
		}

		bool operator==(const HTIterator& hti) const
		{
			return node == hti.node;
		}
	public:
		Node* node; // 哈希节点指针
		HT* ht;		// 哈希表指针(this)
	};
}

哈希桶迭代器自增图解:
在这里插入图片描述


  1. 增加通过key获取value操作
  • 哈希桶节点的模板类型T是不确定的,是unordered_map的时候是键对值(pair<K, V>),而unordered_set只是一个关键码(Key)

  • 所以我们需要建立一个仿函数将它们进行区分(开头有讲到)

unordered_map:

namespace Myself
{
	template <typename K, typename V, typename hashF = BUCKetHash::HashDF<K>>
	class unordered_map
	{
	private:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
	private:
		BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF> ht;
	}; 
}

unordered_set:

namespace Myself
{
	template <typename K, typename hashF = BUCKetHash::HashDF<K>>
	class unordered_set
	{
	private:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
	private:
		BUCKetHash::HashTable<K, K, SetKeyOfT, hashF> ht;
	};
}

  1. 增加通过哈希函数转换无符号整形操作

对关键码进行转换:

  • 因为哈希桶需要对该关键码映射对应的位置,所有需要把该关键码统一修改
namespace BUCKetHash
{
	// 仿函数:处理特殊的Key值问题 -- 后面需要对数据进行无符号整形转换
	template <typename K>
	struct HashDF
	{
		size_t operator()(const K& key)
		{
			return static_cast<size_t>(key);
		}
	};

	template <>
	struct HashDF<string>
	{
		size_t operator()(const string& str)
		{
			size_t HDS = 0;
			for (auto& e : str)
			{
				HDS = HDS * 131 + static_cast<size_t>(e);
			}
			return HDS;
		}
	};
}

🚂2、模拟实现的完整代码

HashTable.h

#pragma once

#include <iostream>
#include <vector>
using namespace std;

// 哈希桶(指针数组):哈希表中存储指针,这个指针存储映射的值,映射相同位置时,创建新的节点直接链到这个指针中
namespace BUCKetHash
{
	// 仿函数:处理特殊的Key值问题 -- 后面需要对数据进行无符号整形转换
	template <typename K>
	struct HashDF
	{
		size_t operator()(const K& key)
		{
			return static_cast<size_t>(key);
		}
	};

	template <>
	struct HashDF<string>
	{
		size_t operator()(const string& str)
		{
			size_t HDS = 0;
			for (auto& e : str)
			{
				HDS = HDS * 131 + static_cast<size_t>(e);
			}
			return HDS;
		}
	};

	// 哈希表节点
	template <typename T>
	struct HashNode
	{
	public:
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{}
	public:
		T _data;
		HashNode<T>* _next;
	};

	// 前置声明,迭代器需要访问哈希桶类的私有成员
	template <typename K, typename T, typename KeyOfT, typename hashF>
	class HashTable;

	// 迭代器
	template <typename K, typename T, typename KeyOfT, typename hashF>
	class HTIterator
	{
	private:
		typedef HashNode<T> Node;					  // 哈希表节点
		typedef HashTable<K, T, KeyOfT, hashF> HT;	  // 哈希表
		typedef HTIterator<K, T, KeyOfT, hashF> Self; // 迭代器本身
	public:
		HTIterator(Node* _node, HT* _ht)
			: node(_node)
			, ht(_ht)
		{}

		HTIterator(const HTIterator& hti)
			: node(hti.node)
			, ht(hti.ht)
		{}

		Self& operator++()
		{
			// 如果传过来的节点指针不为空,则直接跳到下一个节点
			if (node->_next != nullptr)
			{
				node = node->_next;
			}
			// 当前节点为空,找下一个桶
			else
			{
				KeyOfT kot;
				hashF hf;

				// 计算这个节点映射到桶的位置
				size_t hashi = hf(kot(node->_data));
				hashi %= ht->_tables.size();
				++hashi; // 当前桶已经遍历完,需要对hashi进行自增

				for (; hashi < ht->_tables.size(); ++hashi)
				{
					if (ht->_tables[hashi] != nullptr)
					{
						node = ht->_tables[hashi];
						break;
					}
				}
				// 映射位置等于有效数据时,说明已经越界
				if (hashi == ht->_tables.size())
				{
					node = nullptr;
				}
			}
			return *this;
		}
		
		Self operator++(int)
		{
			Self tmp(this);
			++(*this);
			return tmp;
		}

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

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

		bool operator!=(const HTIterator& hti) const
		{
			return node != hti.node;
		}

		bool operator==(const HTIterator& hti) const
		{
			return node == hti.node;
		}
	public:
		Node* node; // 哈希节点指针
		HT* ht;		// 哈希表指针(this)
	};

	// Unordered_set -> HashTable<K, Key, KeyOfT, hashF>
	// Unordered_map -> HashTable<K, pair<K, V>, KeyOfT, hashF>
	
	template <typename K, typename T, typename KeyOfT, typename hashF>
	class HashTable
	{
		template <typename K, typename T, typename KeyOfT, typename hashF>
		friend class HTIterator; // 迭代器内部需要访问哈希表的私有成员
	private:
		typedef HashNode<T> Node;
	public:
		typedef HTIterator<K, T, KeyOfT, hashF> iterator;
		typedef HTIterator<const K, const T, KeyOfT, hashF> const_iterator;
	public:
		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				if (cur != nullptr)
				{
					return iterator(cur, this);
				}
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator cbegin() const
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				if (cur != nullptr)
				{
					return const_iterator(cur, this);
				}
			}
			return cend();
		}

		const_iterator cend() const
		{
			return const_iterator(nullptr, this);
		}
	public:
		HashTable() = default;
		HashTable(const HashTable& ht)
		{
			_tables.resize(ht._tables.size(), nullptr);
			for (size_t i = 0; i < ht._tables.size(); ++i)
			{
				Node* cur = ht._tables[i];
				while (cur != nullptr)
				{
					this->Insert(kot(cur->_data));
					cur = cur->_next;
				}
			}
		}

		HashTable& operator=(const HashTable ht)
		{
			HashTable tmp(ht);
			this->_n = tmp._n;
			tmp._tables.swap(_tables);
			return *this;
		}

		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				while (cur != nullptr)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		// 返回值为pair是为了支持unordered_map的[]重载
		pair<iterator, bool> Insert(const T& data)
		{
			Node* IfExist = Find(kot(data));
			if (IfExist != nullptr)
				return make_pair(iterator(IfExist, this), false); // 插入识别,数据冗余

			if (_tables.size() == 0 || _tables.size() == _n)
			{
				size_t NewSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				// 把旧哈希桶的节点重新映射到新开辟的哈希桶
				HashTable newHT;
				newHT._tables.resize(NewSize, nullptr);

				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur != nullptr)
					{
						// 将旧哈希桶的值挪到新哈希桶中 -- 头插
						Node* next = cur->_next; // 保存下一个节点,挪动后防止找不到
						size_t hashi = HashDF(kot(cur->_data)) % NewSize; // 需要重新映射新扩容后的哈希桶,模新长度的哈希桶

						cur->_next = newHT._tables[hashi];
						newHT._tables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				newHT._tables.swap(_tables);
			}

			// 跟闭散列的方法一样需要继续模表大小
			size_t hashi = HashDF(kot(data));
			hashi %= _tables.size();

			// 构造需要插入的键对值,然后插入到映射的位置 -- 画图
			Node* newNode = new Node(data);
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;
			++_n;

			// 插入成功,返回指向新节点的迭代器
			return make_pair(iterator(newNode, this), true); 
		}

		bool erase(const K& key)
		{
			size_t hashi = HashDF(key);
			hashi %= _tables.size();

			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur != nullptr)
			{
				if (kot(cur->_data) == key)
				{
					// 删除位置为"头"
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			// 查找跟闭散列差不多
			size_t hashi = HashDF(key);
			hashi %= _tables.size();

			// 对映射的位置进行循环遍历查找
			Node* cur = _tables[hashi];
			while (cur != nullptr)
			{
				if (kot(cur->_data) == key)
					return cur;
				cur = cur->_next;
			}
			return nullptr;
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
		hashF HashDF;
		KeyOfT kot;
	};
}

Unordered_map.h

#pragma once
#include "HashTable.h"

namespace Myself
{
	template <typename K, typename V, typename hashF = BUCKetHash::HashDF<K>>
	class unordered_map
	{
	private:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef BUCKetHash::HashNode<pair<K, V>> Node;
	public:
		// 迭代器和常量迭代器
		typedef typename BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF>::iterator iterator;
		typedef typename BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF>::const_iterator const_iterator;
	public:
		iterator begin()
		{
			return ht.begin();
		}

		iterator end()
		{
			return ht.end();
		}

		const_iterator cbegin() const
		{
			return ht.cbegin();
		}

		const_iterator cend() const
		{
			return ht.cend();
		}

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

		bool erase(const K& key)
		{
			return ht.erase(key);
		}

		Node* find(const K& key)
		{
			return ht.Find(key);
		}
		
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF> ht;
	}; 
}

Unordered_set

#pragma once
#include "HashTable.h"
namespace Myself
{
	template <typename K, typename hashF = BUCKetHash::HashDF<K>>
	class unordered_set
	{
	private:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef BUCKetHash::HashNode<K> Node;
	public:
		typedef typename BUCKetHash::HashTable<K, K, SetKeyOfT, hashF>::iterator iterator;
		typedef typename BUCKetHash::HashTable<K, K, SetKeyOfT, hashF>::const_iterator const_iterator;
	public:
		// 迭代器
		iterator begin()
		{
			return ht.begin();
		}

		iterator end()
		{
			return ht.end();
		}

		const_iterator cbegin() const
		{
			return ht.cbegin();
		}

		const_iterator cend() const
		{
			return ht.cend();
		}
		// 其他接口
		pair<iterator, bool> insert(const K& key)
		{
			return ht.Insert(key);
		}

		bool erase(const K& key)
		{
			return ht.erase(key);
		}

		Node* find(const K& key)
		{
			return ht.Find(key);
		}
	private:
		BUCKetHash::HashTable<K, K, SetKeyOfT, hashF> ht;
	};
}

🚃3、哈希表的应用

🚄3.1、位图的概念

概念:

  • 所谓位图,就是用每一位(比特位)来存放某种状态

  • 适用海量数据,数据无重复的场景。通常用来判断某个数据是否存在

在这里插入图片描述


🚅3.2、位图的实现

🌈前言:位图常用的接口只有三个,插入、删除和查找

位图的结构:

  • 位图的结构是一个vector,底层是char/int类型,char可以标记8个状态(1字节),而int可以标识32个状态(4字节),模板类型是一个无符号整形

  • 位图的基本操作就是存储不重复数据,即可以使用0/1来标识存不存在

在这里插入图片描述


  1. 构造函数:
  • 每个char类型可以标识8个状态,如果是80个数据的话,只需要开辟10个空间足以

  • 如果是81 - 87的话,那么只需要多开辟一个char类型空间就足够了,也不会浪费很大内存

template <size_t N>
class bitset
{
public:
	bitset()
	{
		// N是数据个数,我们使用比特位来映射,char类型一个字节占八个比特位
		bit.resize((N / 8) + 1, 0);
	}
}

  1. 插入:
  • 使用哈希表的直接定址法,用一个比特位标识映射的位置在不在

  • 标识状态要注意大小端问题,否则会出错

namespace myself
{
	// 注意大小端问题
	template <size_t N>
	class bitset
	{
	public:

		void set(size_t x)
		{
			// x映射的位置在第几个char中
			size_t i = x / 8;
			
			// x标识的状态在char中的第几个比特位
			size_t j = x % 8;

			// 将映射位置的值(bit[i]) "按位或" 上一个向左移动j位的值
			bit[i] |= (1 << j);
		}
	private:
		vector<char> bit;
	};
}

在这里插入图片描述


  1. 删除:
  • 删除就是把这个数据映射在char中的位置的状态更改尾0即可
namespace myself
{
	template <size_t N>
	class bitset
	{
	public:
		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			
			bit[i] &= ~(1 << j);
		}
	private:
		vector<char> bit;
	};
}

在这里插入图片描述


  1. 查找:
namespace myself
{
	template <size_t N>
	class bitset
	{
	public:
		bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;

			return  bit[i] & (1 << j);
		}
	private:
		vector<char> bit;
	};
}

在这里插入图片描述


🚆3.3、位图完整代码

// 位图:哈希表的变式,用于处理海量数据
namespace myself
{
	// 注意大小端问题
	template <size_t N>
	class bitset
	{
	public:
		bitset()
		{
			// N是数据个数,我们使用比特位来映射,char类型一个字节占八个比特位
			bit.resize((N / 8) + 1, 0);
		}
		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;

			// 将映射位置的值(bit[i]) "按位或" 上一个向左移动j位的值
			bit[i] |= (1 << j);
		}
		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;

			bit[i] &= ~(1 << j);
		}
		bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;

			return  bit[i] & (1 << j);
		}
	private:
		vector<char> bit;
	};
}

🚉3.4、位图的应用

  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

🚇4、位图的变形

首先抛出一个问题:给定100亿个整数,设计算法找出只出现一次和二次的整数!!!

  • 首先,使用位图是解决不了这个问题的,因为位图只能标识两种状态(0/1)

  • 这道题需要标识四种状态才能解决问题


思路:

在这里插入图片描述

完整代码:

namespace myself
{
template <size_t N>
	class two_bitset
	{
	public:
		void set(size_t x)
		{
			bool in1 = bs1.test(x);
			bool in2 = bs2.test(x);

			// 如果二个位图中都不存在这个数据,则状态改为出现一次(01)
			if (in1 == 0 && in2 == 0)
			{
				bs2.set(x);
			}
			// 如果这个数据已经存在,则状态改为出现二次(10)
			else if (in1 == 0 && in2 == 1)
			{
				bs1.set(x);
				bs2.reset(x);
			}
			// 如果这个数据已经存在二次以上,则状态改为(11)
			else
			{
				bs1.set(x);
			}
		}

		// 只存在一次的数据
		bool is_once(size_t x)
		{
			return bs1.test(x) == 0 && bs2.test(x) == 1;
		}
	private:
		bitset<N> bs1;
		bitset<N> bs2;
	};
}

🚈5、布隆过滤器

🚐5.1、布隆过滤器的提出

  • 前言:我们使用哔哩哔哩时,下拉刷新会给我们不停的推荐新的内容,每次刷新都要对旧数据进行去重,那它是如何实现推送去重的呢?用服务器记录了用户看过的所有历史记录,当推荐系统推荐新内容时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

解决方案:

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

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

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


🚑5.2、布隆过滤器的概念

概念:

  • 布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构

  • 特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间

在这里插入图片描述在这里插入图片描述


🚒5.3、布隆过滤器的插入

向布隆过滤器插入"baidu"这一字符串:
在这里插入图片描述

随后再插入"tencent"这一字符串

在这里插入图片描述

注意:这里是假设,插入tencent时就发生了误判,但这不能代表什么,需要大量数据进行测试

namespace myself
{
	// 布隆过滤器 -- 映射多个无符号整形数据(消耗空间多),降低哈希冲突的概率,但是避免不了冲突
	// 假设现在设置三个哈希函数
	template <typename K, size_t M, typename HashFunc1, typename HashFunc2, typename HashFunc3>
	class BloomFilter
	{
	public:
		void Set(const K& key)
		{
			size_t bf1 = HashFunc1()(key) % M;
			size_t bf2 = HashFunc2()(key) % M;
			size_t bf3 = HashFunc3()(key) % M;

			bs.set(bf1);
			bs.set(bf2);
			bs.set(bf3);
		}
	private:
		myself::bitset<M> bs;
	};
}

🚓5.4、布隆过滤器的查找

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

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

bool Test(const K& key)
{
	size_t bf1 = HashFunc1()(key) % M;
	if (bs.test(bf1) == false)
		return false;

	size_t bf2 = HashFunc2()(key) % M;
	if (bs.test(bf2) == false)
		return false;

	size_t bf3 = HashFunc3()(key) % M;
	if (bs.test(bf3) == false)
		return false;

		return true; // 可能三个位置都存在冲突...
}

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


🚔5.5、布隆过滤器的删除

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

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

  • 这种方法违反了初衷(节省空间),所以没必要实现删除

缺陷:

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

🚕5.6、布隆过滤器优缺点

优点:

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

缺点:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值