[C++]哈希


一、哈希的基本介绍


性能上

查找数据,哈希时间复杂度为O(1)
大量随机数据插入,map和set性能快
大量随机数据删除,map和set性能快

哈希映射方法

1.直接定址法:每个值都有自己唯一的位置
优点:无哈希冲突,简单且均匀
缺点:如果空间分散会有很大的空间浪费
适应于范围小且连续的情况

2,除留余数法
优点:空间合适且不浪费过多的空间
缺点:可能存在有相同的映射地址(哈希冲突)

哈希大部分都围绕着解决哈希冲突

1.闭散列:冲突后占用下一个空位置
缺点:查找效率变低,互相冲突导致插入逻辑复杂
线性探测:线性找下一个空位置,++
二次探测:平方步调找下一个位置,^2

2.开散列(哈希桶)(拉链法):冲突的位置下加上链表(哈希桶),由此STL中并没有双向迭代器
缺点:链表太长有效率问题
优点:冲突不会相互影响


二、闭散列的哈希表实现


实际中开散列应用情况优于闭散列,点一下

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

template<class K>
struct KeyToCmp //得到可以进行比较的数据
{
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

template<>
struct KeyToCmp<string> //特化一个string的仿函数,使得字符也能有其映射的地址
{
	size_t operator()(const string& k)
	{
		size_t hash = 0;
		for (auto& e : k)
		{
			hash += e;
		}
		return hash;
	}
};


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 Hash = KeyToCmp<int>>
class HashTable
{
public:
	typedef HashData<K,V> Data;

	HashTable() //构造
	{
		_tables.resize(10);
	}


	//负载因子 = 表中数据/表的大小  衡量哈希表满的程度
	//负载因子越大,插入数据越容易发生哈希冲突,冲突越多,效率越低
	//所以哈希表并不是满了才增容,一般负载因子在0.7左右就开始增容
	//控制的太小,会使得空间浪费
	bool Insert(const pair<K,V>& kv)
	{
		if (Find(kv.first) != nullptr) //已经有了
			return false;
		
		if (_num * 10 / _tables.size() >= 7) //负载因子大了,开始增容
		{
			HashTable<K, V, Hash> tmp;
			tmp._tables.resize(2 * _tables.size()); //新创建一个哈希表,将其数组空间扩大至旧哈希表的两倍
			
			for (auto& e : _tables)
			{
				if (e._state == EXIST) //如果有数据
					tmp.Insert(e._kv); //插入,扩容已经在上面完成了,递归时判断增容条件进不去,不会造成死循环
			}
			_tables.swap(tmp._tables);
		}

		Hash get;
		size_t index = get(kv.first) % _tables.size();

		while (_tables[index]._state == EXIST)
		{
			index++;
			index %= _tables.size();
		}

		_tables[index]._kv = kv;
		_tables[index]._state = EXIST;
		_num++;
		
		return true;
	}


	Data* Find(const K& k)
	{
		
		Hash get;
		size_t index = get(k) % _tables.size();

		while (_tables[index]._state != EMPTY)
		{
			if (_tables[index]._state == EXIST && _tables[index]._kv.first == k)
				return &_tables[index];
			index++;
			index %= _tables.size();
		}
		return nullptr; //哈希冲突是连续的,如果第一个后面为空,说明后面也没有冲突的情况,直接返回
	}


	bool Erase(const pair<K, V>& kv)
	{
		Data* ret = Find(kv.first);
		if (ret)
		{
			ret->_state = DELETE;
			--_num;
			return true;
		}
		else
		{
			return false;
		}
	}
	
private:
	vector<Data> _tables;
	size_t _num = 0; //存储当前存储有效数据的个数
};


三、开散列模拟实现unordered_xxx


unordered_xxx底层都是由开散列实现的

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

namespace BsyOpen
{
	template<class K>
	struct GetWay
	{
		const K& operator()(const K& k)
		{
			return k;
		}
	};

	template<> //特化处理字符串,使得其返回值能够被模
	struct GetWay<string>
	{
		size_t operator()(const string& k)
		{
			size_t way = 0;
			for (size_t i = 0; i < k.size(); ++i)
			{
				way *= 131; //BKDR Hash,使得哈希冲突降低
				way += k[i];
			}
			return way;
		}
	};


	template<class T> //一个哈希表节点
	struct HashNode 
	{
		T _data;
		HashNode<T>* _next;
		//我们自己实现的哈希输出,并非输入的顺序,如果要实现按输入顺序输出就要多指针
		//HashNode<T>* _linknext; //以按顺序输出
		//HashNode<T>* _linkprev; //以删除

		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}
	};


	// 前置声明一下, 否则在迭代器类中, 无法识别出HashTable是什么
	template<class K, class T, class TheKey, class GetWayy> //哈希表
	class HashTable; 

	template<class K,class T,class TheKey,class GetWayy> //哈希表迭代器
	struct __HashIterator
	{
		typedef __HashIterator< K, T, TheKey, GetWayy> Self;
		typedef HashTable<K, T, TheKey, GetWayy> HT;
		typedef HashNode<T> Node;
		Node* _node;
		HT* _pht; //以便在后续operator++中起到作用

		__HashIterator(Node* node,HT* pht)
			:_node(node)
			,_pht(pht)
		{}

		T& operator*()
		{
			return _node->_data;
		}

		T* operator->()
		{
			return &_node->_data;
		}

		Self operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next; //如果桶没走完接着往下走
			}
			else
			{
				TheKey thekey;
				GetWayy getway;
				//计算数值应该位于哪个头节点
				size_t i = getway(thekey(_node->_data)) % _pht->_tables.size();
			  //size_t i = GetWayy()(TheKey()(_node->_data)) % _pht->_tables.size();
				i++; //现在i就是下一个需要验证是否为空的数组下标
				for (; i < _pht->_tables.size(); ++i)
				{
					Node* cur = _pht->_tables[i];
					if (cur)
					{
						_node = cur;
						return *this;
					}
				}
				//此时说明没有下一个节点了
				_node = nullptr;
			}
			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};


	template<class K,class T,class TheKey,class GetWayy> //哈希表
	class HashTable
	{
	public:
		friend struct __HashIterator< K, T, TheKey, GetWayy>;
		typedef HashNode<T> Node;
		typedef __HashIterator< K, T, TheKey,GetWayy> iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return iterator(_tables[i],this);
				}
			}
			return end();
		}

		iterator end()
		{
			return iterator(nullptr,this);
		}

		~HashTable() //销毁完桶之后,,析构自动销毁this,即vector会随着析构函数销毁
		{
			Clear();
		}

		void Clear()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr; //使得无法访问每个头节点
			}
		}

		size_t GetNextPrime(size_t num) //这是一种算法,使得可以减小哈希冲突
		{
			const int primecount = 28;
			const size_t primelist[primecount] = //二倍关系质数表
			{
				53,97,193,389,769,
				1543,3079,6151,12289,24593,
				49157,98317,1996613,393241,786433,
				1572869,3145739,6291469,12582917,25165843,
				50331653,100663319,201326611,402653189,805306457,
				1610612741,3221225473,4294967291
			};

			for (size_t i = 0; i < primecount; ++i)
			{
				if (primelist[i] > num)
				{
					return primelist[i];
				}
			}
			return primelist[primecount - 1]; //如果还大,就一直返回最后一个
		}

		pair<iterator,bool> Insert(const T& data)
		{
			TheKey thekey;
			GetWayy getway;

			if (_num == _tables.size()) //此时负载因子为1,避免以后造成大量冲突,增容
			{
				vector<Node*> newtable;
				size_t newsize = GetNextPrime(_num);
				newtable.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i]; //取每个头
					while (cur)
					{
						//旧表取下重新计算新表中的位置
						Node* next = cur->_next;
						size_t index = getway(thekey(cur->_data)) % newtable.size(); //新表中的位置
						cur->_next = newtable[index]; //头插进去
						newtable[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr; 
				}
				_tables.swap(newtable);
			}


			size_t index = getway(thekey(data)) % _tables.size();

			Node* cur = _tables[index];
			while (cur)
			{
				if (thekey(cur->_data) == thekey(data)) //重复了,不插入,结束
					return make_pair(iterator(cur,this),false);
				else
					cur = cur->_next;
			}

			//检验完后走到这里说明可以插入了
			//选择头插,因为有头节点的位置,不用找尾
			Node* newnode = new Node(data);
			newnode->_next = _tables[index];
			_tables[index] = newnode;

			++_num;
			return make_pair(iterator(newnode, this), true);
		}

		Node* Find(const K& k)
		{
			TheKey thekey;
			GetWayy getway;
			size_t index = getway(k) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (thekey(cur->_data) == k)
					return cur;
				else
					cur = cur->_next;
			}
			return nullptr;
		}


		bool Erase(const K& k)
		{
			TheKey thekey;
			GetWayy getway;
			size_t index = getway(k) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index]; //找到需要找到数据的头
			while (cur)
			{
				if (thekey(cur->_data) == k)//找到了
				{

					if (prev == nullptr) //说明要删除的对象就是第一个节点
						_tables[index] = cur->_next;
					else
						prev->_next = cur->_next;
					
					delete cur;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _num = 0;
	};
}

1.unordered_set

#pragma once
#include"Open_Hash.hpp"

namespace Bsy
{
	template<class K, class GetWayy = BsyOpen::GetWay<K>>
	class unordered_set
	{
		struct TheKeyOfSet
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};


	public:
		typedef typename BsyOpen::HashTable<K, K, TheKeyOfSet, GetWayy>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		pair<iterator,bool> insert(const K& k)
		{
			return _ht.Insert(k);
		}

	private:
		BsyOpen::HashTable<K, K, TheKeyOfSet,GetWayy> _ht;
	};

	void test_unordered_set1()
	{
		unordered_set<int> s;
		s.insert(1);
		s.insert(5);
		s.insert(6);
		s.insert(2);

		unordered_set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

}

2.unordered_map

#pragma once
#include"Open_Hash.hpp"

namespace Bsy
{
	template<class K,class V, class GetWayy = BsyOpen::GetWay<K>>
	class unordered_map
	{
		struct TheKeyOfMap
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename BsyOpen::HashTable<K, pair<K,V>, TheKeyOfMap, GetWayy>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		pair<iterator,bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

		V& operator[](const K& k)
		{
			//pair<iterator, bool> ret = _ht.Insert(make_pair(k, V()));
			//return ret.first->second;
			return _ht.Insert(make_pair(k, V())).first->second;
		}

	private:
		BsyOpen::HashTable<K, pair<K, V>, TheKeyOfMap,GetWayy> _ht;
	};


	void test_unordered_map1()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "paixu"));
		dict.insert(make_pair("left", "zuo"));
		dict.insert(make_pair("long", "chang"));
		dict["left"] = "you";
		dict["hash"] = "haxi";

		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first <<":"<<it->second<< endl;
			++it;
		}
		cout << endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值