哈希表(hashTable)

我们之前了解了红黑树,今天我们来了解它的衍生——哈希表

unrordered_map和unordered_set

我们之前学习过map和set,其实我们的unrordered_map,unordered_set和map,set用法大同小异:

#include<iostream>
#include<unordered_map>
#include<unordered_set>
#include<map>
#include<set>
#include<string>
#include<stdlib.h>
using namespace std;

int main()
{
	srand(time(0));
	//map和set
	set<int> My_set;

	for (int i = 0; i < 10; i++)
	{
		My_set.insert(rand() % 100 + 1);
	}

	for (auto e : My_set)
	{
		cout << e << " " ;
	}

	cout << endl;

	map<string, int> My_map;
	My_map.insert(make_pair("cccc", 89));
	My_map.insert(make_pair("aaaa", 21));
	My_map.insert(make_pair("ffff", 2));
	My_map.insert(make_pair("bbbb", 12));

	for (auto e : My_map)
	{
		cout << e.first << "->" << e.second << endl;
	}
	cout << endl;
}

map和set的结果是有序的:
在这里插入图片描述
我们来看看unrordered_map和unordered_set的结果:

	srand(time(0));
	//map和set
	unordered_set<int> My_set;

	for (int i = 0; i < 10; i++)
	{
		My_set.insert(rand() % 100 + 1);
	}

	for (auto e : My_set)
	{
		cout << e << " ";
	}

	cout << endl;

	unordered_map<string, int> My_map;
	My_map.insert(make_pair("cccc", 89));
	My_map.insert(make_pair("aaaa", 21));
	My_map.insert(make_pair("ffff", 2));
	My_map.insert(make_pair("bbbb", 12));

	for (auto e : My_map)
	{
		cout << e.first << "->" << e.second << endl;
	}
	cout << endl;

在这里插入图片描述
一般情况下而言unrordered_map和unordered_set的性能要比map和set好的多,因为
unrordered_map和unordered_set不用排序。

哈希映射

哈希表基本的的一个概念就是哈希映射,什么意思呢?就是通过某种关系,使两个对象产生唯一的联系。这种关系可以是自然的,也可以是人为规定的。

比如说:学生有学号,有姓名,我们可以通过直接让学号和姓名绑定,实现学号对应姓名这种映射。
还有我们常见的数组,其实某种意义上也是一种哈希映射,数组里面的内容是挨着存放的(0,1,2,3,4…),这样可以实现数组下标查找对应内容的功能。我们也可以自己规定:单数的数组下标存放数字,双数的不做处理。这样也可以实现某两个事物之间产生唯一的联系。

通过哈希映射,可以通过这种关系,实现对数据的快速查找。

哈希碰撞

哈希碰撞的意思是:通过某种关系,产生了一对多的对应关系。我们来举个例子:
我现在有一组数:

int a[] = {1,22,44,55,773,12,34,56}

我现在规定数组里面的数模10之后,结果为该数字的储存的对应的下标。

在这里插入图片描述为了解决哈希冲突,我们的想办法处理有冲突的元素:
解决哈希冲突两种常见的方法是:闭散列和开散列

闭散列

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

那么如何找下一个位置呢?

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
在这里插入图片描述

我们可以根据这个,模拟一个简单的哈希表

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

namespace My_HashTable
{
	enum Staus //状态
	{
		EMPTY,
		EXIST,
		DELETE	
	};

	template<class K,class V>
	struct HashData
	{
		pair<K, V> _kv; //键值对
		Staus _s; //状态
	};

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

	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			int hash = 0;
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}
			return hash;
		}
	};

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

		HashTable()
		{
			_tables.resize(10);
		}
		//寻找
		HashData<K, V>* Find(const K& key)
		{
			Hash hf;
			//哈希映射
			int _number = hf(key) % _tables.size();

			while (_tables[_number]._s != EMPTY)
			{
				if (_tables[_number]._kv.first == key 
					&&
					_tables[_number]._s == EXIST)
				{
					return &_tables[_number];
				}

				_number++;

				_number %= _tables.size();


			}
			return nullptr;
		}

		bool insert(const pair<K,V>& kv)
		{
			Hash hf;
			//满了(负载因子)
			if ((double)_n / _tables.size() == 0.7)
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K, V,HashFunc<K>> newTB; //新表
				newTB._tables.resize(newSize);
				//遍历旧表
				for (int i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newTB.insert(_tables[i]._kv);
					}
				}
				_tables.swap(newTB._tables);
			}
			
			//未满
			size_t _number = hf(kv.first) % _tables.size(); //哈希函数

			while (_tables[_number]._s == EXIST)
			{
				_number++;

				_number %= _tables.size(); //回到开始位置
			}

			//插入
			_tables[_number]._kv = kv;
			_tables[_number]._s = EXIST;
			++_n; 

			return true;
		}

		//删除
		bool Erease(const K& key)
		{
			HashData<K,V>* ret = Find(key);
			if (ret->_s == EXIST)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

		//打印
		void PrintHashTable()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					cout << "[" << _tables[i]._kv.first << "]" << "->" << "EXSIT"<< "->" << _tables[i]._kv.second << endl;
				}
				else if (_tables[i]._s == EMPTY)
				{
					cout << "[" << _tables[i]._kv.first << "]" << "->" << "EMPTY" << "->" << _tables[i]._kv.second << endl;
				}
				else if (_tables[i]._s == DELETE)
				{
					cout << "[" << _tables[i]._kv.first << "]" << "->" << "DELETE"<< "->" << _tables[i]._kv.second << endl;
				}
			}
		}

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


	void Test1()
	{
		int a[] = { 11,2,44,24,54,7,9,12,32,94,95 };
		HashTable<int,int> hash;

		for (auto e : a)
		{
			hash.insert(make_pair(e, e));
		}

		hash.Erease(24);

		hash.insert(make_pair(4, 4));
		hash.PrintHashTable();

	}

	void Test2()
	{
		string arr[] = { "香蕉","苹果","樱桃","苹果","梨子","西瓜" };
		HashTable<string, int> hash;
		for (auto e : arr)
		{
			HashData<string, int>* ret = hash.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				hash.insert(make_pair(e, 1));
			}
		}

		hash.PrintHashTable();
	}
}


开散列(哈希桶)

开散列的话(也叫做拉链法,哈希桶法),就是用一个指针数组存放链表,符合条件的挂在一条链表上

在这里插入图片描述
哈希桶最大的好处就是,当发生哈希冲突的时候,不会占用其他位置,就会自己原地解决。

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

namespace Hash_buckets
{
	//解决字符串转换
	template<class K> //如果是数值直接使用
	struct HashFuc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template<>
	struct HashFuc<std::string> //字符串则进行转化
	{
		size_t operator()(const std::string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}
			return hash;
		}
	};

	//哈希桶结点
	template<class K,class V>
	struct Hash_Node
	{
		//结点
		Hash_Node* _next; //下一个结点
		std::pair<K, V> _kv;

		//构造函数
		Hash_Node(const std::pair<K,V> kv)
			:_next(nullptr)
			,_kv(kv)
		{

		}
	};

	template<class K, class V, class Hash = HashFuc<K>>
	class HashTable
	{
	public:
		typedef Hash_Node<K, V> _Node;
		
		//构造函数
		HashTable()
		{
			_tables.resize(10);
		}

		//析构函数
		~HashTable()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				_Node* cur = _tables[i];

				while (cur)
				{
					_Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		bool Insert(const std::pair<K,V> kv)
		{
			Hash hf;

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

			//判断是否需要扩容
			if (_n == _tables.size())
			{
				//新开的大小
				size_t newSize = _tables.size() * 2;
				HashTable<K, V> newHT;
				newHT._tables.resize(newSize);

				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					_Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);
			}

			//插入
			//哈希函数
			size_t hashi = hf(kv.first) % _tables.size();
			_Node* newnode = new _Node(kv);

			//头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n; // 个数加一

			return true;
			
		}

		void ShowHash()
		{
			std::cout << "共有" << _tables.size() << "条链" << std::endl;
			for (int i = 0; i < _tables.size(); i++)
			{
				_Node* cur = _tables[i];
				while (cur)
				{
					std::cout << "第" << i << "链上的元素有" << cur->_kv.first <<"共"<<cur->_kv.second<<"个" << std::endl;
					cur = cur->_next;
				}
				std::cout << "######################" << std::endl;
			}
		}

		_Node* Find(const K& key)
		{
			Hash hf;
			//哈希函数
			size_t hashi = hf(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 hf;

			size_t hashi = hf(key) % _tables.size();
			_Node* prve = nullptr;
			_Node* cur = _tables[hashi];

			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prve == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prve->_next = cur->_next
					}

					delete cur;
				}
				prve = cur;
				cur = cur->_next;
			}

			return false;
		}
	private:
		//哈希表
		std::vector <_Node*> _tables;
		size_t _n = 0; //个数
	};

	void TestHT1()
	{
		HashTable<int, int> hash;
		int a[] = { 1,20,20,20,30,40,2,3,4,55,23,89,51,23,44 };

		for (auto e : a)
		{
			hash.Insert(std::make_pair(e, e));
		}

		hash.ShowHash();

	}

	void TestHT2()
	{
		std::string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		HashTable<std::string, int> ht;
		for (auto& e : arr)
		{
			//auto ret = ht.Find(e);
			Hash_Node<std::string, int>* ret = ht.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(std::make_pair(e, 1));
			}
		}
		ht.ShowHash();
	}
}

字符串哈希函数

如果大家看的仔细的话,我们的测试用例里有字符串:

std::string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

之前我们可以直接插入数值,直接利用该值为我们插入删除进行服务。但是,计算机不认识字符串,不可能直接用字符串进行操作,唯一的方法就是我们可不可以把字符串转化成数字后,再用处理后的数字进行操作

一开始我们想到的是把字符串中的ASCII码相加:

string str = "hello My World";

int hash = 0; //累加
for(auto e : str)
{
  hash += e;
}

最后得到的hash就可以作为键值对为我们后面的操作进行服务。但是这样的算法没有考虑到顺序问题。比如abcdefbcdeaf,两个字符串累加后得到的数值是一样的,还是会引发哈希冲突,该怎么办呢?

所以就有一大堆的学者去研究这个问题,得到了许多比较高效的字符串hashcode算法,具体的大家可以在网站上进行搜索,这里不再赘述
(如果大家搜过的话,目前常见的字符串hash算法有BKDRHash,APHash,DJBHash,JSHash,RSHash,SDBMHash,PJWHash,ELFHash等等

而我们这里的是用的是BKDR算法:

    //解决字符串转换
	template<class K> //如果是数值直接使用
	struct HashFuc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template<>
	struct HashFuc<std::string> //字符串则进行转化
	{
		size_t operator()(const std::string& key)
		{
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 31;
				hash += e;
			}
			return hash;
		}
	};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值