『C++进阶』第五章--- 哈希

目录

1. unordered系列关联式容器

1.1 unordered_map

1.2 unordered_set

2. 底层结构

2.1 哈希概念

2.2 哈希冲突

2.3 哈希函数

2.4 哈希冲突解决

2.4.1 闭散列(开放寻址法)

        1. 线性探测

2. 二次探测

2.4.2 开散列(开链法)

    1. 开散列概念

2. 开散列实现

3. 开散列增容

4. 开散列的思考

3. 模拟实现

3.1 哈希表的改造

       1. 模板参数列表的改造

2. 增加迭代器操作

3. 增加通过key获取value操作

3. 2 unordered_set

3.3 unordered_map

3.4 哈希表底层实现


1. unordered系列关联式容器

    在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到log_2 N, 即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就就能将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset可自行查看文档介绍。

1.1 unordered_map

 unordered_map在线文档说明

  1.  unordered_map是存储<key,value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<key,value> 按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map 将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是前向迭代器。

1.2 unordered_set

        参见 unordered_set在线文档说明

2. 底层结构

     unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

2.1 哈希概念

        顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较,顺序查找时间复杂度为o(N),平衡树中为树的高度,即o(log_2 N), 搜索到的效率取决于搜索过程中元素的比较次数。

        理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。

        如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立——映射关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  •  插入元素

         根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

  •  搜索元素

         对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相同,则搜索成功。

        该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

       例如: 数据集合{1,7,6,4,5,9};

       哈希函数设置为: hash(key) = key % capacity; capacity 为存储元素底层空间总的大小。

 用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

 问题: 按照上述哈希方式,向集合中插入元素44,会出现什么问题?

2.2 哈希冲突

        对于两个数据元素的关键字key(i)和key(j)(i!=j),有key(i)!=key(j),但有:hash(keyi)==hash(keyj), 即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 我们也把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

        那么发生哈希冲突该如何处理呢?

2.3 哈希函数

     引起哈希冲突的一个原因可能是: 哈希函数设计不够合理。

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址,其值必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见哈希函数

       1.直接定址法--(常用)

          取关键字的某个线性函数为散列地址: hash(key) = A*key +B

          优点: 简单、均匀

          缺点: 需要事先知道关键字的分布情况

          使用场景: 适合查找比较小且连续的情况

        2.​​​​​​ 除留余数法--(常用)

             设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数: hash(key) = key%p(p<=m), 将关键码转换成哈希地址

       3. 平方取中法--(了解)
           假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址

           平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

      4. 折叠法--(了解)

        折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。

        折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

      5. 随机数法--(了解)

        选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。

        通常应用于关键字长度不等时采用此法

      6. 数学分析法--(了解)

        设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如

         假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。
        数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况

        注意: 哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

2.4 哈希冲突解决

        解决哈希冲突两种常见的方式是: 闭散列开散列

2.4.1 闭散列(开放寻址法)

        闭散列: 也叫开放定址法,当发生哈希冲突是,如过哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去。那如何寻找下一个空位置呢?

        1. 线性探测

        比如 2.1 中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

        线性探测: 从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  •  插入
    • 通过哈希函数获取待插入元素在哈希表中的位置
    • 如果该位置没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素

  • 删除

        采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44 查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

思考: 哈希表说明情况下进行扩容? 如何扩容?

开放寻址法完整代码:

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

namespace open_search
{
	enum State
	{
		EXIST,
		EMPTY,
		DELETE
	};
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template< class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_Table.resize(10);
		}
		bool Insert(const pair<K, V>& kv)
		{
			// 查重
			if (Find(kv.first)) return false;
			size_t n = _Table.size();
			// 检查负重因子时候进行扩容
			if (_nums * 10 / n >= 7)
			{
				HashTable HT;
				n = n * 2;
				HT._Table.resize(n);
				for (auto& x : _Table)
				{
					if (x._state == EXIST)
					{
						HT.Insert(x._kv);
					}
				}
				_Table.swap(HT._Table);
			}
			Hash hs;
			size_t t = hs(kv.first) % n;
			while (_Table[t]._state == EXIST)
			{
				t++;
				t %= n;  // 旋转寻找
			}
			_Table[t]._kv = kv;
			_Table[t]._state = EXIST;
			_nums++;
			return true;
		}
		HashData<K,V>* Find(const K& key)
		{
			Hash hs;
			size_t n = _Table.size();
			size_t t = hs(key) % n;
			while (_Table[t]._state !=EMPTY)   // 可能被删除的节点在两个存在的节点中间
			{
				if (_Table[t]._state==EXIST && _Table[t]._kv.first == key) return &_Table[t];
				t++;
				t %= n;
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			auto t = Find(key);
			if (t)
			{
				t->_state = DELETE;
				return true;
			}
			else return false;
		}


	private:
		vector<HashData<K, V>> _Table;
		size_t _nums = 0;
	};
	void TestHT1()
	{
		HashTable<int, int> ht;
		int a[] = { 11,21,4,14,24,15,9 };
		for (auto e : a)
		{
			ht.Insert({ e,e });
		}

		ht.Insert({ 19,19 });
		ht.Insert({ 19,190 });
		ht.Insert({ 19,1900 });
		ht.Insert({ 39,1900 });

		cout << ht.Find(24) << endl;
		ht.Erase(4);
		cout << ht.Find(24) << endl;
		cout << ht.Find(4) << endl;
	}
}
	


        线性探测优点: 实现非常简单

        线性探测缺点: 一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同的关键码占据了可利用的空位置,形成局部堵塞,使得寻找某个关键码的位置需要许多次比较,导致搜索效率降低。那么如何缓解呢?

2. 二次探测

       线性探测的缺陷是产生冲突的数据推挤在一块,这与其找下一个空位置有关,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:i=(t+i^2)% n 。 其中i=1,2,3,4... t是通过散列函数Hash(x) 对元素的关键码key进行计算得到的位置,n是表的大小

        对于2.1 中的如果要插入44,产生冲突,使用解决后的情况为:

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任
何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在
搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,
如果超出
必须考虑增容。

        因此: 比散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

2.4.2 开散列(开链法)

    1. 开散列概念

        开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头节点存储在哈希表中。

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

2. 开散列实现
namespace Hash_bucket
{
	template< class K,class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;
		HashNode(const pair<K,V>& kv )
			: _kv(kv),
			_next(nullptr)
		{}

	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;

	public:
		HashTable()
		{
			_Table.resize(10,nullptr);
		}
		~HashTable()
		{
			for (size_t i=0;i<_Table.size();i++)
			{
				Node* cur = _Table[i];
				while (cur)
				{
					Node* t = cur->_next;
					delete cur;
					cur = t;
				}
				_Table[i] = nullptr;
			}
		}
		bool Insert(const pair<K, V>& kv)
		{
			// 查重
			if(Find(kv.first)) return false;
			Hash hs;
			int n = _Table.size();
            // 扩容
			if (_nums == n)
			{
				n = n * 2;
				vector<Node*> newTable(n, nullptr);
				for (size_t i = 0; i < _Table.size(); i++)
				{
					Node* cur = _Table[i];
					while (cur)
					{
						int t = hs(cur->_kv.first) % n;
						Node* temp = cur->_next;
						// 因为第一次插和第n次插都是一样的,所以不需要特殊处理
						cur->_next = newTable[t];
						newTable[t] = cur;
						cur = temp;
					}
					_Table[i] = nullptr;
				}
				_Table.swap(newTable);
			}
			int t = hs(kv.first) % n;
			Node* cur = new Node(kv);
			cur->_next = _Table[t];
			_Table[t] = cur;
			_nums++;
			return true;
		}
		Node* Find(const K& key)
		{
			Hash hs;
			int n = _Table.size();
			int t = hs(key) % n;
			for (int i = 0; i < n; i++)
			{
				Node* cur = _Table[i];
				while (cur)
				{
					if (cur->_kv.first == key) return cur;
					cur = cur->_next;
				}
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			Hash hs;
			int n = _Table.size();
			int t = hs(key) % n;
			Node* cur = _Table[t];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr) _Table[t] = nullptr;
					else
					{
						prev->_next = cur->_next;
						delete cur;
						_nums--;
						return true;
					}
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}


	private:
		vector<Node*> _Table;
		size_t _nums;
	};
3. 开散列增容

        桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能回导致一个桶中链表节点非常多,会影响哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。

         // 扩容
      if (_nums == n)
	{
		n = n * 2;
		vector<Node*> newTable(n, nullptr);
		for (size_t i = 0; i < _Table.size(); i++)
	{
		Node* cur = _Table[i];
		while (cur)
	{
		int t = hs(cur->_kv.first) % n;
		Node* temp = cur->_next;
		// 因为第一次插和第n次插都是一样的,所以不需要特殊处理
		cur->_next = newTable[t];
		newTable[t] = cur;
		cur = temp;
	}
		_Table[i] = nullptr;
	}
    	_Table.swap(newTable);
	}
4. 开散列的思考

        1. 只能存储key为整型的元素,其他类型怎么解决

// 哈希函数采用处理余数法,被模的key必须要为整型才可以处理
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
// 特化 处理key为字符串类型,转换处理为整型
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto& x : key)
		{
			hash *= 31; // 防止出现ash ,sha 这种相似字符串导致的哈希值重复,所以乘上 31 131 1313 13131 131313
			hash += x;
		}
		return hash;
	}
};

2. 除留余数法,最好模一个素数,如何每次快速去一个类似两倍关系的素数? 

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
	};
	size_t i = 0;
	for (; i < PRIMECOUNT; ++i)
	{
		if (primeList[i] > prime)
			return primeList[i];
	}
	return primeList[i];
}

3. 模拟实现

3.1 哈希表的改造

       1. 模板参数列表的改造

  •  K: 关键码类型
  •  V:不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果unordered_set, V为K
  •  keyOfvalue: 因为V的类型v不同,通过value取key的方式就不同,详细见unordered_set/map的实现
  •  HF:哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整型数字才能取模
template <class K,class V,class KeyofValue, class HF= DefHashF<T>>
class HashBucket;

2. 增加迭代器操作

template<class K, class T, class Ptr, class Ref, class  KeyofValue, class Hash>
	struct  HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyofValue, Hash> Table;
		typedef HashIterator<K, T, Ptr, Ref, KeyofValue, Hash> Self;
		Node* _node;
		const Table*  _t;
	public:
		HashIterator(const Node* node, const Table* t)
			: _node()
			, _t(t)
		{}

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

		Ptr operator -> ()
		{
			return &_node->_data;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node && _t->_Table == s._t->_Table;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		Self operator++ ()
		{
			if (_node->_next)
			{
				_node = _node->_next;
				return _node;
			}

			Hash hs;
			KeyofValue kofv;
			size_t t = hs(kofv(_node->_data)) % _t->_Table.size();
			t++;
			while (t < _t->_Table.size())
			{
				if (_t->_Table[t]) break;
				t++;
			}

			if (t < _t.size()) _node = _t->_Table[t];

			else _node=nullptr; // end()
						
			return *this;
		

		}
	};

3. 增加通过key获取value操作

// 哈希函数采用处理余数法,被模的key必须要为整型才可以处理
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
// 特化 处理key为字符串类型,转换处理为整型
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto& x : key)
		{
			hash *= 31; // 防止出现ash ,sha 这种相似字符串导致的哈希值重复,所以乘上 31 131 1313 13131 131313
			hash += x;
		}
		return hash;
	}
};

3. 2 unordered_set

template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:
	struct KeyofValue
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	typedef typename  Hash_bucket::HashTable<K, const K, KeyofValue, Hash>::Iterator iterator;
	typedef typename Hash_bucket::HashTable<K, const K, KeyofValue, Hash>::const_Iterator const_iterator;

	iterator begin()
	{
		return _t.Begin();
	}
	const_iterator begin() const
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}
	const_iterator end() const
	{
		return _t.End();
	}
	pair<iterator, bool> insert(const K& key)
	{
		return _t.Insert(key);
	}
	pair<iterator, bool> find(const K& key)
	{
		return _t.Find(key);
	}
	bool erase(const K& key)
	{
		return _t.Erase(key);
	}

3.3 unordered_map

#include "HashTable.h"

template <class K,class V,class Hash=HashFunc<K>>  // 只对k进行哈希计算
class unordered_map
{
public:
	struct keyofvalue
	{
		const K& operator()(const pair< K, V>& kv) 
		{
			return kv.first;
		}
	};
	typedef typename Hash_bucket::HashTable<K,  pair<const K, V>, keyofvalue, Hash>::Iterator iterator;
	typedef typename Hash_bucket::HashTable<K,  pair<const K, V>, keyofvalue, Hash>::const_Iterator const_iterator;   

	iterator begin()
	{
		return _t.Begin();
	}
	const_iterator begin() const
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}
	const_iterator end() const
	{
		return _t.End();
	}
	pair<iterator, bool> insert(const pair<K,V>& kv)
	{
		return _t.Insert(kv);
	}
	pair<iterator, bool> find(const K& key)
	{
		return _t.Find(key);
	}
	bool erase(const K& key)
	{
		return _t.Erase(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		//return (ret.first)->second;
		return  (*(ret.first)).second;
		
	}


private:
	Hash_bucket::HashTable<K,  pair<const K, V>, keyofvalue,Hash> _t;
	// K不能修改,因为K如果修改,那么会发生混乱

};

3.4 哈希表底层实现

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

// 哈希函数采用处理余数法,被模的key必须要为整型才可以处理
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
// 特化 处理key为字符串类型,转换处理为整型
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto& x : key)
		{
			hash *= 31; // 防止出现ash ,sha 这种相似字符串导致的哈希值重复,所以乘上 31 131 1313 13131 131313
			hash += x;
		}
		return hash;
	}
};


namespace open_search
{
	enum State
	{
		EXIST,
		EMPTY,
		DELETE
	};


	template< class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_Table.resize(10);
		}
		bool Insert(const pair<K, V>& kv)
		{
			// 查重
			if (Find(kv.first)) return false;
			size_t n = _Table.size();
			// 检查负重因子时候进行扩容
			if (_nums * 10 / n >= 7)
			{
				HashTable HT;
				n = n * 2;
				HT._Table.resize(n);
				for (auto& x : _Table)
				{
					if (x._state == EXIST)
					{
						HT.Insert(x._kv);
					}
				}
				_Table.swap(HT._Table);
			}
			Hash hs;
			size_t t = hs(kv.first) % n;
			while (_Table[t]._state == EXIST)
			{
				t++;
				t %= n;  // 旋转寻找
			}
			_Table[t]._kv = kv;
			_Table[t]._state = EXIST;
			_nums++;
			return true;
		}
		HashData<K,V>* Find(const K& key)
		{
			Hash hs;
			size_t n = _Table.size();
			size_t t = hs(key) % n;
			while (_Table[t]._state !=EMPTY)   // 可能被删除的节点在两个存在的节点中间
			{
				if (_Table[t]._state==EXIST && _Table[t]._kv.first == key) return &_Table[t];
				t++;
				t %= n;
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			auto t = Find(key);
			if (t)
			{
				t->_state = DELETE;
				return true;
			}
			else return false;
		}


	private:
		vector<HashData<K, V>> _Table;
		size_t _nums = 0;
	};

}
	
namespace Hash_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  KeyofValue, class Hash>
	class HashTable;

	template<class K, class T, class Ptr, class Ref, class  KeyofValue, class Hash>
	struct  HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyofValue, Hash> Table;
		typedef HashIterator<K, T, Ptr, Ref, KeyofValue, Hash> Self;
		Node* _node;
		const Table*  _t;
	public:
		HashIterator(Node* node, const Table* t)
			: _node(node)
			, _t(t)
		{}

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

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

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

		Self operator++ ()
		{
			if (_node->_next)
			{
				_node = _node->_next;
				return *this;
			}

			Hash hs;
			KeyofValue kofv;
			size_t t = hs(kofv(_node->_data)) % _t->_Table.size();
			t++;
			while (t < _t->_Table.size())
			{
				if (_t->_Table[t]) break;
				t++;
			}

			if (t < _t->_Table.size()) _node = _t->_Table[t];

			else _node=nullptr; // end()
						
			return *this;
		

		}
	};

	template<class K, class T, class  KeyofValue, class Hash>
	class HashTable
	{
	public:
		typedef HashNode<T> Node;
		template<class K, class T, class Ptr, class Ref, class  KeyofValue, class Hash>
		friend struct  HashIterator;

		typedef HashIterator<K, T, T*, T&, KeyofValue, Hash> Iterator;
		typedef HashIterator< K,  T, const T*, const T&, KeyofValue, Hash> const_Iterator;



		Iterator Begin()
		{
			int n = _Table.size();
			if (n == 0) return End();
			for (int i = 0; i < n; i++)
			{
				if (_Table[i])
				{	
					return Iterator(_Table[i], this);
				}
			}
			return End();
		}
		Iterator Begin() const 
		{
			int n = _Table.size();
			if (n == 0) return End();
			for (int i = 0; i < n; i++)
			{
				if (_Table[i])
				{
					return Iterator(_Table[i], this);
				}
			}
			return End();
		}
		Iterator End()
		{
			return Iterator(nullptr, this);

		}
		Iterator End() const 
		{
			return Iterator(nullptr, this);

		}

		HashTable()
		{
			_Table.resize(10,nullptr);
		}
		~HashTable()
		{
			for (size_t i=0;i<_Table.size();i++)
			{
				Node* cur = _Table[i];
				while (cur)
				{
					Node* t = cur->_next;
					delete cur;
					cur = t;
				}
				_Table[i] = nullptr;
			}
		}
		pair<Iterator,bool> Insert(const T& data)
		{
			// 查重
			KeyofValue kofv;
			Iterator temp = Find(kofv(data));
			if (temp != End()) return make_pair(temp, false);
			Hash hs;
			int n = _Table.size();
			if (_nums == n)
			{
				n = n * 2;
				vector<Node*> newTable(n, nullptr);
				for (size_t i = 0; i < _Table.size(); i++)
				{
					Node* cur = _Table[i];
					while (cur)
					{
						int t = hs(kofv(cur->_data)) % n;
						Node* temp = cur->_next;
						// 因为第一次插和第n次插都是一样的,所以不需要特殊处理
						cur->_next = newTable[t];
						newTable[t] = cur;
						cur = temp;
					}
					_Table[i] = nullptr;
				}
				_Table.swap(newTable);
			}
			int t = hs(kofv(data)) % n;
			Node* cur = new Node(data);
			cur->_next = _Table[t];
			_Table[t] = cur;
			_nums++;
			return make_pair(Iterator(cur,this),true);
		}
		Iterator Find(const K& key)
		{
			Hash hs;
			KeyofValue kofv;
			int n = _Table.size();
			int t = hs(key) % n;
			for (int i = 0; i < n; i++)
			{
				Node* cur = _Table[i];
				while (cur)
				{
					if (kofv(cur->_data) == key) return Iterator(cur,this);
					cur = cur->_next;
				}
			}
			return End();
		}
		bool Erase(const K& key)
		{
			Hash hs;
			KeyofValue kofv;
			int n = _Table.size();
			int t = hs(key) % n;
			Node* cur = _Table[t];
			Node* prev = nullptr;
			while (cur)
			{
				if (kofv(cur->_data) == key)
				{
					if (prev == nullptr) _Table[t] = nullptr;
					else
					{
						prev->_next = cur->_next;
						delete cur;
						_nums--;
						return true;
					}
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}


	private:
		vector<Node*> _Table;
		size_t _nums;
	};

}

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值