哈希Hash

unordered_set

#include<iostream>
#include<vector>
#include<unordered_set>
#include<set>
#include<time.h>

using namespace std;

void test_time()
{
	int n = 1000000;
	vector<int> v;
	srand(time(0)); //初始化随机数发生器
	for (int i = 0; i < n; ++i)
	{
		v.push_back(rand()); //随机数发生器
	}

	set<int> s;
	size_t begin1 = clock();//进程运行之后到这里时的毫秒数

	for (auto e : v)
	{
		s.insert(e);
	}
	size_t end1 = clock();

	cout << "set : " << end1 - begin1 << endl;


	unordered_set<int> us;
	size_t begin2 = clock();

	for (auto e : v)
	{
		us.insert(e);
	}
	size_t end2 = clock();

	cout << "unordered_set : " << end2 - begin2 << endl;
}

int main()
{
	test_time();
}

set : 9065
unordered set : 3835

STL里的算法在实际中少使用

闭散列 线性探测

set-> HashTable<K, K>
map<K, V>-> HashTable<K, pair<K, V>>
HashTable.h:

namespace Close
{	enum State
	{
		EMPTY,
		EXIST,
		DELETE 
//删除状态的意义:避免干扰后面的查找;伪删除:没有删值,只是把状态修改,不改变结构
	};//枚举:常量集合

	template<class T>
	struct HashNode//节点里有值、状态
	{
		State _state = EMPTY;//内置类型用缺省值初始化
		T _t;
	};

	template<class K>
	struct Hash
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	//模板特化
	template<>
	struct Hash < string >
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash += ch;
				hash = hash * 131 + ch; //字符串哈希算法
			}
			return hash;
		}
	};
	//使用红黑树来封装map和set是一样的,由第二个模板参数来控制存储的类型是K,还是pair<const K,V>
	template<class K, class T, class HashFunc = Hash<K>>
//K是string时,需要把K转换为整形,使用模板特化
	class HashTable
	{
	public:
		bool Insert(const T& t)
		{
			if (_tables.size() == 0 || _size * 10 / _tables.size() == 7)//或
			{
			size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2//单纯的把原来的数据拷贝到新空间		load factor负载因子	
//newsize是不对的,可能原来冲突的数据,由于空间的扩容,不再是冲突的了;应该开辟好一段空间,重新对上面的数据进行计算,然后放入这段空间内,再释放原来的空间,开新的2倍的vector
/*方法1:vector<hashnode<t>> newtables;
newtables.resize(newsize);
原空间上的数据_tables,重新计算放到相对应的新空间上
for (size_t i = 0; i < _tables.size(); ++i)
{
	if (_tables[i]._state == exist)
	{
		//线性探测找在新表中的位置
	}
}
newtables.swap(_tables);*/

				HashTable<K, T, HashFunc> newht;//方法2:定义哈希表对象
				newht._tables.resize(newsize);//类里通过对象访问
				for (auto& e : _tables)
				{//遍历旧表,旧表this不改动,一旦改动则映射关系乱完了
					if (e._state == EXIST)
					{newht.Insert(e._t); //数据插入newht,复用冲突时探测的逻辑
//直接重新来一遍插入,重新计算位置,放在新的表中,不会再次发生增容,空间足够
					}
				}
				_tables.swap(newht._tables);
			}
			//不允许数据冗余
			HashNode<T>*  ret = Find(t);
			if (ret)
				return false;

			HashFunc hf;//仿函数HashFunc目的:转化为int,
			//一开始的时候,你的哈希表里面是没有元素的,所以会出现模0,出错情况
			size_t start = hf(t) % _tables.size();
			size_t index = start; //在二次探测的时候方便修改
			//线性探测,找一个空位置
			size_t i = 1;
			while (_tables[index]._state == EXIST) //应该去找下一个空位置
			{
				index = start + i;
				index %= _tables.size();
				++i;
			}
			//跳出循环就是两种情况1.EMPTY  2. DELETE这两种情况都可以把值放进去
			_tables[index]._t = t;//把值放进去
			_tables[index]._state = EXIST;//修改状态
			_size++;

			return true;
		}

		HashNode<T>* Find(const K& key)//vecter里每个数据是HashNode
		{
			HashFunc hf;
			size_t start = hf(key) % _tables.size();//调用string的operator(),转为int之后可以取模计算
			size_t index = start;
			size_t i = 1;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._t == key && _tables[index]._state == EXIST) //因为可能找到的这个值被删除掉了
				{
					return &_tables[index];//_tables里每个数据是node,取地址,返回地址
				}
				index = start + i;
				index %= _tables.size();
				++i;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashNode<T>* ret = Find(key);
			if (ret == nullptr)
			{
				return false;
			}
			else
			{
				//伪删除
				ret->_state = DELETE;
				return true;
			}
		}
	private:
		vector<HashNode<T>> _tables;//自定义类型:调自己的拷贝构造;深拷贝由vector完成:使用库的好处,空间由vector开的,出了作用域调自己的析构函数(编译器默认生成的),每个位置存HashNode;vector里存节点
		size_t _size = 0; //有效数据的个数,空间都没有,这里不是初始化,内置类型:值拷贝
	};
}

字符串哈希算法

void TestHashTable()
	{
		HashTable<int, int> ht;//本身是int时,不需要传第3个参数
		ht.Insert(5);
		ht.Insert(15);
		ht.Insert(16);
		ht.Insert(17);
		ht.Insert(25);
		ht.Insert(35);
		ht.Insert(45);
		ht.Insert(55);

		struct StrHash//仿函数
		{
			size_t operator()(const string& s)
			{
				size_t hash = 0;
				for (auto ch : s)
				{
					hash += ch;
				}
				return hash;
			}
		};

HashTable<string, string, StrHash> strht;//整形是有限的数,字符串是无限的,方法1:仿函数
HashTable<string, string> strht;//方法2:模板特化,有特化就用特化
		strht.Insert("sort");
		strht.Insert("insert");
#include "HashTable.h"
#include "unordered_map.h"
#include "unordered_set.h"
int main()
{   //testop();
	//Close::TestHashTable();
	bit::test_unordered_set();
		return 0;
}

开散列 哈希桶

HashTable.h

namespace Open
{
	size_t GetNextPrime(size_t prime)
	{
		static 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
		};

		size_t i = 0;
		for (; i < PRIMECOUNT; ++i)
		{
			if (primeList[i] > prime)
				return primeList[i];
		}
		return primeList[i];
	}

	template<class T>
	struct HashLinkNode
	{
		T _t;
		HashLinkNode<T>* _next;

		HashLinkNode(const T& t)
			: _t(t)
			, _next(nullptr)
		{}
	};

	template<class K>
	struct Hash
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};

	// 特化
	template<>
	struct Hash < string >
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				//hash += ch;
				hash = hash * 131 + ch;
			}

			return hash;
		}
	};

//类模板的前置声明,原因:构造迭代器的时候,需要HashTable,但模板没有进行特化,找不到
	template<class K, class T, class KeyOfT, class hash>
	class HashTable;

//对于封装迭代器来说,++会到桶里面的下1个节点,当桶里不再有结点时,需要到下1个有hash映射的位置
	template<class K, class T, class Ref,class Ptr ,class KeyOfT, class hash> 
	struct HashIterator
	{//friend template class HashTable;//错误:友元加反了,不在这里
		typedef HashIterator<K, T, Ref,Ptr,KeyOfT, hash> Self;
		typedef HashLinkNode<T> Node;
		Node* _node;
		HashTable<K, T, KeyOfT, hash>* _pht; 
//指向哈希表的指针:_pht,目的:遍历哈希表时,当1个通遍历完,找到下1个需要遍历的桶
		HashIterator(Node* node, HashTable<K, T, KeyOfT, hash>* pht)
//构造函数:传node、哈希表HashTable;传vectcor复杂,
			:_node(node)
			, _pht(pht)
		{}

		Ref operator*()
		{
			return _node->_t;
		}

		Ptr operator->()
		{
			return &(_node->_t);
		}

		bool operator!=(const Self& s) const //普通对象、const对象都可以调
		{
			return _node != s._node;
		}
		//对于operator++(前置++)来说,加完之后依旧返回的是一个迭代器的指针
		//1. 当前桶还有数据,继续走
		//2. 当前桶没有数据,跳到下一个桶,从第一个开始
		Self operator++()//(哈希表)单向迭代器没有--
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
//一个桶已经走完了,找下一个桶,用当前桶可以算出index位置,需要一个hash表
//算出的index的原来位置,已经全部都走完了,从他的下一个位置开始计算
				size_t index = _pht->HashFunc(kot(_node->_t),_pht->_tables.size());//表的大小_pht->_tables.size()
				++index;
				while (index < _pht->_tables.size())
				{
					if (_pht->_tables[index])
					{
						//这个桶非空,就开始遍历这个桶;表里存的第1个节点的指针
						_node = _pht->_tables[index];
						break;//找到下1个桶了
					}
					else
					{
						++index;
					}
				}
//while结束有可能1.break跳出来的   2.循环走结束了,两种情况区分一下
				if (index == _pht->_tables.size())
				{//没有找到下1个桶
					_node = nullptr;
				}
			}
			return *this;//返回的是自己
		}
	};


	template<class K,class T,class KeyOfT,class hash = Hash<K>>
	class HashTable
	{
		typedef HashLinkNode<T> Node;
		friend struct HashIterator < K, T, T&, T*, KeyOfT, hash > ;
	public:
		typedef HashIterator<K, T, T&,T*,KeyOfT, hash> Iterator;
		typedef HashIterator<K, T, const T&, const T*, KeyOfT, hash> Const_Iterator;
		Iterator Begin()//得到第1个桶里第1个数
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{//不知道第1个桶在哪里,需要找到第1个桶
				if (_tables[i])//如果是非空则找到了第1个桶
				{//_tables[i]:节点指针,用此构造1个迭代器
					return Iterator(_tables[i],this); //this:指向HashTable的指针
				}
			}

			//如果哈希表中一个数据也没有,那么返回nullptr也是正确的
			return End();
		}
		Iterator End()
		{
			return Iterator(nullptr,this);
		}

		size_t HashFunc(const K& key, size_t n)//计算下标
		{
			hash hf;
			size_t ki = hf(key);//K转为int
			return ki % n;
		}
	
pair<Iterator, bool> Insert(const T& t)
		{	//开散列/哈希桶负载因子控制在1,满了再加,空间利用率更高
			//自己冲突的多,只会影响自己,不影响别人
			KeyOfT kot;
			//控制负载因子 == 1的时候增容
			if (_size == _tables.size())
			{
				size_t newsize = GetNextPrime(_tables.size());//得到素数,有效验证能够提高效率
				vector<Node*> newtables;
				newtables.resize(newsize,nullptr);

				//循环的把每个哈希桶拿过来,重新挂接
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					//旧表中结点直接取下来挂在新表中
					Node* cur = _tables[i];
					while (cur)
					{//选择头插,单链表尾插时,需要找尾
						Node* next = cur->_next;//提前保存cur的next,防止cur被挂到新表,找不到旧表中的next
						size_t index = HashFunc(kot(cur->_t),newtables.size());//重新计算在所开辟的新表中的位置
						cur->_next = newtables[index];//头插
						newtables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;//旧表指针指向空
				}
				newtables.swap(_tables);
			}//所有数据都要重新插入1次
			//需要得到下标的位置
			size_t index = HashFunc(kot(t),_tables.size());//kot:把T里面的key提取出来,再转为int
//做map的key要支撑比较大小、做unordered_map的key要支持取模		
//不允许键值冗余,查找t在不在,先找到那个位置,然后在遍历整个哈希桶
			Node* cur = _tables[index];
			while (cur)
			{
				if (kot(cur->_t) == kot(t))
				{//如果存在则返回,不存在则插入
					return make_pair(Iterator(cur,this), false);
				}
				cur = cur->_next;
			}
//哈希表中一开始存储的是NULL,当底下有挂接的时候,存储的将会变为第1个结点的地址
//头插到链表桶里面
			Node* newnode = new Node(t);
			newnode->_next = _tables[index];
			_tables[index] = newnode;
			return make_pair(Iterator(newnode,this), true);
		}

		//Find就是通过Key来寻找的
		Iterator Find(const K& key)
		{
			KeyOfT kot;
			size_t index = HashFunc(key,_tables.size());
			Node* cur = _tables[index];
			//在hash表中找到位置,然后在遍历该位置的整个hash桶
			while (cur)
			{
				if (kot(cur->_t) == key)
				{
					return Iterator(cur,this);
				}
				cur = cur->_next;
			}
			return End();
		}
		bool Earse(const K& key)
		{
			KeyOfT kot;
			size_t index = HashFunc(key, _tables.size());//映射的位置index
			//一个单链表的删除
			Node* prev = nullptr;
			Node* cur = _tables[index];
			//现在hash表中找到位置,然后在遍历该位置的整个hash桶
			while (cur)
			{
				if (kot(cur->_t) == key)
				{
					//进行删除
					if (prev == nullptr)
					{
						//删除的是头
						_tables[index] = cur->_next; //链表挂接的第一个就直接找到了
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;//找到了且删除成功
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;//没找到
		}
	private:
		vector<Node*> _tables;//vector里存Node*节点指针![请添加图片描述](https://img-blog.csdnimg.cn/47b8e514e54a4afa8850cc8c39936f04.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-I5piv6L-Z6LSn,size_20,color_FFFFFF,t_70,g_se,x_16)

		size_t _size = 0;//有效数据的个数
	};
}

unordered_set.h

#pragma once
#include "HashTable.hpp"

//map和set一样,自身是没有什么东西的,各种操作是在红黑树的基础上实现的
//unordered_set和unordered_map都是在开散列HashTable基础上实现的
namespace bit
{
	template<class K,class hash = Open::Hash<K>> 
	class unordered_set
	{
		struct SetKOfT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	public:
		typedef typename Open::HashTable<K, K, SetKOfT, hash>::Iterator iterator;//基于开散列Open

		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

		bool earse(const K& key)
		{
			return _t.Earse(key);
		}
		pair<iterator, bool> insert(const K& k)
		{
			return _t.Insert(k);
		}

	private:
		Open::HashTable<K, K, SetKOfT, hash> _t;
	};

	void test_unordered_set()
	{
		bit::unordered_set<int> us;
		us.insert(1);
		us.insert(54);
		us.insert(58);
		us.insert(59);
		us.insert(21);
		us.insert(22); 
		us.insert(23);
		us.insert(24);
		for (auto& e : us)
		{
			cout << e << " ";
		}
		cout << endl;

		unordered_set<int>::iterator it = us.find(22);
		cout << *it << endl;

		us.earse(24);
		us.earse(21);
		us.earse(22);
		for (auto& e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}


unordered_map.h

#pragma once 
#include"HashTable.hpp"

namespace bit
{
	template<class K,class V,class hash = Open::Hash<K>>//基于开散列
	class unordered_map
	{
		struct MapKOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename Open::HashTable<K, pair<const K, V>, MapKOfT, hash>::Iterator iterator;
		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _ht.Insert(kv);
		}

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

		iterator end()
		{
			return _ht.End();
		}

		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

		bool earse(const K& key)
		{
			return _ht.Earse(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator,bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		Open::HashTable<K, pair<const K, V>, MapKOfT,hash> _ht;
	};

	void test_unordered_map()
	{
		bit::unordered_map<int, int> um;
		um.insert(make_pair(1, 1));
		um.insert(make_pair(2, 1));
		um.insert(make_pair(3, 1));

		unordered_map<int,int>::iterator it = um.begin();
		while (it != um.end())
		{
			cout << it->first << ":"<<it->second<<endl;
			++it;
		}

		bit::unordered_map<string,string> dict;
		dict["hash"] = "哈希";
		dict["sort"] = "排序";
	
		for (auto& e : dict)
		{
			cout << e.first << ":" << e.second << endl;
		}

		cout <<endl;

		dict.earse("hash");
		dict.earse("find");
		for (auto& e : dict)
		{
			cout << e.first << ":" << e.second << endl;
		}
	}
}

BitSet.h位图

#pragma once
#include <vector>

namespace bit
{
	template<size_t N>//n 个比特位
	class bitset
	{
	public:
		bitset()
		{//开空间
			_bits.resize((N >> 3) + 1, 0);//右移3位:÷8
		}

		void set(size_t x)
		{
			size_t index = x >> 3;
			size_t num = x % 8;

			_bits[index] |= (1 << num);
		}

		void reset(size_t x)
		{
			size_t index = x >> 3;
			size_t num = x % 8;

			_bits[index] &= (~(1 << num));
		}

		bool test(size_t x)
		{
			size_t index = x >> 3;
			size_t num = x % 8;

			return _bits[index] & (1 << num);
		}
	private:
		std::vector<char> _bits;//vector里存的char
		//std::vector<int> _bits; 
	};

	void test_bitset()
	{
		bitset<100> bs;
		bs.set(10);
		bs.set(17);
		bs.set(80);

		cout << bs.test(10) << endl;
		cout << bs.test(17) << endl;
		cout << bs.test(80) << endl;
		cout << bs.test(81) << endl << endl;

		bs.reset(80);
		bs.set(81);

		cout << bs.test(10) << endl;
		cout << bs.test(17) << endl;
		cout << bs.test(80) << endl;
		cout << bs.test(81) << endl;

		//bitset<-1> bs_max;,开了大致42亿个值的映射
		//bitset<0xffffffff> bs_max;按16进制
	}
}

结果: 1 1 1 0 1 1 0 1

布隆过滤器BloomFilter.h

#include "BitSet.h"
#include <string>

struct StrHash1
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			//hash += ch;
			hash = hash * 131 + ch;
		}

		return hash;
	}
};

struct StrHash2
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash = 65599 * hash + ch;
			//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;  
		}
		return hash;
	}
};

struct StrHash3
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		size_t magic = 63689;
		for (auto ch : s)
		{
			hash = hash * magic + ch;
			magic *= 378551;
		}
		return hash;

		return hash;
	}
};

template<size_t N, class K = string,
class HashFunc1 = StrHash1,
class HashFunc2 = StrHash2,
class HashFunc3 = StrHash3>//HashFunc仿函数给多,误判的概率越小;有缺省参数;有N个比特位的布隆过滤器
class BloomFilter
{
public:
	BloomFilter()
		:_bs(N*4)//自定义类型:初始化地方=定义地方,N*4的原因:这格局概率得到的公式
		, _n(N*4)//表的大小:n
	{}
	void Set(const K& key)
	{
		HashFunc1 hf1;//定义对象,对象可以像函数一样使用
		size_t i1 = hf1(key) % _n;//映射第1个位置
		_bs.set(i1);//置为1

		HashFunc2 hf2;
		size_t i2 = hf2(key) % _n;
		_bs.set(i2);

		HashFunc3 hf3;
		size_t i3 = hf3(key) % _n;
		_bs.set(i3);
		cout << i1 << "--" << i2 << "--"<<i3<<endl;
	}

	// 一般不支持删除,删除可能存在把其他值也干掉了
	void Reset(const K& key);

	bool Test(const K& key)//判断key是否存在,key可能是结构体、字符串
	{
		HashFunc1 hf1;
		size_t i1 = hf1(key) % _n;
		if (!_bs.test(i1))
		{
			return false;
		}

		HashFunc2 hf2;
		size_t i2 = hf2(key) % _n;
		if (!_bs.test(i2))
		{
			return false;
		}

		HashFunc3 hf3;
		size_t i3 = hf3(key) % _n;
		if (!_bs.test(i3))
		{
			return false;//但凡有1个不在就已经return了
		}
		return true;
	}
private:
	bit::bitset<N> _bs;//里面存的是位图
	size_t _n;
};

void TestBloomFilter()
{
	BloomFilter<10> bf;
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528154.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528156.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528165.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528167.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528168.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/2528169.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/252816.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/252817.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/252818.html");
	bf.Set("https://www.cnblogs.com/-clq/archive/2012/05/31/252819.html");


	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html")<<endl;
	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528154.html") << endl;
	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528156.html") << endl;
	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528165.html") << endl<<endl;

	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528166.html") << endl;
	cout << bf.Test("https://www.cnblogs.com/-clq/archive/2012/05/31/2528135.html") << endl;
}

海量数据

给两个文件,分别有100亿个query,我们只有1G内存,两个文件交集?精确算法
1、估算给的数据大概占用多少空间。假设一个query平均是10byte字节,100亿个query大概占用100G
2、哈希切分200份
100G query的文件A:创建200个小文件A0,A1,. . . A199;依次获取文件中每个query,i = Hash(query) % 200;每个query进入算出的Ai文件,i=几进入对应的几号小文件
100Gquery文件B:创建200个小文件 B0,B1… B199;依次获取文件中每个query,i = Hash(query) %200每个query进入算出的.Bi文件
Ai小文件和Bi小文件求交集
Ai读取到一个set中,依次取Bi中的query判断在不在Ai中,在就是交集
如果是平均切分也是可以的,只是跟Ai小文件的交集,可能在[BO,B199],都需要比一遍,不同的字符串在每个文件里都有
如果是哈希切分,A和B中相同query一定进入编号相同的Ai和Bi,只需要按编号比即可。这样效率高。如果有和A0一样的字符串一定在B0,因为一样的query通过一样的哈希函数Hash(query),最终得到一样的i。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值