C++ 哈希表模拟实现(补充)

目录

1.引例:两个练习题

2.定义基本的存储结构

3.Insert()

4.Erase()

5.string为参数的情况

6.添加类模板 

7.整体代码


1.引例:两个练习题

2.定义基本的存储结构

#pragma once
namespace CloseHash
{
	template<class K,class V>
	struct HashData
	{
		pair<K, V> _kv;
	};

	template<class K,class V>
	class HashTable
	{
	private:
		HashData* _table;
		size_t _size;
		size_t _capacity; //用于增容
	};
}

后者直接用vector存储

#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace CloseHash
{
	template<class K,class V>
	struct HashData
	{
		pair<K, V> _kv;
	};

	template<class K,class V>
	class HashTable
	{
    public://待写
	private:
		//HashData* _table;
		//size_t _size;
		//size_t _capacity; //用于增容
		vector<HashData> _table;
		size_t _n;//存储的有效数据的个数
	};
}

3.Insert()

如何判断一个位置是空还是满?若删除其中一个值,用空值去标识,下一次找后面冲突的值找不到了。前一个冲突的值被删了,导致不连续了,找到空就结束了。

 提供一个状态标识即可

 namespace CloseHash
{
	enum State//状态
	{
		EMPTY,
		EXITS,
		DELTE,
	};
	template<class K,class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;//默认状态给空
	};

	template<class K,class V>
	class HashTable
	{
	public://待写函数
	private:
		//HashData* _table;
		//size_t _size;
		//size_t _capacity; //用于增容
		vector<HashData> _table;
		size_t _n;//存储的有效数据的个数
	};
}
 bool Insert(const pair<K, V>& kv)
{
	HashData<K, V>* ret = Find(kv, first);
	if (ret)
	{
		return false;
	}
	//判断是否满
	//计算负载因子,大于0.7就增容
	//if (_n*10 / _table.size() > 7)
	if (_tabale.size() == 0)
	{
		_table.resize(10);
	}
	else if ((double)_n / (double)_table.size() > 0.7)
	{
		//方法一,不足之处在于要重复写插入逻辑
		//vector<HashData>newtable;
		//newtable.resize(_table.size*2);  新表增容,增到旧表的二倍
		//for (auto& e : _table)
		//{
		//	if (e._table == EXITS)
		//	{
		//		//重新计算放到newtable中去
		//		//跟下面插入逻辑类似
		//	}
		//}
		//_table.swap(newtable);

		HashTable<K, v>newHT;
		newHT._table.resize(_table.size() * 2);
		for (auto& e : _table)
		{
			if (e._state == EXITS)
			{
				newHT.Insert(e._kv);
			}
		}
		_table.swap(newHT._table);
	}

	size_t start = kv.first % _table.size();//不能%_table.capacity()
	size_t index = start;

	//探测后面的位置 -- 线性探测或二次探测
	size_t i = 1;
	while (_table[index]._state == EXITS)
	{
		index += start + i;//此处的线性探测想改成二次探测将i改为i*i即可
		index %= _table.size();
		++i;
	}

	_table[index]._kv = kv;
	_table[index]._state = EXITS;
	++_n;

	return true;
}

HashData<K, V>* Find(const K& key)
{
	//排除0的情况
	if (_table.size() == 0)
	{
		return nullptr;
	}
	size_t start = key % _table.size();
	size_t index = start;
	size_t i = 1;
	while (_table[index]._state != EMPTY)
	{
		if (_table[index]._state == EXITS//避免已经删除的元素仍可被寻找的情况
			&& _table[index]._kv.first == key)
		{
			return &_table[index];
		}
		inded = start + i;
		index %= _table.size();
		++i;
	}
	return nullptr;
}

4.Erase()

bool Erase(const K& key)
{
	HashData<K, V>* ret = Find(key);
	if (ret == nullptr)
	{
		return false;
	}
	else
	{
		ret->_state = DELETE;
		return true;
	}
}

5.string为参数的情况

若出现下面测试代码的情况,如何更改?

void TestHashTable2()
{
	string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
	HashTable<string, int> ht;
	for (auto str : a)//string作k的情况
	{
		auto ret = ht.Find(str);
		if (ret)
		{
			ret->_kv.second++;
		}
		else
		{
			ht.Insert(make_pair(str, 1));
		}
	}
}

所有要取模的地方添加仿函数,该仿函数的功能就是将数据转换成整型。

struct IniHashFunc
{
	int operator()(int i)
	{
		return i;
	}
};

 

特别的,将字符串转换成整型的仿函数仍存在问题。采用字符串哈希算法的思路改正即可    各种字符串Hash函数 - clq - 博客园

以此类推,K为不同类型元素的情况写对应的仿函数即可

思考:

 一个类型去作map/set的Key有什么要求? 能支持比较大小

一个类型去作unordered_map/unordered_set的Key有什么要求?  能支持转换成整型+相等比较

6.添加类模板 

可以转化成整型的数据采用下面模板,返回值为int

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

当K是string时不会走原生的模板,采用特化单独创建一个模板

template<>
struct Hash<string>
{
	//字符串转成对应的整型,整型才能取模算映射位置
	//期望->字符串不同,转出的整型值尽量不同
	size_t operator()(const string& s)
	{
		//return s[0];  存在int insert类似首字母相同的情况

		//存在abcd,bcad或abbb.abca的情况(ascii码和相同的情况)
		size_t value = 0;
		for (auto ch : s)
		{
			value += ch;
			value *= 131;//采用BKDRHash算法的思想 乘以131
		}
		return value;
	}
};

使用类模板后不同的数据类型会调用不同类型的仿函数 

 此时可以省略仿函数,参数自动使用隐式类型转换

 

7.整体代码

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

namespace CloseHash
{
	enum State
	{
		EMPTY,
		EXITS,
		DELETE,
	};

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

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

	// 特化
	template<>
	struct Hash<string>
	{
		// "int"  "insert" 
		// 字符串转成对应一个整形值,因为整形才能取模算映射位置
		// 期望->字符串不同,转出的整形值尽量不同
		// "abcd" "bcad"
		// "abbb" "abca"
		size_t operator()(const string& s)
		{
			// BKDR Hash
			size_t value = 0;
			for (auto ch : s)
			{
				value += ch;
				value *= 131;
			}

			return value;
		}
	};

	template<class K, class V, class HashFunc = Hash<K>>
	class HashTable
	{
	public:
		bool Insert(const pair<K, V>& kv)
		{
			HashData<K,V>* ret = Find(kv.first);
			if (ret)
			{
				return false;
			}

			// 负载因子大于0.7,就增容
			//if (_n*10 / _table.size() > 7)
			if (_table.size() == 0)
			{
				_table.resize(10);
			}
			else if ((double)_n / (double)_table.size() > 0.7)
			{
				//vector<HashData> newtable;
				// newtable.resize(_table.size*2);
				//for (auto& e : _table)
				//{
				//	if (e._state == EXITS)
				//	{
				//		// 重新计算放到newtable
				//		// ...跟下面插入逻辑类似
				//	}
				//}

				//_table.swap(newtable);

				HashTable<K, V, HashFunc> newHT;
				newHT._table.resize(_table.size() * 2);
				for (auto& e : _table)
				{
					if (e._state == EXITS)
					{
						newHT.Insert(e._kv);
					}
				}

				_table.swap(newHT._table);
			}

			HashFunc hf;
			size_t start = hf(kv.first) % _table.size();
			size_t index = start;

			// 探测后面的位置 -- 线性探测 or 二次探测
			size_t i = 1;
			while (_table[index]._state == EXITS)
			{
				index = start + i;
				index %= _table.size();
				++i;
			}

			_table[index]._kv = kv;
			_table[index]._state = EXITS;
			++_n;

			return true;
		}

		HashData<K,V>* Find(const K& key)
		{
			if (_table.size() == 0)
			{
				return nullptr;
			}

			HashFunc hf;
			size_t start = hf(key) % _table.size();
			size_t index = start;
			size_t i = 1;
			while (_table[index]._state != EMPTY)
			{
				if (_table[index]._state == EXITS 
					&& _table[index]._kv.first == key)
				{
					return &_table[index];
				}

				index = start + i;
				index %= _table.size();
				++i;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret == nullptr)
			{
				return false;
			}
			else
			{
				ret->_state = DELETE;
				return true;
			}
		}

	private:
	/*	HashData* _table;
		size_t _size;
		size_t _capacity;*/
		vector<HashData<K, V>> _table;
		size_t _n = 0;  // 存储有效数据的个数
	};

	void TestHashTable1()
	{
		int a[] = { 1, 5, 10, 100000, 100, 18, 15, 7, 40};
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		auto ret = ht.Find(100);
		if (ret)
		{
			cout << "找到了"<<endl;
		}
		else
		{
			cout << "没有找到了" << endl;
		}

		ht.Erase(100);

		ret = ht.Find(100);
		if (ret)
		{
			cout << "找到了" << endl;
		}
		else
		{
			cout << "没有找到了" << endl;
		}
	}

	struct stringHashFunc
	{
		// "int"  "insert" 
		// 字符串转成对应一个整形值,因为整形才能取模算映射位置
		// 期望->字符串不同,转出的整形值尽量不同
		// "abcd" "bcad"
		// "abbb" "abca"
		size_t operator()(const string& s)
		{
			// BKDR Hash
			size_t value = 0;
			for (auto ch : s)
			{
				value += ch;
				value *= 131;
			}

			return value;
		}
	};

	void TestHashTable2()
	{
		string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
		HashTable<string, int> ht;
		for (auto str : a)
		{
			auto ret = ht.Find(str);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(str, 1));
			}
		}
	}

	struct Student
	{
		// ...
	};

	struct StudentHashFunc
	{
		size_t operator()(const Student& kv)
		{
			// 如果是结构体
			// 1、比如说结构体中有一个整形,基本是唯一值 - 学号
			// 2、比如说结构体中有一个字符串,基本是唯一值 - 身份证号
			// 3、如果没有一项是唯一值,可以考虑多项组合
			size_t value = 0;
			// ...
			return value;
		}
	};

	void TestHashTable3()
	{
		string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
		// 任意类型都可以做key,跟上一个把这个类型对象转换成整形的仿函数即可
		HashTable<Student, int, StudentHashFunc> ht;
	}

	void TestStringHashFunc()
	{
		stringHashFunc hf;
		cout << hf("insert")<<endl;
		cout << hf("int") << endl<<endl;

		cout << hf("abcd") << endl;
		cout << hf("bacd") << endl << endl;

		cout << hf("abbb") << endl;
		cout << hf("abca") << endl << endl;
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值