目录
哈希
常见哈希函数
除留余数法
哈希冲突
哈希冲突解决
闭散列
a、线性探测
插入
查找
删除
线性探测的实现代码
b、二次探测
二次探测的实现
开散列
开散列实现
插入
查找
删除
析构函数
代码汇总
哈希
常见哈希函数
-  
   直接定址法 -- (常用) -- 不存在哈希冲突
 
- 除留余数法 -- (常用) -- 存在哈希冲突,重点解决哈希冲突
 
- 平方取中法 -- (了解) -- 存在哈希冲突,只能适用于整数
 
- 折叠法 -- (了解) -- 不存在哈希冲突,只能适用于整数
 
- 随机数法 -- (了解)
 
-  
     数学分析法 -- (了解)
 

除留余数法
        哈希基于映射,值跟存储位置建立关联映射关系。以我们就学过计数排序,实现的原理是:
 利用映射,取最大值到最小值的大小建立数组通过唯一的对应关系,利用映射关系计数排序,正是因为此计数排序也有巨大的缺陷,由于数组大小取决最大值与最小值,如果遇见:3 7 19 300 70000,仅仅5个数据就要创建70000个空间,而哈希利用除留余数法建立关联映射关系:

哈希冲突
哈希冲突解决
解决哈希冲突两种常见的方法是:闭散列和开散列
- 闭散列 -- 开放定址法 
  
- a、线性探测
 - b、二次探测
 
 - 开散列 -- 拉链法/哈希桶(此方法更好,也是库中所用的)
 
闭散列
a、线性探测
如,下列场景,现在需要插入元素44,先通过哈希函数计算哈希地址,为4(44%10 = 4),因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突:
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
插入
- 通过哈希函数获取待插入元素在哈希表中的位置。
 -  
  如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到下一个空位置,插入新元素。
 

从此可以看出闭散列的线性探测是不好的,容易数据之间过于的占用位置,造成互相影响,但是其也是有效的方式。(冲突越多效率越低)
查找
- 根据插入的规则,如果数据存在。即,数据一定在:该哈希地址位置,或者在地址位置后连续不为空的序列里,否则无该值。
 

删除
- 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。
 - 线性探测采用标记的伪删除法来删除一个元素。
 
为什么以伪删除法来删除一个元素?
如果我们不用伪删除法来删除一个元素:
伪删除法:加一个状态标志位
// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST 此位置已经有元素, DELETE 元素已经删除enum State { EMPTY , EXIST , DELETE };
即:删除的时候并不是标为空,而是标为删除。这样查找的时候看EMPTY,EMPTY是无该数据。插入的是时候看是DELETE还是EMPTY,是DELETE更改数据,是EMPTY填补数据。
线性探测的实现代码
由于key值需要进行%数求哈希地址为,遇见能强转为size_t的还好,但是如果遇见的是string之类不能强转的就会出现问题,所以我们需要利用仿函数来解决此类问题,对于能强转的我们提供成默认的仿函数,并利用特化提供string类型的,其余不能强转的同理。
字符串哈希算法

#include<vector>
#include<utility>
#include<iostream>
#include<string>
using namespace std;
enum State
{
	EMPTY,  //没有元素
	EXIST,  //存在元素
	DELETE  //该元素已被删除
};
// 单位数据
template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};
// 仿函数为了防止出现Key是string的存在,因为string不能直接%,其余不能直接%同理
//能强转的
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return size_t(key);
	}
};
//由于string不能强转为size_t,需要显示写仿函数,特化
template<>
struct HashFunc<string>
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}
		return val;
	}
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	Hash hash;
	//插入
	bool Insert(const pair<K, V>& kv)
	{
		// 防止已有该值
		if (Find(kv.first))
			return false;
		//哈希表的扩容
		if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7) // 散列表的载荷因子:a = 填入表中的元素个数 / 散列表的长度。(此处7为规定的载荷因子)
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			// size变大,即%数变大,哈希地址改变,需要重新insert。
			HashTable<K, V> newHT;
			newHT._tables.resize(newSize);
			for (auto e : _tables)
			{
				if (e._state == EXIST)
					newHT.Insert(e._kv);
			}
			_tables.swap(newHT._tables);
		}
		size_t hashi = hash(kv.first) % _tables.size(); //此处%的是size是因为底层为vector实现,而其opertor[]的范围规定为size
		// 哈希表是通过:size大小的列表的如同头与尾相接,以循环寻找插入位置。
		// (由于引入了载荷因子,列表不可能满)
		while (_tables[hashi]._state != EMPTY)
		{
			hashi++;
			hashi %= _tables.size();
		}
		// 插入数据
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		_size++;
		return true;
	}
	//查找
	HashData<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0)
		{
			return nullptr;
		}
		size_t state = hash(key) % _tables.size();
		size_t hashi = state;
		while (_tables[hashi]._state != EMPTY)
		{
			if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key)
				return &_tables[hashi];
			hashi++;
			hashi %= _tables.size();
			if (hashi == state) // 防止出现边删除边插入而导致的,不超过载荷因子下而存满(概率极低)
				break;
		}
		return nullptr;
	}
	//删除
	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			--_size;
			return true;
		}
		else
			return false;
	}
	//打印存在的数据
	void Print()
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			if (_tables[i]._state == EXIST)
				cout << "[" << i << "]:" << _tables[i]._kv.first << "  ";
		}
	}
private:
	vector<HashData<K, V>> _tables;
	size_t _size = 0;  //存储多少个有效数据
};
void TestHash()
{
	HashTable<int, int> h;
	int array[] = { 1, 5, 8, 33, 77, 36, 86, 1, 8, 5, 8, 2, 5 };
	for (auto e : array)
	{
		h.Insert(make_pair(e, e));
	}
	h.Erase(1);
	h.Erase(6);
	cout << h.Find(1) << endl;
	cout << h.Find(8) << endl;
	h.Print();
}
void TestHashString()
{
	HashTable<string, int> h;
	string array[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	for (auto ch : array)
		h.Insert(make_pair(ch, 1));
	h.Print();
}
int main()
{
	TestHash();
	TestHashString();
	return 0;
} 
如 a = 0.7即:填入表中的元素个数只能是散列表长度的70%。当大于就扩容。
- 线性探测优点:实现非常简单。
 - 线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同 关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。
 
b、二次探测

二次探测的实现
二次探测的实现与线性探测的实现的核心是极为相似的,也就是对于哈希地址取值方式的改变。
#include<string>
#include<vector>
#include<iostream>
using namespace std;
enum State
{
	EMPTY,
	EXIST,
	DELETE
};
template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
// 特化
template<>
struct HashFunc<string>
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}
		return val;
	}
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
			return false;
		// 负载因子到了就扩容
		if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7) // 扩容
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			HashTable<K, V, Hash> newHT;
			newHT._tables.resize(newSize);
			// 旧表的数据映射到新表
			for (auto e : _tables)
			{ 
				if (e._state == EXIST)
					newHT.Insert(e._kv);
			}
			_tables.swap(newHT._tables);
		}
		Hash hash;
		size_t start = hash(kv.first) % _tables.size();
		size_t i = 0;
		size_t hashi = start;
		// 二次探测
		while (_tables[hashi]._state == EXIST)
		{
			++i;
			hashi = start + i * i;
			hashi %= _tables.size();
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_size;
		return true;
	}
	HashData<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0)
			return nullptr;
		Hash hash;
		size_t start = hash(key) % _tables.size();
		size_t hashi = start;
		int i = 0;
		while (_tables[hashi]._state != EMPTY)
		{
			if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key)
				return &_tables[hashi];
			i++;
			hashi = start + i * i;
			hashi %= _tables.size();
			if (hashi == start)
				break;
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			--_size;
			return true;
		}
		return false;
	}
	void Print()
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			cout << "[" << i << "]:" << _tables[i]._kv.first << "  ";
		}
		cout << endl;
	}
private:
	vector<HashData<K, V>> _tables;
	size_t _size = 0; //存储的数据个数
};
void TestTable()
{
	HashTable<int, int> h;
	int array[] = { 1,6,545,876,235,8765,41 };
	for (auto e : array)
	{
		h.Insert(make_pair(e, e));
	}
	cout << h.Find(545) << endl;
	h.Erase(545);
	cout << h.Find(545) << endl;
	h.Print();
}
int main()
{
	TestTable();
	return 0;
} 
总的来说闭散列是不好的,可以说是被淘汰的,但是意识一种值得学习的是思维。其作为哈希实现的一种思维结构,闭散列以这种开放定址法,总是会由于冲突而去占用别的位置。于是便有为解决冲突的开散列的桶式结构 —— 拉链法/哈希桶。
开散列

开散列实现
由于存储的数据可能是string之类,无法直接强转为size_t的类型,所以我们需要使用仿函数实现:
namespace cr
{
	// 哈希桶中的单链表的节点
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _data;
		HashNode<K , V>* _next;
		HashNode(const pair<K, V>& data)
			:_data(data)
			, _next(nullptr)
		{}
	};
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	// 特化
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t count = 0;
			for (auto ch : key)
			{
				count *= 131;
				count += ch;
			}
			return count;
		}
	};
	// 哈希桶的封装实现
	template<class K, class V, class Hash = HashFunc<K>>
	class HashBucket
	{
	private:
		typedef HashNode<K, V> Node;
	public:
    
    //……
	private:
		vector<Node*> _tables; // 哈希表
		size_t _size = 0; // 有效数据个数
	};
} 
插入
由于是单链表,并且哈希桶并未要求对数据进行排序,所以表中的每一个单链表,进行尾插还是头插都是可以的,此处选择头插。
对于空间的扩容,根据相关实验素数的效率更高,于是哈希桶的空间开辟是根据素数数组开辟的:
// 提取空间开辟的大小(素数)
inline size_t __stl_next_prime(size_t n)
{
	static const size_t __stl_num_primes = 28;
	static const size_t __stl_prime_list[__stl_num_primes] =
	{
		53, 97, 193, 389, 769,
		1543, 3079, 6151, 12289, 24593,
		49157, 98317, 196613, 393241, 786433,
		1572869, 3145739, 6291469, 12582917, 25165843,
		50331653, 100663319, 201326611, 402653189, 805306457,
		1610612741, 3221225473, 4294967291
	};
	//4294967291个int类型已经是16G,大小已经足够。
	for (size_t i = 0; i < __stl_num_primes; ++i)
	{
		if (__stl_prime_list[i] > n)
			return __stl_prime_list[i];
	}
	return -1;
} 
// 插入
bool Insert(const pair<K, V>& key)
{
	//查重
	if (Find(key.first))
		return false;
	Hash hash;
	// 扩容
	if (_size == _tables.size())
	{
		vector<Node*> newTables;
		newTables.resize(__stl_next_prime(_size), nullptr);
		// 将旧表数据移动到新表
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			// 将旧表的每个哈希桶的单链表节点移动到新表上
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = hash(key.first) % newTables.size(); // 在新表上的哈希地址
				// 在新表的哈希地址上头插
				cur->_next = newTables[hashi];
				newTables[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newTables);
	}
	// 找到哈希地址并进行头插
	size_t hashi = hash(key.first) % _tables.size();
	Node* cur = new Node(key);
	cur->_next = _tables[hashi];
	_tables[hashi] = cur;
	++_size;
	return true;
} 
查找
算哈希地址后根据哈希地址遍历该地址的桶查找。
// 查找
Node* Find(const K& key)
{
	if (_tables.size() == 0) // 未存入数据
		return nullptr;
	Hash hash;
	size_t hashi = hash(key) % _tables.size();
	Node* cur = _tables[hashi];
	while (cur)
	{
		if (cur->_data.first == key)
			return cur;
		cur = cur->_next;
	}
	return nullptr;
}
 
删除
根据哈希地址寻找后,直接按照单链表的删除方式即可。

// 删除
bool Erase(const K& key)
{
	if (_tables.size() == 0) // 未存入数据
		return false;
	Hash hash;
	size_t hashi = hash(key) % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (cur->_data.first == key) // 找到需删除的数据
		{
			if (prev == nullptr) // 删除的是桶的第一个数据
				_tables[hashi] = cur->_next;
			else
				prev->_next = cur->_next;
			delete cur;
			--_size;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
} 
析构函数
由于是单链表,是new的空间,所以需要写析构函数,对一个一个节点释放。
~HashBucket()
{
	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;
	}
} 
代码汇总
#include<iostream>
#include<utility>
#include<vector>
#include<time.h>
using namespace std;
namespace cr
{
	// 哈希桶中的单链表的节点
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _data;
		HashNode<K , V>* _next;
		HashNode(const pair<K, V>& data)
			:_data(data)
			, _next(nullptr)
		{}
	};
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	// 特化
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t count = 0;
			for (auto ch : key)
			{
				count *= 131;
				count += ch;
			}
			return count;
		}
	};
	// 哈希桶的封装实现
	template<class K, class V, class Hash = HashFunc<K>>
	class HashBucket
	{
	private:
		typedef HashNode<K, V> Node;
	public:
		~HashBucket()
		{
			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;
			}
		}
		// 提取空间开辟的大小(素数)
		inline size_t __stl_next_prime(size_t n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __stl_prime_list[__stl_num_primes] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};
			//4294967291个int类型已经是16G,大小已经足够。
			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > n)
					return __stl_prime_list[i];
			}
			return -1;
		}
		// 插入
		bool Insert(const pair<K, V>& key)
		{
			//查重
			if (Find(key.first))
			{
				return false;
			}
			Hash hash;
			// 扩容
			if (_size == _tables.size())
			{
				vector<Node*> newTables;
				newTables.resize(__stl_next_prime(_size), nullptr);
				// 将旧表数据移动到新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					// 将旧表的每个哈希桶的单链表节点移动到新表上
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hash(key.first) % newTables.size(); // 在新表上的哈希地址
						// 在新表的哈希地址上头插
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}
			// 找到哈希地址并进行头插
			size_t hashi = hash(key.first) % _tables.size();
			Node* cur = new Node(key);
			cur->_next = _tables[hashi];
			_tables[hashi] = cur;
			++_size;
			
			return true;
		}
		// 查找
		Node* Find(const K& key)
		{
			if (_tables.size() == 0) // 未存入数据
				return nullptr;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_data.first == key)
					return cur;
				cur = cur->_next;
			}
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			if (_tables.size() == 0) // 未存入数据
				return false;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_data.first == key) // 找到需删除的数据
				{
					if (prev == nullptr) // 删除的是桶的第一个数据
						_tables[hashi] = cur->_next;
					else
						prev->_next = cur->_next;
					delete cur;
					--_size;
					
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
		// 有效数据个数
		size_t Size()
		{
			return _size;
		}
		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}
		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
					++num;
			}
			return num;
		}
		// 桶的最长长度
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}
				if (len > maxLen)
					maxLen = len;
			}
			return maxLen;
		}
	private:
		vector<Node*> _tables; // 哈希表
		size_t _size = 0; // 有效数据个数
	};
	void TestHT()
	{
		int n = 19000000;
		vector<int> v;
		v.reserve(n);
		srand(time(0));
		//rand()所提供的数最多位3万多,所以在远大于3万会大量数重复
		for (int i = 0; i < n; ++i)
		{
			//v.push_back(i);
			v.push_back(rand() + i);  // 重复少
			//v.push_back(rand());  // 重复多
		}
		size_t begin1 = clock();
		HashBucket<int, int> hb;
		for (auto e : v)
		{
			hb.Insert(make_pair(e, e));
		}
		size_t end1 = clock();
		cout << "数据个数:" << hb.Size() << endl;
		cout << "表的长度:" << hb.TablesSize() << endl;
		cout << "桶的个数:" << hb.BucketNum() << endl;
		cout << "平均每个桶的长度:" << (double)hb.Size() / (double)hb.BucketNum() << endl;
		cout << "最长的桶的长度:" << hb.MaxBucketLenth() << endl;
		cout << "负载因子:" << (double)hb.Size() / (double)hb.TablesSize() << endl;
	}
}
int main()
{
	cr::TestHT();
	return 0;
} 
 




















