13【CPP】Hash(闭散列||开散列)

闭散列

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

线性探测

需要定义三个状态,空、删除、存在。因为如果不定义删除状态,那么在删除元素后就会对该元素后面的元素查找产生影响。

实现

#pragma once
#include<vector>
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,class V>
class HashTable
{
public:
	HashTable()
	{
		_tables.resize(10);
	}
public:
	bool Insert(const pair<K, V>& kv)
	{
		//负载因子超过0.7就扩容
		if (_tables.size()==0||_n * 10 / _tables.size() >= 7)
		{
			//扩容要开全新空间,重新算位置
			size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			HashTable<K, V> newht;
			newht._tables.resize(newsize);

			for (auto& data : _tables)
			{
				if (data._state == EXIST)//重新算在新表的位置
				{
					newht.Insert(data._kv);
				}
			}

			_tables.swap(newht._tables);//vector底层直接交换指针就行
		}

		size_t hashi = kv.first % _tables.size();
		size_t i = 1;//控制步长
		size_t index = hashi;

		while (_tables[index]._state==EXIST)//如果存在hashi继续往后走
		{
			index = hashi + i;
			index %= _tables.size();
			i++;
		}

		_tables[index]._kv = kv;
		_tables[index]._state = EXIST;
		_n++;

		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0)
			return false;
		size_t hashi = kv.first % _tables.size();

		size_t i = 1;
		size_t index = hashi;
		while (_tables[index]._state != EMPTY)
		{
			if (_tables[index]._kv.first == key)
			{
				return &_tables[index];
			}
			index = hashi + i;
			index %= _tables.size();
			++i;
		}
	}

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

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

void Test_hashtable1()
{
	HashTable<int, int> ht;
	int a[] = { 4,14,24,34,5,7,1 };
	for (auto e : a)
	{
		ht.Insert(make_pair(e, e));
	}
	ht.Insert(make_pair(13, 13));

}

问题

上述代码查找存在问题,当插入数据后,扩容前,删除一部分数据再插入数据,并且数据正好占据了其他空位,导致表中状态除了存在就是删除。

HashData<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0)
			return false;
		size_t hashi = kv.first % _tables.size();

		size_t i = 1;
		size_t index = hashi;
		while (_tables[index]._state != EMPTY)
		{
			if (_tables[index]._kv.first == key)
			{
				return &_tables[index];
			}
			index = hashi + i;
			index %= _tables.size();
			++i;
			//如果已经查找了一圈,那么说明全是存在+删除
			if (index == hashi)
			{
				break;
			}
		}
	}

缺陷

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。解决方案:二次探测、开散列。

开散列

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

实现

插入

namespace HashBucket
{
	template<class K,class V>
	struct HashNode
	{
		HashNode(const pair<K, V>& kv)
			:_kv(kv)
		{}
		struct HashNode<K, V>* _next=nullptr;
		pair<K, V> _kv;
	};
	template<class K,class V>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			size_t hashi = kv.first % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			
			return true;
		}
	private:
		vector<Node*> _tables;
		size_t _n=0;//存储的有效数据个数
	};
}

这里选择使用头插,时间复杂度o(1),省去了尾插找尾的步骤。hashtable成员变量_tables是一个指针数组,存放的是指向每一个节点链表头的指针。

负载因子

在这里插入图片描述
负载因子越大,冲突的概率越高,查找效率越低,空间利用率越高。
对于闭散列,我们采取当负载因子为1时进行扩容。

扩容

bool Insert(const pair<K, V>& kv)
		{
			//负载因子等于1时扩容
			if (_n == _tables.size())
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);

				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = cur->_kv.first % newtables.size();
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(newtables);
			}
			size_t hashi = kv.first % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			
			return true;
		}

这里扩容不是重新创一个一个节点,而是把原本_tables中的数据再重新遍历一遍依次插入到新开的vector中,最后实现交换。

加入仿函数/模版特化

如果类型是string或其他自定义类型那么无法计算key,那么可以利用仿函数转化成整形。
在这里插入图片描述
对于需要处理的string或自定义类型,传入自己写好的仿函数即可
在这里插入图片描述
仿函数中不一定要取第一个字符的ASCII码,可以取所有字符的和加起来的值去查找,这样可以大大提高效率
当然还有很多哈希算法更强,如BKDR哈希,详见字符串哈希算法
在这里插入图片描述

完整实现代码

#pragma once
#include<vector>
#include<iostream>
#include<string>

using namespace std;

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

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

	template<class K, class V>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);
		}
	public:
		bool Insert(const pair<K, V>& kv)
		{
			//负载因子超过0.7就扩容
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				//扩容要开全新空间,重新算位置
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				for (auto& data : _tables)
				{
					if (data._state == EXIST)//重新算在新表的位置
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);//vector底层直接交换指针就行
			}

			size_t hashi = kv.first % _tables.size();
			size_t i = 1;//控制步长
			size_t index = hashi;

			while (_tables[index]._state == EXIST)//如果存在hashi继续往后走
			{
				index = hashi + i;
				index %= _tables.size();
				i++;
			}

			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
				return false;
			size_t hashi = key % _tables.size();

			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._kv.first == key)
				{
					return &_tables[index];
				}
				index = hashi + i;
				index %= _tables.size();
				++i;
				//如果已经查找了一圈,那么说明全是存在+删除
				if (index == hashi)
				{
					break;
				}
			}
		}

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

		void Print()const
		{
			for (auto e : _tables)
			{
				if(e._state==EXIST)
					cout << e._kv.first << " ";
			}
			cout << endl;
		}

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

	void Test_hashtable1()
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}
		ht.Insert(make_pair(13, 13));

		ht.Print();

	}
}

namespace HashBucket
{
	template<class K,class V>
	struct HashNode
	{
		HashNode(const pair<K, V>& kv)
			:_kv(kv)
		{}
		struct HashNode<K, V>* _next=nullptr;
		pair<K, V> _kv;
	};


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

	//针对string模版特化
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			//BKDR哈希
			size_t hash = 0;
			for (auto ch : s)
			{
				hash += ch;
				hash *= 31;
			}
			return hash;
		}
	};

	template<class K,class V,class Hash=HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				cur = nullptr;
			}
		}
		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			//负载因子等于1时扩容
			Hash hash;
			if (_n == _tables.size())
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);

				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hash(cur->_kv.first) % newtables.size();
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(newtables);
			}
			

			size_t hashi = hash(kv.first) % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			
			return true;
		}


		Node* Find(const K& key)
		{
			Hash hash;
			size_t hashi =hash( key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Hash hash;
			//不能向开散列一样find去删
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			//单链表删除需要一个prev
			Node* prev = nullptr;

			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi]= cur->_next;
					}
					delete cur;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		size_t MaxBucketSize()
		{
			size_t max = 0;
			for (size_t i=0;i<_tables.size();i++)
			{
				size_t size = 0;
				auto cur = _tables[i];
				while (cur)
				{
					++size;
					cur = cur->_next;
				}
				printf("[%d]->%d\n",i, size);
				if (size > max)
				{
					max = size;
				}
			}
			return max;
		}

		void Print()
		{
			for ( auto cur : _tables)
			{
				while (cur)
				{
					std::cout << cur->_kv.first << " ";
					cur = cur->_next;
				}
			}
			cout << endl;
		}
	private:
		vector<Node*> _tables;
		size_t _n=0;//存储的有效数据个数
	};

	void Test_hashbucket1()
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}
		ht.Insert(make_pair(13, 13));
		ht.Insert(make_pair(23, 23));
		ht.Insert(make_pair(33, 33));
		ht.Insert(make_pair(43, 43));

		ht.Print();

		ht.Erase(13);
		ht.Erase(33);

		ht.Print();

	}

	/*struct HashStr
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash += ch;
				hash *= 31;
			}
			return hash;
		}
	};*/

	void Test_hashbucket2()
	{
		HashTable<string, string> hts;
		hts.Insert(make_pair("mao", "毛"));
		hts.Insert(make_pair("dou", "豆"));

		hts.Print();
	}

	void Test_hashbucket3()
	{
		size_t N = 100000;
		HashTable<int, int> ht;
		srand(time(0));
		for (size_t i = 0; i < N; i++)
		{
			size_t x = rand();
			ht.Insert(make_pair(x, x));
		}

		cout << ht.MaxBucketSize() << endl;
	}
}
  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Jenkins散列函数是一种哈希函数,它由Bob Jenkins在1997年设计的。它被广泛应用于计算机科学领域,特别是在哈希表、数据结构和密码学中。 Jenkins散列函数的设计目标是具有良好的散列性能和低碰撞率。它采用了一系列位操作、移位操作和异或操作来混合输入数据,并通过一系列迭代来增加混淆度。这种设计使得Jenkins散列函数在处理不同类型的数据时都能产生较好的散列结果。 在使用Jenkins散列函数时,你需要将要散列的数据作为输入,并调用相应的函数来计算散列值。Jenkins散列函数有多个变种,包括32位和64位版本,你可以根据自己的需求选择适合的版本。 以下是使用Jenkins散列函数的一些示例代码(使用C++语言): ```cpp #include <iostream> #include <cstdint> // Jenkins散列函数32位版本 uint32_t jenkins_hash_32(const void* key, size_t length) { const uint8_t* data = static_cast<const uint8_t*>(key); uint32_t hash = 0; for (size_t i = 0; i < length; ++i) { hash += data[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } // Jenkins散列函数64位版本 uint64_t jenkins_hash_64(const void* key, size_t length) { const uint8_t* data = static_cast<const uint8_t*>(key); uint64_t hash = 0; for (size_t i = 0; i < length; ++i) { hash += data[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } int main() { const char* str = "Hello, Jenkins!"; size_t length = strlen(str); uint32_t hash32 = jenkins_hash_32(str, length); uint64_t hash64 = jenkins_hash_64(str, length); std::cout << "32-bit hash: " << hash32 << std::endl; std::cout << "64-bit hash: " << hash64 << std::endl; return 0; } ``` 上述代码演示了如何使用Jenkins散列函数计算给定字符串的32位和64位散列值。你可以根据自己的需求将其集成到你的项目中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值