C++ 哈希

目录

哈希:

哈希表(散列表):

哈希函数:

哈希冲突:

 闭散列:

闭散列的结构: 

插入数据

查找元素

删除元素

开散列

哈希表结点

迭代器

迭代器结构:

operator++()

operator*()   operator->()   operator!=()

开散列的哈希表

哈希表的结构

仿函数

析构函数

begin()   end()

查找元素( Find() )

增加元素( Insert() )

删除元素( Erase() )

封装unordered_map和unordered_set​​​​​​​

unordered_set

unordered_map


哈希

通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,我们便可以通过映射关系很快的查找到想要的元素。

哈希表(散列表):

希表(散列表)是一种数据结构通过哈希(散列)方法,构造出来的结构称为哈希表(Hash Table)(或者称散列表),哈希表查找元素的时间复杂度是O(1)。

哈希函数:

通过哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数。

常见的哈希函数:除留余数法:设哈希表中允许的地址数为m,插入的数据为key,则:

key(关键码对应的哈希地址) = key(关键码) %  m

例如:

 将要插入的数据先%数组的大小,找到对应的映射位置插入

哈希冲突:

当我们在插入13时,会发现13对应的位置已经有数据了,则就构成哈希冲突

解决哈希冲突两种常见的方法是:闭散列和开散列

 
闭散列:

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么通过线性探测,或二次探测的方法,找到key存放到冲突位置中的“下一个” 空位置去。

闭散列的结构: 


enum State //判断数组位置的状态
{
  EMPTY, //空
  EXITS,  //存在
  DELETE,  //删除
};

template<class T>
struct HashData
{
	T _data;
	State _state; //状态
};

template<class K, class T, class KeyOfT> //仿函数,取出数据的关键字
class HashTable
{
	typedef HashData<T> HashData;
public:
	bool Insert(const T& d); //增

	HashTable* Find(const K& key); //查

	bool Erase(const K& key); //改

private:
	vector<HashData> _tables;  //数组
	size_t _num = 0; //存了几个有效数据
};

哈希表中存放着一个数组(存放着结点)和有效数据的个数。

哈希表结点中存放的除了数据,还有一个被标记的状态,方便我们实现增删查改的操作。

插入数据

负载因子:负载因=表的数据/表的大小,衡量哈希表满的程度,表越接近满,插入数据越容易冲突,冲突越多,效率越低。

哈希表并不是满了才增容,开放定制法中,一般负载因子到0.7左右就开始增容

负载因子越小,冲突概率越低,整体效率越高,但是负载因子越小,浪费的空间越大

bool Insert(const T& data)
{
  KeyOfT koft;
  
  //表为空时增容,或者负载营造到0.7时增容,
  if(_tables.size()==0 || _num*10/_tables.size()>=7)
  {
        //定义一个哈希表的对象,将空间开成旧表的2倍
        HashTable<K, T, KeyOfT> newht;
		size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
		newht._tables.resize(newsize);
        
        //遍历旧表的数据,在新表中调用Insert接口,将旧表的元素依次插入的新表
        for (size_t i = 0; i < _tables.size(); ++i)
		{
			if (_tables[i]._state == EXITS)
			{
				newht.Insert(_tables[i]._data);
			}

		}
        //将新表和旧表交换
		_tables.swap(newht._tables);     
  }

  //计算出data中的key在表中映射的位置
  size_t index = koft(data) % _tables.size();
  //找到空的位置
  while (_tables[index]._state == EXITS)
  { 
    //如果插入的元素表中已经有了,则返回false
    if(kofk(_tables[index]._date)==koft(data))
    {
       return false;
    }
    
    ++index;

    if(index==_tables.size())
    {
      index=0;
    }

  }

  //找到插入的位置
  _tables[index]._data = data;
  _tables[index]._state = EXITS;//将状态标记存在
  _num++;

}

查找元素

		HashTable* Find(const K& key)
		{
			KeyOfT koft;
            //元素在表中的映射位置
			size_t index = key % _tables.size();
            //不为空就表示可能还在后面
			while (_tables[index]._state != EMPTY)
			{
				if (koft(_tables[index]._data) == key)
				{
					if (_tables[index]._state == EXITS)
					{
						return &_tables[index];
					}
                    //如果状态是删除,就返回nullptr
					else if (_tables[index]._state == DELETE)
					{
						return nullptr;
					}
				}

				++index;
				if (index == _tables.size)
				{
					index = 0;
				}
			}
		}

删除元素

bool Erase(const K& key)
{
    //调用Find()查找元素
	HashData* ret = Find(key);
    //不为空表示元素存在
	if (ret)
	{
        //将删除的元素标记成删除
		ret->_state = DELETE;
		--_num;
		return true;
	}
	else
	{
		return false;
	}
}

闭散列的开发定制法不是一种好的解决方式,因为他时一种我的位置别站了,我就去抢别人的位置的思路。

开散列

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

哈希表结点

 	//结点
    template <class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

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


哈希表的结点除了存放数据外,还存放这下一个结点的指针。

迭代器

迭代器结构:

    //两个仿函数,KeyOfT是取到关键子,HashFunc是将除整形外的类型转换成可以取%的整形    
    template<class K, class T, class KeyOfT, class HashFunc>
	class __HTIterator
	{
		typedef HashNode<T> Node;
		typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
		typedef HashTable<K, T, KeyOfT, HashFunc> HT;
	public:
		Node* _node;
		HT* _pht;

		__HTIterator(Node* node, HT* pht)  //构造函数
			: _node(node)
			, _pht(pht)
		{}

        //将运算符重载
		Self& operator++()

		T& operator*()

		T* operator->()

		bool operator!=(const Self& s) const

	};

因为哈希表不是一个连续的结构, 我们没法天然的获取到下一个结点所在的位置,我们需要将哈希表的迭代器封装成一个类,对运算符进行重载。

迭代器中存放着哈希表的结点和哈希表的对象。

operator++()

如何找到迭代器下一个所处的位置呢? 

如果当前迭代器指向节点的_next不为空:

 如果迭代器所指向的结点的_next不为空,应为我们便可以通过他的_next找到下一个结点。

如果当前迭代器指向节点的_next为空:

如果迭代器所指向的结点的_next为空,我们需要从新计算一下这个桶所在的位置,将桶指向下一个不为Null的桶。

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				HashFunc hf;
				size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();
				++hashi;
				// 找下一个不为空的桶
				for (hashi; hashi < _pht->_tables.size(); ++hashi)
				{
					if (_pht->_tables[hashi])
					{
						_node = _pht->_tables[hashi];
						break;
					}
				}

				// 没有找到不为空的桶,用nullptr去做end标识
				if (hashi == _pht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

operator*()   operator->()   operator!=()

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

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

开散列的哈希表

哈希表的结构

	//两个仿函数,KeyOfT是取到关键子,HashFunc是将除整形外的类型转换成可以取%的整形
    template <class K, class T, class KeyOfT, class HashFunc= DefaultHash<K>>
	class HashTable
	{
       //设置成友缘函数
	   template <class K, class T, class KeyOfT, class HashFunc>
       friend class __HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;

		iterator begin()   

		iterator end()

		~HashTable()  //析构函数

		pair<iterator, bool> Insert(const T& data)  //增加元素

		iterator Find(const K& key)  //查找元素

		bool Erase(const K& key)  //删除元素
		
	private:
		vector<Node*> _tables; // 指针数组
		size_t _num = 0;
	};

仿函数


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

template<>
struct DefaultHash<string>
{
	size_t operator()(const string& key)
	{
		// BKDR
		size_t hash = 0;
		for (auto ch : key)
		{
			hash = hash * 131 + ch;
		}

		return hash;
	}

因为我们存的不一定是int 类型,哈希函数采用处理余数法,被%的key必须要为整形才可以处理,此处提供将key转化为整形的方法。

我们将string转换成size_t的类型。

析构函数

将每一个桶中的元素删除,将指针置空

		~HashTable()
		{
			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;
			}
		}

begin()   end()


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


	    iterator begin()
		{
            //找到第一个不为空的桶
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return iterator(cur, this);
				}
			}
			return end();
		}

查找元素( Find() )

		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return iterator(nullptr, this);
			}

			KeyOfT kot;
			HashFunc hf;

            //查找Key映射的桶的位置
			size_t hashi = hf(key);
			hashi %= _tables.size();

			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
                    //返回cur位置的迭代器
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

增加元素( Insert() )

如果插入的元素在表中已经存在,则返回表中的相同的元素的位置。

如果插入的元素在表中不存在,则返回新插入表中的元素的位置。

		pair<iterator, bool> Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;

            //通过find()来判断表中有没有和data相同的元素
			iterator pos = Find(kot(data));
			if (pos != nullptr)
			{
                //如果表中已经有了和data相同的元素,则返回表中和data相同的元素的位置
				return make_pair(pos, false);
			}

			//如果插入有效元素个数和数组的大小相同,就增容
			if (_tables.size() == _num)
			{
				size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				//size_t newSize = GetNextPrime(_tables.size());
				if (newSize != _tables.size())
				{
					vector<Node*> newTable;
					newTable.resize(newSize, nullptr);
					for (size_t i = 0; i < _tables.size(); ++i)
					{
						Node* cur = _tables[i];
						while (cur)
						{
							Node* next = cur->_next;
                            //在新表中对应的位置
							size_t hashi = hf(kot(cur->_data)) % newSize;
                            //在新表中头插
							cur->_next = newTable[hashi];
							newTable[hashi] = cur;

							cur = next;
						}

						_tables[i] = nullptr;
					}
                    //将旧表和新表交换
					newTable.swap(_tables);
				}
			}
             
            
			size_t hashi = hf(kot(data));
			hashi %= _tables.size();

			// 头插到对应的桶
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_num;

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

删除元素( Erase() )

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			HashFunc hf;
			KeyOfT kot;
             
            //找到对应的位置
			size_t hashi = hf(key);
			hashi %= _tables.size();

			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
                    //如果要删除的元素是桶中的第一个元素
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
                     //如果要删除的元素不是桶中的第一个元素
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;

					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

封装unordered_map和unordered_set

unordered_map和unordered_set的底层所使用的都是哈希表

unordered_set

namespace MySet
{
	template <class K, class HashFunc = DefaultHash<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
    typedef typename OPEN_HASH::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;

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

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

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

		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		OPEN_HASH::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
	};
}

unordered_map

namespace MyMap
{
	template<class K, class V, class HashFunc = DefaultHash<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename OPEN_HASH::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::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);
		}

		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		OPEN_HASH::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
	};
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值