哈希算法(闭散列)- 支持string(仿函数的应用 / 偏特化)

仿函数的应用

仿函数的应用就是替代C语言中的函数指针,仿函数就是模板 + 结构体 + 运算符重载

闭散列仿函数实现方式1

直接分开定义:模板定义一类结构体,string特殊的单独定义

// 直接string类型单独定义一个结构体
// HashFunc<class K>做Hash(仿函数)的缺省参数
template<class K>
struct HashFunc
{
	size_t operator()(const K& k)
	{
		return (size_t) k;
	}
};

struct HashString
{
	size_t operator()(const string& k)
	{
		// BKDR
		size_t hashi = 0;
		for (auto e : k)
		{
			hashi *= 31;
			hashi += e;
		}

		return hashi;
	}
};

直接string类型单独定义一个结构体
HashFunc<class K>做Hash(仿函数)的缺省参数

使用说明,当使用int参数时可以不加Hash(仿函数的声明),但是要使用string时实例化的是后必须要加class Hash的声明,确定使用的是string转成int的仿函数

模板定义的改变 - 增加了仿函数的类 - class Hash = HashFunc<K>(带缺省值 - int可用)

// Hash是仿函数的模板
// 如果不传默认走模板 - HashFunc<K>
template<class K, class V, class Hash = HashFunc<K>>

如果是以上方式一的仿函数实现方式,那么实例化时若是string类型需要手动传入HashString的结构体声明,确保调用此结构体中的仿函数

// 实例化
HashTable<string, string, HashString> ht;

仿函数实现一完整程序

// 仿函数 - 未使用偏特化(仿函数实现方式1)
template<class K>
struct HashFunc
{
	size_t operator()(const K& k)
	{
		return (size_t) k;
	}
};

struct HashString
{
	size_t operator()(const string& k)
	{
		size_t hashi = 0;
		for (auto e : k)
		{
			hashi *= 31;
			hashi += e;
		}

		return hashi;
	}
};

namespace open_adress
{
	enum Status
	{
		Empty,
		Exist,
		Delete
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;
	};

	//一个结构体 / 一个类对应一个模板
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		// 构造函数
		HashTable()
		{
			_tables.resize(10);
		}

		// 线性探测
		bool Insert_Linear(const pair<K, V>& kv)
		{
			// string类型仿函数特殊声明
			Hash _hf; 

			if (Find(kv.first))
				return false;
			// 考虑扩容
			// 当负载因子等于0.7时就扩容
			if (_n * 10 / _tables.size() == 7)
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(newSize);

				for (size_t i = 0; i < _tables.size(); ++i)
				{
					if(_tables[i]._s == Exist)
						newHT.Insert_Linear(_tables[i]._kv);
				}

				//_tables.swap(newHT._tables);
				newHT._tables.swap(_tables);
			} 
			
			size_t hashi = _hf(kv.first) % _tables.size();

			while (_tables[hashi]._s == Exist)
			{
				++hashi;
				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._s = Exist;
			++_n;
			return true;
		}

		// 二次探测
		bool Insert_Twice(const pair<K, V>& kv)
		{
			Hash _hf;

			if (Find(kv.first))
				return false;

			if (_n * 10 / _tables.size() == 7)
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K, V, Hash> newHt;
				newHt._tables.resize(newSize);
				
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					if (_tables[i]._s == Exist)
						newHt.Insert_Twice(_tables[i]._kv);
				}
					
				newHt._tables.swap(_tables);
			}

			size_t hashi = _hf(kv.first) % _tables.size();
			size_t hashtwice = 0;

			while (_tables[hashi]._s == Exist)
			{
				hashi = _hf(kv.first) % _tables.size();
				//++hashtwice; // 优化
				// Warning: +=: 从double转换到size_t,可能丢失数据
				hashi += size_t(pow(++hashtwice, 2));
				hashi %= _tables.size();
			}
			
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = Exist;
			++_n;

			return true;
		}

		// Delete状态存在的意义:如果没有Delete状态,会导致Empty后面的数据均无法访问
		HashData<K, V>* Find(const K& k)
		{
			Hash _hf;
			size_t hashi = _hf(k) % _tables.size();

			while (_tables[hashi]._s != Empty)
			{
				// 易错点:如果此值是已经删除的值,那么也会被当作是存在的值,则被删除的值也会被Find
				//无法重新插入
				// 但插入值是只要Find不到就可以插入,所以会无法插入目标值,或把目标值覆盖
				// if (_tables[hashi]._kv.first == k)
				if(_tables[hashi]._s == Exist
				&& _tables[hashi]._kv.first == k)
				{
					return &_tables[hashi];
				}

				++hashi;
				hashi %= _tables.size();
			}

			return nullptr;
		}

		// 伪删除法
		bool Erase(const K& k)
		{
			HashData<K, V>* ret = Find(k);
			
			if (ret)
			{
				ret->_s = Delete;
				--_n;
				return true;
			}

			return false;
		}

		void Print()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i]._s == Exist)
				{
					//cout << "[" << i << "]-> " << _tables[i]._kv.first << endl;
					cout << "[" << i << "]-> " << _tables[i]._kv.first << " -> " << _tables[i]._kv.second << endl;
				}

				else if (_tables[i]._s == Empty)
				{
					cout << "[" << i << "]-> " << "Empty" << endl;
				}

				else
				{
					cout << "[" << i << "]-> " << "Delete" << endl;
				}
			}

			cout << endl;
		}


	private:
		vector<HashData<K, V>> _tables;
		size_t _n;
	};
	void TestString()
	{
		// 模板实例化
		HashTable<string, string, HashString> ht;
		
		ht.Insert_Twice(make_pair("Sort", "排序"));
	    ht.Insert_Twice(make_pair("Left", "左边"));
		ht.Insert_Twice(make_pair("Right", "右边"));
		ht.Insert_Twice(make_pair("Middle", "中间"));

		ht.Print();
	}
};

闭散列仿函数实现方式2

库中的unordered_map是不用在实例化时传HashString,(模板时要带有参数class Hash = HashFun<K>),要实现此效果就是要使用模板的偏特化template<>,模板偏特化的定义就是找最匹配,将string偏特化,如果是其它类型就走模板template<class K>,其余则走template<>,函数名均是HashFunc,所以缺省值中的HashFun<K>变化,不用再实例化传HashString

 仿函数 - 模板 + 结构体 + 运算符重载 - 意义:替代函数指针
template<class K>
struct HashFunc
{
	size_t operator()(const K& k)
	{
		return (size_t) k;
	}
};

// 偏特化 - 特殊化string,当为string时默认走此模板,偏特化的意义是在类型不是string的情况下走默认K的模板,在string走特殊化模板
template<>
struct HashFunc<string>
{
	size_t operator()(const string& k)
	{
		size_t hashi = 0;
		for (auto e : k)
		{
			hashi *= 31;
			hashi += e;
		}

		return hashi;
	}
};

缺省值是class Hash = HashFun<K>,使用偏特化,两个结构体相同,都调用同一个结构体,所以不用实例化传结构体,只是最优匹配原则,string类型会自动匹配HashFunc<string>,这样就解决了实例化要传HashString的问题,现在仅需要传key / value的类型即可

仿函数实现误区

以下为仿函数使用的错误案例:将string和K类型的封装再一起,在K为int的时候可以正常使用(二者构成函数重载 - 参数类型不同),但当K为string时就会冲突,二者函数名和参数均相同,就会产生冲突

//Error
template<class K>
struct HashFunc
{
	size_t operator()(const K& k)
	{
		return (size_t)k;
 
	}
	
	// string时会产生冲突
	size_t operator()(const string& k)
	{
		size_t hashi = 0;
		for (auto e : k)
		{
			hashi *= 31;
			hashi += e;
		}

		return hashi;
	}
};

string类型支持转换成key的测试代码

封装在Hashtable.h中,main函数中直接域作用名调用

void TestString()
{
	HashTable<string, string> ht;
		
	//ht.Insert_Linear(make_pair("Sort", "排序"));
	//ht.Insert_Linear(make_pair("Left", "左边"));
	//ht.Insert_Linear(make_pair("Right", "右边"));
	//ht.Insert_Linear(make_pair("Middle", "中间"));

	ht.Insert_Twice(make_pair("Sort", "排序"));
	ht.Insert_Twice(make_pair("Left", "左边"));
	ht.Insert_Twice(make_pair("Right", "右边"));
	ht.Insert_Twice(make_pair("Middle", "中间"));

	ht.Print();
}

总结:仿函数:模板 + 结构体 + 运算符重载 / 简化:模板 + 模板偏特化 + 结构体 + 运算符重载

End!!!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值