哈希应用(详解)

目录

1.位图

2.位图的应用

3.布隆过滤器


1.位图

1.1 位图的概念:

        在海量数据中,并且数据都不重复, 那么要查找某一个数据使用其他的方法, 不仅会消耗很大的内存, 并且效率非常之低下. 那么就是可以采用每个比特位进行存放数据的信息(是否存在).

 1.2 举个小小的实际栗子:

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

(1)我们可能会思考到遍历;二分查找那么你要考虑到如果是遍历那么时间复杂度为O(n); 二分查找不仅要求数据是升序排放而且时间复杂度为O(log2^n).

(2)如果采用位图的方法:

         那么需要多少个比特位捏? 2^32 = 4,294,967,296(42亿个数据);

         那么需要多少内存捏? 2^32 / 8 / 1024 / 1024 / 1024 = 0.5G

 1.3位图的实现

(1) bitset构造函数:

        _bitset是数组类型为int, 分成4byte, 那么就是4 * 8= 32bit; 然后全部初始化为0.

        bitset()
		{
			//_bits.resize(N / 32 + 1, 0);
			_bits.resize((N >> 5) + 1, 0);
		}

(2) set位图操作:

        因为一个数据我们不知道它是在哪个块中(这里简单理解32bit为一个块, 然后还有在块的第几个位置.那么刚好需要找到.

        

        然后还要将j位数据置1,其他位不变捏?

那就是采用这一位的数据按位或上1.如果为0,那么也是1, 为1更加是1.

        void set(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] |= (1 << j);
		}

(3) reset操作:

        和上面set找到数据一样; 处理数据就是将j位置置0, 其他位置不变.

如何保证j位置置0, 其他位置不变?

        那就是将j位置按位与1的取反,那么无论j为是0还是1都被置0; 1的其他位取反之后是1和其他位按位与还是本身.

        void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] &= ~(1 << j);
		}

(4)测试test:

        测试数据的j位是否为1还是为0.

        void test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			return _bits[i] & (1 << j);
		}

(5)主函数main:

#define _CRT_SECURE_NO_WARNINGS 1
#include"bitset.h"
#include<iostream>
#include<vector>
using namespace std;

int main()
{
	study::bitset<100> bs;
	bs.set(40);
	bs.set(39);

	cout << bs.test(38) << endl;
	cout << bs.test(39) << endl;
	cout << bs.test(40) << endl;
	cout << bs.test(41) << endl;

	bs.reset(40);
	cout << bs.test(38) << endl;
	cout << bs.test(39) << endl;
	cout << bs.test(40) << endl;
	cout << bs.test(41) << endl;

	return 0;
}

2.位图的应用

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

        分析: 那么可以将一个位图的状态分为三种: 出现0次, 出现1次, 出现两次及以上.

    template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			//0->1 00 -> 01
			if (_bs1.test(x) == false && _bs2.test(x) == false)
			{
				_bs2.set(x);
			}
			//1->2 01 -> 10
			else if (_bs1.test(x) == false && _bs2.test(x) == true)
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			//2->3 10 -> 11
			else if (_bs1.test(x) == true && _bs2.test(x) == false)
			{
				_bs1.set(x);
				_bs2.set(x);
			}
			//3->..不变
		}

		void print()
		{
			for (size_t i = 0; i < N; i++)
			{
				//01
				if (_bs1.test(i) == false && _bs2.test(i) == true)
				{
					cout << "1->" <<  i << endl;
				}
				//10
				else if (_bs1.test(i) == true && _bs2.test(i) == false)
				{
					cout << "2->" << i << endl;
				}
			}
			cout << endl;
		}
	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};

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

        可以这样分析: 将两个文件的数据映射两个位图, 如果一个值在两张位图里面都存在就是交集.

    template<size_t N>
	class intersection
	{
	public:
		void set(size_t x)
		{
			//出现0次 0 -> 1
			if (_bits1.test(x) == false)
			{
				_bits1.set(x);
			}
			//出现两次及其以上.

			if (_bits2.test(x) == false)
			{
				_bits2.set(x);
			}
		}

		void check(size_t x)
		{
			for (size_t i = 0; i < N; i++)
			{
				//相交就返回.
				if (_bits1.test(i) == true && _bits2.test(i) == true)
				{
					cout << "相交" << i << endl;
				}
			}
			cout << endl;
		}

	private:
		bitset<N> _bits1;
		bitset<N> _bits2;
	};

 (3) 1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数?

   是不是和第一个实例优点相同; 那你猜对了,一个位图的状态分为种情况; 分别是出现次数为0; 出现为1次; 出现次数为2; 出现次数2次以上.

	template<size_t N>
	class threebitset
	{
	public:
		void set(size_t x)
		{
			//0次 000 -> 001
			if (_bs1.test(x) == false && _bs2.test(x) == false && _bs3.test(x) == false)
			{
				_bs3.set(x);
			}
			//1次 001 -> 010
			else if (_bs1.test(x) == false && _bs2.test(x) == false && _bs3.test(x) == true)
			{
				_bs2.set(x);
				_bs3.reset(x);
			}
			//2次 010 -> 011
			else if (_bs1.test(x) == false && _bs2.test(x) == true && _bs3.test(x) == false)
			{
				_bs3.set(x);
			}
			//3次 011 -> 100
			else if (_bs1.test(x) == false && _bs2.test(x) == true && _bs3.test(x) == true)
			{
				_bs1.set(x);
				_bs2.reset(x);
				_bs3.reset(x);
			}
			//3次及其以上 100;不管
		}

		void print()
		{
			for (size_t i = 0; i < N; i++)
			{
				//出现0次
				if (_bs1.test(i) == false && _bs2.test(i) == false && _bs3.test(i) == false)
				{
					cout << "0->" << i << endl;
				}
				//出现1次
				else if (_bs1.test(i) == false && _bs2.test(i) == false && _bs3.test(i) == true)
				{
					cout << "1->" << i << endl;
				}
				//出现2次
				else if (_bs1.test(i) == false && _bs2.test(i) == true && _bs3.test(i) == false)
				{
					cout << "2->" << i << endl;
				}
			}
			cout << endl;
		}

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

3.布隆过滤器

考虑一个小问题:

        如果是整形考虑在不在以及子类问题, 那就是位图可以解决;

那如果是其他类型考虑在不在捏? 那就要上布隆过滤器.

3.1代码编写

(1)  BKDRHash和APHash以及DJBHash都是用来映射的, 由于一个值如果映射一个位置就很容易导致冲突, 造成误判, 然而上面的三种方法就是一个值映射到多个位置, 降低误判的概率,但是无法避免.

(2)一般布隆过滤器是不允许删除的,因为映射的值如果删除很可能会影响到其他的值的查找.

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include"Bitset.h"
using namespace std;

namespace Study
{
	struct BKDRHash
	{
		size_t operator()(const string& key)
		{
			// BKDR
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}

			return hash;
		}
	};

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

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

	template<size_t N,
		class K = string,
		class HashFunc1 = BKDRHash,
		class HashFunc2 = APHash,
		class HashFunc3 = DJBHash>
class BoolFilter
	{
	public:
		void set(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % N;
			size_t hash2 = HashFunc2()(key) % N;
			size_t hash3 = HashFunc3()(key) % N;

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

		//一般不允许删除滴,因为会影响到其他的值.
		void Reset(const K& key);

		bool Test(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % N;
			if (_bs.test(hash1) == false)
				return false;

			size_t hash2 = HashFunc2()(key) % N;
			if (_bs.test(hash2) == false)
				return false;

			size_t hash3 = HashFunc3()(key) % N;
			if (_bs.test(hash3) == false)
				return false;

			//存在误判
			return true;
		}
	private:
		study::bitset<N> _bs;
	};

	void TestBF1()
	{
		BoolFilter<100> bf;
		bf.set("桃子");
		bf.set("香蕉");
		bf.set("哈密瓜");
		bf.set("西瓜");

		cout << bf.Test("桃子") << endl;
		cout << bf.Test("香蕉") << endl;
		cout << bf.Test("哈密瓜") << endl;
		cout << bf.Test("葡萄") << endl;
		cout << bf.Test("西瓜") << endl;
		cout << bf.Test("火龙果") << endl;
		cout << bf.Test("香瓜 ") << endl;
		cout << bf.Test("猕猴桃") << endl;
	}

	void TestBF2()
	{
		srand(time(0));
		const size_t N = 10000;
		BoolFilter<N * 10> bf;

		vector<string> v1;
		string url = "西瓜";

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

		for (auto& str : v1)
		{
			bf.set(str);
		}

		vector<string> v2;
		for (size_t i = 0; i < N; i++)
		{
			string urlstr = url;
			urlstr += to_string(999999 + i);
			v2.push_back(urlstr);
		}

		size_t n2 = 0;
		for (auto& str : v2)
		{
			if (bf.Test(str))//误判
			{
				n2++;
			}
		}
		cout << "相似字符的误判率为:" << (double)n2 / (double)N << endl;
	
		vector<string> v3;
		for (size_t i = 0; i < N; i++)
		{
			string url = "桃子";
			url += 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;
	}
}

 3.2布隆过滤器的应用

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

         分析: 假如一个query是50byte, 那么100亿query就是5000亿byte也就是约等于500G,那么绝对会超出内存, 更加找不出来, 那么可以考虑一下哈希分割的思想. 

首先就是A和B都将每个query的数据进行映射(i = Hahs(query) % 500); 然后映射到A0-A499/ B0-B499的小文件里面.最后Ai和BI找交集即可(Ai和Bi分别都插入setA和SetB,找交集).

        那么又有一个小问题: 如果一个小文件的大小是10G内存还是不够捏? 

可能有下面两种情况: 首先就是小文件里大多数是一个query, 还有就是小文件里面很多不同的query.        面对这两种情况我们都可以采用set里面解决, 为啥捏? 如果是情况一那么一定插入成功的只有第一次出现的,后面失败. 情况二的话不断插入set就会内存不足, 抛异常, 那么我们可以采取二次分割, 再找交集的方法.

 (2给一个超过100G大小的log file, log里面存放IP地址, 找到ip地址出现最多的?

        和上面类似, 也是将ip进行哈希映射, i = Hash(ip) % 100; 然后进入到映射的下标i的小文件里面, 相同的ip进入同一个小文件,最后用map统计出数据.

相关推荐

  1. 【C++ 应用

    2024-04-24 15:56:01       17 阅读
  2. Redis 数据结构详解命令

    2024-04-24 15:56:01       19 阅读
  3. 常见算法及其应用场景

    2024-04-24 15:56:01       24 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-24 15:56:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-24 15:56:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-24 15:56:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-24 15:56:01       20 阅读

热门阅读

  1. 分组排序取第一条数据 SQL写法

    2024-04-24 15:56:01       11 阅读
  2. Redis 大KEY/慢查询问题的排查和解决

    2024-04-24 15:56:01       15 阅读
  3. flutter组件 InheritedWidget

    2024-04-24 15:56:01       16 阅读
  4. leetcode922-Sort Array By Parity II

    2024-04-24 15:56:01       11 阅读
  5. 图书借阅系统开发笔记

    2024-04-24 15:56:01       11 阅读
  6. i18n在VUE3中使用插槽动态传入组件

    2024-04-24 15:56:01       12 阅读
  7. 【Mysql】Mysql8存储引擎优化与锁和事务管理优化

    2024-04-24 15:56:01       12 阅读
  8. gitea的简单介绍

    2024-04-24 15:56:01       13 阅读
  9. 什么是Git?&& 工作原理

    2024-04-24 15:56:01       15 阅读
  10. spring bean的作用域

    2024-04-24 15:56:01       13 阅读