实现哈希表

之前已经学过哈希表的概念,哈希函数,哈希冲突等相关知识,如果忘记了可以回顾一下—》了解什么是哈希表

本次我们就简单的实现一下哈希表。哈希函数将采用除留余数法,用开放定址法(一次探测和二次探测)和链地址法处理哈希冲突,话不多说,直接上代码:

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

//模板的偏特化
//如果key为字符串,我们就采用这种方式计算哈希地址,从而减少哈希冲突
template<>
struct Hash<string>
{
	size_t operator()(const string& s)
	{
		//BKDR哈希
		size_t value = 0;
		for (auto ch : s)
		{
			value *= 31;
			value += ch;
		}
		return value;
	}
};


//表长不为素数,二次探测不一定总是能找到一个不发生哈希冲突的地址
//所以将其改为素数扩容,就能解决此问题
size_t GetNextPrime(size_t num)
{
	static const unsigned long prime[28] =
	{
		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
	};
	
	for (size_t i = 0; i < 28; ++i)
	{
		if (prime[i] > num)
		{
			return prime[i];
		}
	}
	return prime[27];
}

//开放定址法
namespace CloseHash
{

	//3总状态,分别表示存在,空和删除
	enum status
	{
		EXIST,
		EMPTY,
		DELETE
	};

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


	template<class K, class V, class HashFunc = Hash<K>>
	class HashTable
	{
	public:

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret == nullptr)
				return false;
			else
				ret->_status = DELETE;//改变状态即可
			_n--;
			return true;
		}


		HashData<K, V>* Find(const K& key)
		{
			//表中没有数据或者表的大小为0,则直接返回nullptr
			if (_tables.size() == 0 || _n == 0)
				return nullptr;

			HashFunc hf;
			size_t start = hf(key) % _tables.size();//不能%capacity,因为不能访问size之外的空间
			size_t i = 0;
			size_t index = start;
			//线性探测
			//只要当前位置不为空,就继续找,不可能死循环,因为表中不可能被填满,因为负载因子大于等于0.7就扩容
			while (_tables[index]._status != EMPTY)
			{
				if (_tables[index]._kv.first == key && _tables[index]._status == EXIST)
					return &_tables[index];//找到返回数据的引用

				++i;
				//一次探测
				index = start + i;
				//二次探测 
				//index = start + i*i;
				index %= _tables.size();
			}
			//找不到
			return nullptr;
		}

		bool Insert(const pair<K, V>& kv)
		{
			HashData<K, V>* ret = Find(kv.first);
			if (ret != nullptr)
				return false;


			//当size大小为0(表示插入第一个数据)或者负载因子大于等于0.7就扩容
			//负载因子越小冲突的概率越低,空间浪费就越多,负载因子越大,冲突概率就越大,浪费的空间就越小
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				//扩容
				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;//按照二倍进行扩容
				//按素数扩容,减少哈希冲突
				size_t newsize = GetNextPrime(_tables.size());

				//vector> newTable;
				//newTable.resize(newsize);

				遍历原表,把原表中的数据,重新按newsize映射到新表
				//for (size_t i = 0; i < _tables.size(); ++i)
				//{
				//	//但是这么做不够简单
				//}
				
				//
				HashTable<K, V, HashFunc> newHT;
				newHT._tables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					if (_tables[i]._status == EXIST)
						newHT.Insert(_tables[i]._kv);
				}
				_tables.swap(newHT._tables);
				//这里不需要交换_n,因为这是在把原表的数据放在新表,还没有插入数据,所以_n不变
			}

			HashFunc hf;
			size_t start = hf(kv.first) % _tables.size();//不能%capacity,因为不能访问size之外的空间
			size_t i = 0;
			size_t index = start;
			//线性探测
			while (_tables[index]._status == EXIST)
			{
				++i;
				//一次探测
				index = start + i;
				//二次探测 
				//index = start + i*i;
				index %= _tables.size();
			}
			_tables[index]._kv = kv;
			_tables[index]._status = EXIST;
			++_n;

			return true;
		}

	private:

		vector<HashData<K, V>> _tables;
		//存储有效数据的个数
		size_t _n = 0;
	};
}

//**********************************************************************************************************

//链地址法
namespace LinkHash
{
	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 HashFunc = Hash<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}

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

			HashFunc hf;
			//计算key在哪个链表中
			size_t start = hf(key) % _tables.size();//不能%capacity,因为不能访问size之外的空间
			Node* cur = _tables[start];
			//如果key存在,就返回key在链表的具体位置,不存在就返回nullptr
			while (cur != nullptr)
			{
				if (cur->_kv.first == key)
					return cur;
				cur = cur->_next;
			}

			return nullptr;
		}


		bool erase(const K& key)
		{
			if (_tables.empty())
			{
				return false;
			}

			HashFunc hf;
			//计算key在哪个链表中
			size_t start = hf(key) % _tables.size();//不能%capacity,因为不能访问size之外的空间
			Node* cur = _tables[start];
			//保存链表中的前一个结点
			Node* prev = nullptr;
			while (cur != nullptr)
			{
				if (cur->_kv.first == key)
				{
					//头删
					if (prev == nullptr)
						_tables[start] = cur->_next;
					//中间删和尾删
					else
						prev->_next = cur->_next;
					--_n;
					delete cur;
					return true;
				}
				//查找链表的后续位置
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}

			return false;
		}
		
		bool Insert(const pair<K, V>& kv)
		{
			HashFunc hf;
			Node* ret = Find(kv.first);
			//存在kv,不再插入,返回nullptr
			if (ret != nullptr)
				return false;

			//负载因子为1或者插入第一个数据时就扩容
			if (_n == _tables.size())
			{
				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newTables;
				newTables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur != nullptr)
					{
						//保存cur的下一个结点
						Node* next = cur->_next;
						//重新计算哈希位置
						size_t index = hf(cur->_kv.first) % newTables.size();
						//链接到newTables上
						cur->_next = newTables[index];
						newTables[index] = cur; 
						//继续判断下一个,直到nullptr为止
						cur = next;
					}
					_tables[i] = nullptr; 
				}
				_tables.swap(newTables);
				
			}

			size_t index = hf(kv.first) % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[index];
			_tables[index] = newnode;

			++_n;
			return true;
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

代码测试:

//测试开放定址法
void TestHashTable1()
{
	HashTable<int, int> ht;
	int a[] = { 2, 12, 22, 32, 42, 52, 62, 72, 2, 2 };
	for (auto e : a)
	{
		ht.Insert(make_pair(e, e));
	}

	//打印12的地址
	cout << ht.Find(12) << endl;
	ht.Erase(12);
	//因为12已经被删除,所以打印出的地址应该是0
	cout << ht.Find(12) << endl;

	//因为表中没有102,所以打印地址为0
	cout << ht.Find(102) << endl;
	ht.Insert(make_pair(102, 102));
	//插入102后,打印的地址不为0
	cout << ht.Find(102) << endl;
}

实现哈希表_第1张图片

//测试链地址法
void TestHashTable2()
{
	HashTable<int, int> ht;
	int a[] = { 2, 12, 22, 32, 42, 52, 62, 72, 52, 52 };
	for (auto e : a)
	{
		ht.Insert(make_pair(e, e));
	}

	//12在表中存在,所以地址不为空
	cout << ht.Find(12) << endl;

	ht.erase(62);
	ht.erase(72);
	//62和72都被删除,所打印地址为0
	cout << ht.Find(62) << endl;
	cout << ht.Find(72) << endl;

	ht.Insert(make_pair(62, 62));
	//将62插入到表中,打印的地址不为0
	cout << ht.Find(62) << endl;
}

实现哈希表_第2张图片

你可能感兴趣的:(数据结构,C++知识,哈希表的实现,哈希算法,哈希,开放定址法,链地址法)