当前位置: 首页 > news >正文

哈希的原理、实现

目录

引言

一、哈希概念

二、哈希函数

三、哈希冲突解决方法

四、unordered系列关联式容器(以unordered_map为例)

五、哈希的应用 

完整代码

六、总结 


引言

在计算机科学领域,哈希是一种非常重要的数据结构和算法思想,广泛应用于各种场景,如关联式容器、海量数据处理等。本文将深入探讨哈希的相关知识,包括哈希函数、哈希冲突及其解决方法,还会结合C++代码实现进行详细说明。

一、哈希概念

哈希的基本原理

在顺序结构和平衡树中,元素关键码与其存储位置之间没有直接对应关系,查找元素时往往需要多次比较关键码。而哈希结构通过哈希函数(hashFunc),让元素的存储位置与关键码建立映射关系,这样在查找元素时,能直接通过哈希函数快速定位,大大提高了查找效率。

例如,对于数据集合{1, 7, 6, 4, 5, 9} ,设定哈希函数为  hash(key) = key % capacity  (这里  capacity  为10 )。那么:

-  hash(1) = 1%10 = 1 

-  hash(7) = 7%10 = 7 

-  hash(6) = 6%10 = 6 

-  hash(4) = 4%10 = 4 

-  hash(5) = 5%10 = 5 

-  hash(9) = 9%10 = 9 

哈希冲突

当不同的关键码通过相同的哈希函数计算出相同的哈希地址时,就会发生哈希冲突。比如,若要插入元素44 ,按照上述哈希函数  hash(44) = 44%10 = 4  ,而位置4 已经被元素4 占据,这就产生了冲突。

二、哈希函数

哈希函数设计原则

1. 定义域覆盖:哈希函数的定义域必须涵盖需要存储的全部关键码。若散列表允许有  m  个地址,其值域应在0到  m - 1  之间。

2. 地址均匀分布:计算出的地址能均匀分布在整个空间中,这样可以减少冲突的发生。

3. 简单性:哈希函数应尽量简单,以提高计算效率。

常见哈希函数

1. 直接定址法

- 原理:取关键字的某个线性函数为散列地址,公式为  Hash(Key) = A*Key + B  。

- 优点:简单、均匀。

- 缺点:需要事先知道关键字的分布情况。

- 使用场景:适合查找比较小且连续的情况。

- C++示例代码:

// 简单示例,假设A = 1, B = 0size_t DirectAddressHash(int key) {return key; }

2. 除留余数法

- 原理:设散列表允许的地址数为  m  ,取一个不大于  m  ,但最接近或者等于  m  的质数  p  作为除数,按照哈希函数  Hash(key) = key % p (p<=m)  将关键码转换成哈希地址。

- 优点:常用且简单有效。

- 缺点:选择合适的质数  p  很关键。

- C++示例代码:

size_t RemainderHash(int key, int m) {// 假设已经获取到合适的质数pint p = GetAppropriatePrime(m); return key % p;}

3. 平方取中法

- 原理:假设关键字为1234 ,对它平方就是1522756 ,抽取中间的3位227作为哈希地址;再比如关键字为4321 ,对它平方就是18671041 ,抽取中间的3位671(或710)作为哈希地址 。

- 优点:适合不知道关键字的分布,而位数又不是很大的情况 。

- 缺点:计算相对复杂。

- C++示例代码:

size_t MidSquareHash(int key) {long long square = (long long)key * key;// 假设取中间3位,这里根据实际情况调整int mid = (square / 100) % 1000; return mid;}

4. 折叠法

- 原理:将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址 。

- 优点:适合事先不需要知道关键字的分布,适合关键字位数比较多的情况 。

- 缺点:可能会导致分布不均匀。

- C++示例代码:

size_t FoldHash(const string& key, int tableSize) {size_t sum = 0;int partSize = 4; // 假设每部分4位,可调整for (size_t i = 0; i < key.size(); i += partSize) {size_t part = 0;for (int j = 0; j < partSize && i + j < key.size(); ++j) {part = part * 10 + (key[i + j] - '0');}sum += part;}return sum % tableSize;}

5. 随机数法

- 原理:选择一个随机函数,取关键字的随机函数值为它的哈希地址,即  H(key) = random(key)  ,其中  random  为随机函数 。

- 优点:适用于关键字长度不等时 。

- 缺点:随机性可能导致分布不佳。

- C++示例代码:

size_t RandomHash(int key) {srand(static_cast<unsigned int>(time(nullptr)));return rand() % 100; // 假设哈希表大小为100,可调整}

6. 数学分析法

- 原理:设有  n  个  d  位数,每一位可能有  r  种不同的符号,这  r  种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址 。

- 优点:针对特定数据分布有较好效果。

- 缺点:需要分析数据特征。

- C++示例代码:略(因具体实现依赖数据特征分析)

三、哈希冲突解决方法

闭散列(开放定址法)

当发生哈希冲突时,如果哈希表未被装满,就把  key  存放到冲突位置中的“下一个”空位置中。寻找下一个空位置的方式有多种,这里介绍线性探测和二次探测。

线性探测

- 原理:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

- 插入示例:比如要插入元素44 ,哈希地址为4 ,但位置4 已被占用,就依次探测位置5、6、7等,直到找到空位置插入。

- 删除注意事项:不能直接物理删除哈希表中已有的元素,否则会影响其他元素的搜索,一般采用标记的伪删除法。

- C++代码实现:


 

template<class K, class V>class HashTable {struct Elem {pair<K, V> _val;State _state = EMPTY;};public:bool Insert(const pair<K, V>& val) {_CheckCapacity();size_t hashAddr = HashFunc(val.first);size_t startAddr = hashAddr;while (_ht[hashAddr]._state != EMPTY) {if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == val.first)return false;hashAddr++;if (hashAddr == _ht.capacity())hashAddr = 0;if (hashAddr == startAddr)return false;}_ht[hashAddr]._state = EXIST;_ht[hashAddr]._val = val;_size++;return true;}int Find(const K& key) {size_t hashAddr = HashFunc(key);while (_ht[hashAddr]._state != EMPTY) {if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)return hashAddr;hashAddr++;if (hashAddr == _ht.capacity())hashAddr = 0;}return -1;}bool Erase(const K& key) {int index = Find(key);if (-1 != index) {_ht[index]._state = DELETE;_size--;return true;}return false;}private:void _CheckCapacity() {if (_size * 10 / _ht.capacity() >= 7) {HashTable<K, V, HF> newHt(GetNextPrime(_ht.capacity()));for (size_t i = 0; i < _ht.capacity(); ++i) {if (_ht[i]._state == EXIST)newHt.Insert(_ht[i]._val);}_ht.swap(newHt._ht);}}size_t HashFunc(const K& key) {return key % _ht.capacity();}private:vector<Elem> _ht;size_t _size = 0;};

二次探测

- 原理:为了避免线性探测产生的数据堆积问题,二次探测找下一个空位置的方法为: H_i = (H_0 + i^2) % m  或者  H_i = (H_0 - i^2) % m  ,其中  H_0  是通过散列函数  Hash(x)  对元素的关键码  key  进行计算得到的位置, m  是表的大小 。

- 优点:相比线性探测,能一定程度缓解数据堆积。

- 缺点:实现相对复杂。

- C++代码实现(插入部分示例):

bool Insert(const pair<K, V>& val) {_CheckCapacity();size_t hashAddr = HashFunc(val.first);size_t i = 1;size_t startAddr = hashAddr;while (_ht[hashAddr]._state != EMPTY) {if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == val.first)return false;hashAddr = startAddr + i * i;hashAddr %= _ht.capacity();if (hashAddr == startAddr)return false;i++;}_ht[hashAddr]._state = EXIST;_ht[hashAddr]._val = val;_size++;return true;}

开散列(链地址法)

原理

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

C++代码实现

template<class V>struct HashBucketNode {HashBucketNode(const V& data) : _pNext(nullptr), _data(data) {}HashBucketNode<V>* _pNext;V _data;};template<class V>class HashBucket {public:HashBucket(size_t capacity = 3) : _size(0) {_ht.resize(GetNextPrime(capacity), nullptr);}~HashBucket() {Clear();}void Insert(const V& data) {_CheckCapacity();size_t bucketNo = HashFunc(data);HashBucketNode<V>* pCur = _ht[bucketNo];while (pCur) {if (pCur->_data == data)return;pCur = pCur->_pNext;}pCur = new HashBucketNode<V>(data);pCur->_pNext = _ht[bucketNo];_ht[bucketNo] = pCur;_size++;}void Erase(const V& data) {size_t bucketNo = HashFunc(data);HashBucketNode<V>* pCur = _ht[bucketNo];HashBucketNode<V>* pPrev = nullptr;while (pCur) {if (pCur->_data == data) {if (pPrev)pPrev->_pNext = pCur->_pNext;else_ht[bucketNo] = pCur->_pNext;delete pCur;_size--;return;}pPrev = pCur;pCur = pCur->_pNext;}}private:void _CheckCapacity() {if (_size == BucketCount()) {HashBucket<V, HF> newHt(BucketCount());for (size_t bucketIdx = 0; bucketIdx < BucketCount(); ++bucketIdx) {HashBucketNode<V>* pCur = _ht[bucketIdx];while (pCur) {_ht[bucketIdx] = pCur->_pNext;size_t bucketNo = newHt.HashFunc(pCur->_data);pCur->_pNext = newHt._ht[bucketNo];newHt._ht[bucketNo] = pCur;pCur = _ht[bucketIdx];}}newHt._size = _size;this->Swap(newHt);}}size_t HashFunc(const V& data) {return data % _ht.capacity();}private:vector<HashBucketNode<V>*> _ht;size_t _size;};

四、unordered系列关联式容器(以unordered_map为例)

底层结构

 unordered_map  底层使用哈希结构,通过哈希函数将键值对映射到不同的桶中,实现快速查找。

接口说明

1. 构造函数: unordered_map  用于构造不同格式的  unordered_map  对象。

2. 容量相关:

-  bool empty() const  :检测  unordered_map  是否为空。

-  size_t size() const  :获取  unordered_map  的有效元素个数。

3. 迭代器相关:

-  begin  :返回  unordered_map  第一个元素的迭代器。

-  end  :返回  unordered_map  最后一个元素下一个位置的迭代器。

-  cbegin  :返回  unordered_map  第一个元素的  const  迭代器。

-  cend  :返回  unordered_map  最后一个元素下一个位置的  const  迭代器。

4. 元素访问: operator[]  用于返回与  key  对应的  value  ,如果  key  不存在则插入一个默认值。

5. 查询:

-  iterator find(const K& key)  :返回  key  在哈希桶中的位置。

-  size_t count(const K& key)  :返回哈希桶中关键码为  key  的键值对的个数(在  unordered_map  中最大为1 )。

6. 修改操作:

-  insert  :向容器中插入键值对。

-  erase  :删除容器中的键值对。

-  void clear()  :清空容器中有效元素个数。

-  void swap(unordered_map&)  :交换两个容器中的元素。

7. 桶操作:

-  size_t bucket_count()const  :返回哈希桶中桶的总个数。

-  size_t bucket_size(size_t n)const  :返回  n  号桶中有效元素的总个数。

-  size_t bucket(const K& key)  :返回元素  key  所在的桶号 。

代码实现细节

 unordered_map  在C++中是基于哈希表实现的关联式容器,它存储的是  pair<K, V>  键值对, K  为  key  的类型, V  为  value  的类型,通过哈希函数( HF  类型)来实现快速的键值查找。以下是其具体实现的代码剖析:

类模板定义与类型别名

#include <vector>#include <memory>// 假设已有默认哈希函数定义template <class T>class DefHashF {public:size_t operator()(const T& val) const {// 简单示例,对于整数直接返回值,实际应用中需更复杂哈希计算return static_cast<size_t>(val); }};// 哈希桶节点定义template<class V>struct HashBucketNode {HashBucketNode(const V& data) : _pNext(nullptr), _data(data) {}HashBucketNode<V>* _pNext;V _data;};// 哈希桶类template<class K, class V, class KeyOfValue, class HF = DefHashF<K>>class HashBucket {friend class unordered_map<K, V, HF>;using PNode = HashBucketNode<V>*;public:// 迭代器类型定义(内部迭代器类后续补齐)class Iterator;Iterator Begin() {size_t bucketNo = 0;for (; bucketNo < _ht.size(); ++bucketNo) {if (_ht[bucketNo])break;}if (bucketNo < _ht.size())return Iterator(_ht[bucketNo], this);elsereturn Iterator(nullptr, this);}Iterator End() { return Iterator(nullptr, this); }Iterator Find(const K& key);Iterator Insert(const V& data);Iterator Erase(const K& key);size_t Count(const K& key) {auto it = Find(key);return (it != End())? 1 : 0;}size_t BucketCount() const { return _ht.size(); }size_t BucketSize(size_t bucketNo) const {size_t count = 0;PNode pCur = _ht[bucketNo];while (pCur) {count++;pCur = pCur->_pNext;}return count;}private:void _CheckCapacity() {if (_size == _ht.size()) {std::vector<PNode> newHt(_ht.size() * 2, nullptr);for (size_t bucketIdx = 0; bucketIdx < _ht.size(); ++bucketIdx) {PNode pCur = _ht[bucketIdx];while (pCur) {PNode next = pCur->_pNext;size_t bucketNo = HF{}(KeyOfValue{}(pCur->_data));pCur->_pNext = newHt[bucketNo];newHt[bucketNo] = pCur;pCur = next;}_ht[bucketIdx] = nullptr;}_ht = std::move(newHt);}}std::vector<PNode> _ht;size_t _size = 0;};// 哈希桶迭代器类template<class K, class V, class KeyOfValue, class HF>class HashBucket<K, V, KeyOfValue, HF>::Iterator {public:using Self = Iterator;using PNode = HashBucketNode<V>*;using HashBucketPtr = HashBucket<K, V, KeyOfValue, HF>*;Iterator(PNode pNode = nullptr, HashBucketPtr pHT = nullptr) : _pNode(pNode), _pHT(pHT) {}Self& operator++() {if (_pNode->_pNext) {_pNode = _pNode->_pNext;}else {size_t bucketNo = _pHT->HF{}(KeyOfValue{}( _pNode->_data)) + 1;for (; bucketNo < _pHT->_ht.size(); ++bucketNo) {if (_pHT->_ht[bucketNo]) {_pNode = _pHT->_ht[bucketNo];break;}}if (bucketNo == _pHT->_ht.size()) {_pNode = nullptr;}}return *this;}Self operator++(int) {Self tmp = *this;++(*this);return tmp;}V& operator*() { return _pNode->_data; }V* operator->() { return &(_pNode->_data); }bool operator==(const Self& it) const { return _pNode == it._pNode; }bool operator!=(const Self& it) const { return _pNode != it._pNode; }private:PNode _pNode;HashBucketPtr _pHT;};template<class K, class V, class HF = DefHashF<K>>class unordered_map {typedef pair<K, V> ValueType;typedef HashBucket<K, ValueType, KeyOfValue, HF> HT;struct KeyOfValue {const K& operator()(const ValueType& data) {return data.first;}};public:typedef typename HT::Iterator iterator;unordered_map() = default;iterator begin() { return _ht.Begin(); }iterator end() { return _ht.End(); }size_t size() const { return _ht._size; }bool empty() const { return _ht._size == 0; }V& operator[](const K& key) {auto it = _ht.Find(key);if (it != _ht.End()) {return *it;}else {auto newIt = _ht.Insert(ValueType(key, V()));return *newIt;}}const V& operator[](const K& key) const {auto it = _ht.Find(key);if (it != _ht.End()) {return *it; }else {// 对于const版本,这里可根据需求决定是否抛出异常等处理// 简单示例返回默认V值return V(); }}iterator find(const K& key) { return _ht.Find(key); }size_t count(const K& key) { return _ht.Count(key); }std::pair<iterator, bool> insert(const ValueType& value) {auto [it, success] = _ht.Insert(value);return {it, success};}iterator erase(iterator position) {return _ht.Erase(KeyOfValue{}(*position));}size_t bucket_count() { return _ht.BucketCount(); }size_t bucket_size(const K& key) {size_t bucketNo = HF{}(key);return _ht.BucketSize(bucketNo);}private:HT _ht;};

这里定义了  unordered_map  的类模板, HF  有默认的哈希函数类型  DefHashF<K>  。 ValueType  别名表示键值对类型, HT  则是对底层  HashBucket  类的实例化别名, KeyOfValue  结构体用于从键值对中提取  key  。
 
迭代器相关
 

public:typename typedef HT::Iterator iterator;
public:unordered_map() : _ht() {}iterator begin() { return _ht.Begin(); }iterator end() { return _ht.End(); }


 
 
通过  typedef  引入底层  HashBucket  的迭代器类型, begin  和  end  函数直接调用  HashBucket  类对应的函数,返回容器起始和结束位置的迭代器。
 
容量相关操作
 
  

  size_t size()const { return _ht.Size(); }bool empty()const{return _ht.Empty(); }


 
 
 size  函数返回容器中有效元素的个数, empty  函数判断容器是否为空,都是通过调用底层  HashBucket  类的对应方法实现。
 
元素访问操作
 
   

 V& operator[](const K& key) {return (*(_ht.InsertUnique(ValueType(key, V()))).first).second;}const V& operator[](const K& key)const;


 
 
 operator[]  操作符用于通过  key  访问  value  。如果  key  不存在,会插入一个默认值的键值对。这里调用  HashBucket  的  InsertUnique  方法插入(若不存在)并返回对应  value  。
 
查询操作
 
  

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


 
 find  函数用于查找指定  key  的位置,返回对应的迭代器; count  函数返回指定  key  在容器中出现的次数(在  unordered_map  中最多为1 ),均是转发给底层  HashBucket  类的相应函数处理。
 
修改操作
 
  

  pair<iterator, bool> insert(const ValueType& value) {return _ht.Insert(value);}iterator erase(iterator position) {return _ht.Erase(position);}


 
 
 insert  函数用于插入键值对,返回一个  pair  ,包含迭代器和表示插入是否成功的  bool  值; erase  函数用于删除指定位置的元素,返回删除元素后的下一个元素的迭代器,同样是依赖底层  HashBucket  类的实现。
 
桶相关操作
 
   

 size_t bucket_count() { return _ht.BucketCount(); }size_t bucket_size(const K& key) { return _ht.BucketSize(key); }


 bucket_count  函数返回哈希桶的总数, bucket_size  函数返回指定  key  所在桶中的元素个数,也是通过调用  HashBucket  类的对应方法来实现。
 
底层HashBucket类的部分实现
 

template<class K, class V, class KeyOfValue, class HF = DefHashF<K>>
class HashBucket {friend HBIterator<K, V, KeyOfValue, HF>;
public:typedef HBIterator<K, V, KeyOfValue, HF> Iterator;Iterator Begin() {size_t bucketNo = 0;for (; bucketNo < _ht.capacity(); ++bucketNo) {if (_ht[bucketNo])break;}if (bucketNo < _ht.capacity())return Iterator(_ht[bucketNo], this);elsereturn Iterator(nullptr, this);}Iterator End() { return Iterator(nullptr, this); }Iterator Find(const K& key);Iterator Insert(const V& data);Iterator Erase(const K& key);size_t Count(const K& key) {if (Find(key) != End())return 1;return 0;}size_t BucketCount()const { return _ht.capacity(); }size_t BucketSize(size_t bucketNo) {size_t count = 0;PNode pCur = _ht[bucketNo];while (pCur) {count++;pCur = pCur->_pNext;}return count;}
private:vector<PNode*> _ht;size_t _size;
};


 
 
 HashBucket  类是  unordered_map  的底层实现基础,它包含了哈希桶数组( _ht  )和元素个数( _size  )等成员。 Begin  函数用于找到第一个有元素的桶并返回对应的迭代器, End  函数返回表示结束的迭代器。 Find  、 Insert  、 Erase  等函数分别实现元素的查找、插入和删除操作, Count  函数统计指定  key  的个数, BucketCount  函数返回桶的总数, BucketSize  函数统计指定桶中的元素个数。
 
通过上述代码实现, unordered_map  借助底层  HashBucket  类,利用哈希表的特性,实现了高效的键值对存储、查找和修改等操作。在实际应用中,合理使用  unordered_map  能极大提升程序处理大量键值数据的效率。
 

五、哈希的应用
 


1. 海量数据处理面试题场景
- 问题示例:在海量数据中找出出现次数超过一定阈值的元素。
- 解决思路:可以利用  unordered_map  来统计每个元素出现的次数。例如,对于一个包含大量整数的数组,要找出出现次数超过数组长度1% 的元素。
- 代码示例:
 

#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;vector<int> findFrequentElements(vector<int>& nums) {unordered_map<int, int> countMap;for (int num : nums) {countMap[num]++;}vector<int> result;int threshold = nums.size() / 100; // 1%阈值for (const auto& p : countMap) {if (p.second > threshold) {result.push_back(p.first);}}return result;
}


 
 
2. 其他应用场景
- 缓存系统:利用哈希快速查找的特性,判断数据是否在缓存中,提高数据访问效率。
- 数据库索引:一些数据库采用哈希索引来加速数据的检索。
 

完整代码

HashTable.h

#define _CRT_SECURE_NO_WARNINGS
#include<vector>template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){// BKDRsize_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};namespace open_address
{enum STATE{EXIST,EMPTY,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;STATE _state = EMPTY;};template<class K, class V, class HashFunc = DefaultHashFunc<K>>class HashTable{public:/*HashTable(){_table.resize(10);}*/bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}// 扩容//if ((double)_n / (double)_table.size() >= 0.7)if (_n * 10 / _table.size() >= 7){size_t newSize = _table.size() * 2;// 遍历旧表,重新映射到新表HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);// 遍历旧表的数据插入到新表即可for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._table);}// 线性探测HashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashData<const K, V>* Find(const K& key){// 线性探测HashFunc hf;size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST&& _table[hashi]._kv.first == key){return (HashData<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();}return nullptr;}// 按需编译bool Erase(const K& key){HashData<const K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}return false;}private:vector<HashData<K, V>> _table;size_t _n = 0; // 存储有效数据的个数};
}
///
// 泛型编程:不是针对某种具体类型,针对广泛的类型(两种及以上) -- 模板
namespace hash_bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明template<class K, class T, class KeyOfT, class HashFunc>class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;Node* _node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;/*HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}*/HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){}// 普通迭代器时,他是拷贝构造// const迭代器时,他是构造HTIterator(const Iterator& it):_node(it._node), _pht(it._pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){// 当前桶还没完_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();// 从下一个位置查找查找下一个不为空的桶++hashi;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}};// 1、哈希表// 2、封装map和set// 3、普通迭代器// 4、const迭代器// 5、insert返回值  operator[]// 6、key不能修改的问题// set -> hash_bucket::HashTable<K, K> _ht;// map -> hash_bucket::HashTable<K, pair<K, V>> _ht;template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<T> Node;// 友元声明template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>friend struct HTIterator;public:typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;iterator begin(){// 找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur, this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{// 找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return const_iterator(cur, this);}}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}size_t GetNextPrime(size_t prime){static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={53,         97,         193,       389,       769,1543,       3079,       6151,      12289,     24593,49157,      98317,      196613,    393241,    786433,1572869,    3145739,    6291469,   12582917,  25165843,50331653,   100663319,  201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};size_t i = 0;for (; i < PRIMECOUNT; ++i){if (primeList[i] > prime)return primeList[i];}return primeList[i];}HashTable(){_table.resize(GetNextPrime(1), nullptr);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}///pair<iterator, bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end()){return make_pair(it, false);}HashFunc hf;// 负载因子到1就扩容if (_n == _table.size()){//size_t newSize = _table.size() * 2;size_t newSize = GetNextPrime(_table.size());vector<Node*> newTable;newTable.resize(newSize, nullptr);// 遍历旧表,顺手牵羊,把节点牵下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();// 头插Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}iterator Find(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}bool Erase(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}--_n;delete cur;return true;}prev = cur;cur = cur->_next;}return false;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << ":" << cur->_kv.second << "->";cur = cur->_next;}printf("NULL\n");}cout << endl;}private:vector<Node*> _table; // 指针数组size_t _n = 0; // 存储了多少个有效数据};
}

UnOrderedSet.h

#pragma once
#include"HashTable.h"namespace bit
{template<class K>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}pair<const_iterator, bool> insert(const K& key){//return _ht.Insert(key);pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);return pair<const_iterator, bool>(ret.first, ret.second);}private:hash_bucket::HashTable<K, K, SetKeyOfT> _ht;};
}

UnorderedMap.h

#pragma once
#include"HashTable.h"namespace bit
{template<class K, class V>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;};
}

test.cpp

#pragma once
#include<iostream>
#include<unordered_set>
#include<unordered_map>
#include<set>
using namespace std;//int main()
//{
//	// 去重
//	unordered_set<int> us;
//	size_t old = us.bucket_count();
//	cout << old << endl;
//	for (size_t i = 0; i < 10000; i++)
//	{
//		us.insert(i);
//
//		if (old != us.bucket_count())
//		{
//			old = us.bucket_count();
//			cout << old << endl;
//		}
//	}
//
//	/*unordered_set<int>::iterator it = us.begin();
//	while (it != us.end())
//	{
//		cout << *it << " ";
//		++it;
//	}
//	cout << endl;*/
//
//	/*unordered_map<string, string> dict;
//	dict["sort"] = "排序";
//	dict["insert"] = "插入";
//	dict["string"] = "字符串";
//	dict["left"];
//
//	for (auto& kv : dict)
//	{
//		cout << kv.first << ":" << kv.second << endl;
//	}
//	cout << endl;*/
//
//
//
//
//	return 0;
//}//int main()
//{
//	const size_t N = 100000;
//
//	unordered_set<int> us;
//	set<int> s;
//
//	vector<int> v;
//	v.reserve(N);
//	srand(time(0));
//	for (size_t i = 0; i < N; ++i)
//	{
//		v.push_back(rand());
//		//v.push_back(rand()+i);
//		//v.push_back(i);
//	}
//
//
//	size_t begin1 = clock();
//	for (auto e : v)
//	{
//		s.insert(e);
//	}
//	size_t end1 = clock();
//	cout << "set insert:" << end1 - begin1 << endl;
//
//	size_t begin2 = clock();
//	for (auto e : v)
//	{
//		us.insert(e);
//	}
//	size_t end2 = clock();
//	cout << "unordered_set insert:" << end2 - begin2 << endl;
//
//
//	size_t begin3 = clock();
//	for (auto e : v)
//	{
//		s.find(e);
//	}
//	size_t end3 = clock();
//	cout << "set find:" << end3 - begin3 << endl;
//
//	size_t begin4 = clock();
//	for (auto e : v)
//	{
//		us.find(e);
//	}
//	size_t end4 = clock();
//	cout << "unordered_set find:" << end4 - begin4 << endl << endl;
//
//	cout <<"插入数据个数:"<< s.size() << endl;
//	cout <<"插入数据个数:" << us.size() << endl << endl;;
//
//	size_t begin5 = clock();
//	for (auto e : v)
//	{
//		s.erase(e);
//	}
//	size_t end5 = clock();
//	cout << "set erase:" << end5 - begin5 << endl;
//
//	size_t begin6 = clock();
//	for (auto e : v)
//	{
//		us.erase(e);
//	}
//	size_t end6 = clock();
//	cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
//	
//	return 0;
//}#include"HashTable.h"//int main()
//{
//	open_address::HashTable<int, int> ht;
//	int a[] = { 1,111,4,7,15,25,44,9 };
//	for (auto e : a)
//	{
//		ht.Insert(make_pair(e, e));
//	}
//
//	ht.Erase(15);
//
//	auto ret = ht.Find(4);
//	//ret->_kv.first = 41;
//	ret->_kv.second = 400;
//
//	//HashTable<string, string, StringHashFunc> dict;
//	open_address::HashTable<string, string> dict;
//	dict.Insert(make_pair("sort", "排序"));
//	dict.Insert(make_pair("left", "xxx"));
//	auto dret = dict.Find("left");
//	//dret->_kv.first = "xx";
//	dret->_kv.second = "左边";
//
//	string s1("xxx");
//	string s2("xxx");
//
//
//	open_address::DefaultHashFunc<string> hf;
//	cout << hf(s1) << endl;
//	cout << hf(s2) << endl;
//	cout << hf("bacd") << endl;
//	cout << hf("abcd") << endl;
//	cout << hf("abbe") << endl;
//	cout << hf("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=STATE&fenlei=256&rsv_pq=0xdd48647300054f47&rsv_t=1cd5rO%2BE6TJzo6qf9QKcibznhQ9J3lFwGEzmkc0Goazr3HuQSIIc2zD78Pt0&rqlang=en&rsv_enter=1&rsv_dl=tb&rsv_sug3=2&rsv_n=2&rsv_sug1=1&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&prefixsug=STAT%2526gt%253B&rsp=5&inputT=656&rsv_sug4=796") << endl;
//
//	return 0;
//}//int main()
//{
//	hash_bucket::HashTable<int, int> ht;
//	int a[] = { 1,111,4,7,15,25,44,9 };
//	for (auto e : a)
//	{
//		ht.Insert(make_pair(e, e));
//	}
//	ht.Print();
//
//	ht.Insert(make_pair(14, 14));
//	ht.Print();
//
//	ht.Insert(make_pair(24, 24));
//	ht.Print();
//
//	ht.Insert(make_pair(34, 34));
//	ht.Print();
//
//	ht.Erase(44);
//	ht.Erase(4);
//	ht.Erase(24);
//	ht.Print();
//
//	hash_bucket::HashTable<string, string> dict;
//	dict.Insert(make_pair("sort", "排序"));
//	dict.Insert(make_pair("left", "xxx"));
//	dict.Insert(make_pair("insert", "插入"));
//	dict.Insert(make_pair("string", "字符串"));
//	dict.Insert(make_pair("bucket", "桶"));
//
//	auto dret = dict.Find("left");
//	//dret->_kv.first = "xx";
//	dret->_kv.second = "左边";
//	dict.Print();
//
//	return 0;
//}#include"UnOrderedSet.h"
#include"UnOrderedMap.h"//int main()
//{
//	bit::unordered_set<int> us;
//	us.insert(3);
//	us.insert(1);
//	us.insert(3);
//	us.insert(4);
//	us.insert(5);
//	us.insert(0);
//
//	bit::unordered_set<int>::iterator it = us.begin();
//	while (it != us.end())
//	{
//		// 不能修改key
//		//*it += 10;
//		cout << *it << " ";
//		++it;
//	}
//	cout << endl;
//
//	bit::unordered_map<string, string> dict;
//	dict.insert(make_pair("sort", "排序"));
//	dict.insert(make_pair("left", "左边"));
//	dict.insert(make_pair("insert", "插入"));
//	dict.insert(make_pair("sort", "xxx"));
//
//	bit::unordered_map<string, string>::iterator dit = dict.begin();
//	while (dit != dict.end())
//	{
//		// 不能修改key
//		//dit->first += 'x';
//		dit->second += 'x';
//
//		cout << dit->first << ":" << dit->second << endl;
//
//
//		++dit;
//	}
//	cout << endl;
//
//	dict["sort"] = "排序";
//	dict["insert"] = "插入";
//	dict["string"] = "字符串";
//	dict["right"];
//
//	for (auto& kv : dict)
//	{
//		cout << kv.first << ":" << kv.second << endl;
//	}
//
//	return 0;
//}

六、总结
 


哈希是一种强大且应用广泛的数据结构和算法思想。通过合理设计哈希函数和解决哈希冲突,能够实现高效的数据存储和查找。 unordered  系列关联式容器是哈希在C++中的典型应用,理解其底层原理有助于我们在实际编程中更好地选择和使用数据结构,提升程序性能。同时,哈希在海量数据处理等领域也发挥着重要作用,掌握相关知识对解决复杂问题大有裨益。
 
希望通过这篇博客,能让大家对哈希有更深入、全面的理解。在实际编程中,根据不同的需求和场景,灵活运用哈希相关知识,打造出更高效、稳定的程序。

相关文章:

  • 【OpenGL学习】(二)OpenGL渲染简单图形
  • 系统架构设计(八):三层架构
  • SVN 版本控制入门指南
  • Qt与Hid设备通信
  • Python多进程编程执行任务
  • Class类的详细说明
  • Go语言 GORM框架 使用指南
  • Unity 人物模型学习笔记
  • Windows 上安装下载并配置 Apache Maven
  • 英语学习5.17
  • 系统架构设计师案例分析题——软件架构设计篇
  • 深入解析 React 的 useEffect:从入门到实战
  • 网络切片:给用户体验做“私人定制”的秘密武器
  • Spring Boot- 2 (数万字入门教程 ):数据交互篇
  • shell脚本之条件判断,循环控制,exit详解
  • NestJS——日志、NestJS-logger、pino、winston、全局异常过滤器
  • JDBC 的编写步骤及原理详解
  • 多指标组合策略
  • 什么情况下使用ActiveMQ
  • 【读代码】端到端多模态语言模型Ultravox深度解析
  • 解放日报:“感觉全世界人都在上海买买买”
  • 尹锡悦宣布退出国民力量党
  • 科普|认识谵妄:它有哪些表现?患者怎样走出“迷雾”?
  • 马上评|家长抱婴儿值护学岗,如何避免“被自愿”?
  • 受关税政策影响,沃尔玛将上调部分商品在美售价
  • 商务部:中方将适时发布中美经贸磋商相关消息