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

【C++】unordered_set和unordered_map的实现

本篇unordered_set和unordered_map的实现基于:

自己实现的hash表 【C++】哈希表的实现(链地址法)

与map和set差不多的实现手法:【C++】模拟实现map和set 

 1.实现出复⽤哈希表的框架,并⽀持insert

创建3个头文件和一个源文件。

这个HashTable.h文件可以用之前写好的,可直接拷贝,再进行修改。 

key参数就⽤K,value参数就⽤V,哈希表中的数据类型,我们使⽤T。 
//HashTable.h文件
#pragma once
#include <iostream>
#include <vector>
#include<string>
using namespace std;inline unsigned long __stl_next_prime(unsigned long n)
{// Note: assumes long is at least 32 bits.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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}
namespace hash_bucket  // 链地址法/哈希桶
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}};template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};template<>struct HashFunc<string>{size_t operator()(const string& s){size_t hashi = 0;for(auto ch : s){ hashi += ch;hashi *= 131;}return hashi;}};template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>class HashTable{typedef HashNode<T> Node;public:HashTable():_table(__stl_next_prime(0)),_n(0){}bool Insert(const T& data){KeyOfT kot;if (Find(kot(data)));return false;Hash hash;if (_n == _table.size()) //扩容{vector<Node*> newtable(__stl_next_prime(_table.size() + 1));//创建新表for (int i = 0; i < _table.size(); i++) //遍历旧表{Node* cur = _table[i];while (cur) //遍历当前位置挂的所有节点{Node* next = cur->_next; //记录cur的下一个节点size_t hash0 = hash(kot(cur->_data)) % newtable.size(); //计算在新表的位置//直接把节点头插到新表cur->_next = newtable[hash0];newtable[hash0] = cur;cur = next; //遍历这条链上的下一个节点}_table[i] = nullptr;}_table.swap(newtable);//旧表新表互换}size_t hashi = hash(kot(data)) % _table.size(); //映射位置Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return true;}Node* Find(const K& key){KeyOfT kot;Hash hash;size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return cur; //找到了}cur = cur->_next;}return nullptr; //没找到}bool Erase(const K& key){KeyOfT kot;Hash hash;size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur){Node* prev = nullptr;if (cur->_kv.first == key) //找到了{if (prev == nullptr) //删除节点为头节点{_table[hashi] = cur->_next;}else //删除节点为中间节点或尾节点{prev->_next = cur->_next;}delete cur;_n--;return true;}else{prev = cur;cur = cur->_next;}}return false; //删除失败}~HashTable(){for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}private:vector<Node*> _table;size_t _n;};
}
//UnorderedSet.h文件
#include "HashTable.h"template<class K>
class Unordered_Set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:bool insert(const K& key){return _sht.Insert(key);}
private:hash_bucket::HashTable<K, K, SetKeyOfT> _sht;
};
//UnorderMap.h文件
#include "HashTable.h"
template<class K, class V>
class Unordered_Map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:bool insert(const pair<K, V>& kv){return _mht.Insert(kv);}
private:hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT> _mht;
};

这里的逻辑和自己实现map/set的逻辑是一样的,可参考那篇文章。

在test.cpp中测试一下,下面代码没出问题的话,就实现好了。

#include "UnorderedMap.h"
#include "UnorderedSet.h"int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3,3,15 };Unordered_Set<int> us;for (auto i : a){us.insert(i);}return 0;
}

2.⽀持iterator的实现

先把迭代器的结构写出来。

T是数据的类型,Ref是数据的引用,Ptr是数据的指针,KeyOfT是取Key,Hash是解决Key不能取模的仿函数

//这里需要HashTable的前置声明
template<class K, class T, class KeyOfT, class Hash> 
class HashTable;template< class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT; //这里使用了HashTable,之前需要有他的声明typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node;HT* _ht;HTIterator(Node* node, HT* ht):_node(node),_ht(ht){}};

然后把几个简单的运算符重载的实现写出来。 

template< class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node;HT* _ht;HTIterator(Node* node, HT* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}
};

2.1 operator++的实现

Self& operator++()
{if (_node->_next)//如果当前桶不为空{_node = _node->_next;}else //当前桶不为空{//就要找下一个桶}
}

这里主要是解决怎么从一个桶找到下一个桶。

可以先计算当前位置的hashi,然后对这个hashi进行+1的操作,就能找到下一个桶。

Self& operator++()
{if (_node->_next)//当前位置的下一个节点不为空{_node = _node->_next;}else //如果是当前桶的最后一个位置{//找下一个桶KeyOfT kot; //取出KeyHash hash;  //解决Key不能取模问题size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();//找映射位置hashi++; //下一个桶}
}

解释一下这句代码:  hash(kot(_node->_data))

我们不知道_node->_data是一个key还是一个pair,所以用kot(_node->_data)取出_data的key部分,这个key又不一定是能取模的类型,可能是string,所以用 hash(kot(_node->_data))来转换成能取模的。

下一个桶可能没有数据,我们要找下一个不为空的桶,所以还要while循环找,并且找的时候不能越界。

Self& operator++()
{if (_node->_next)//如果当前桶不为空{_node = _node->_next;}else //当前桶不为空{//找下一个桶KeyOfT kot;Hash hash;size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();//找映射位置hashi++;while (hashi < _ht->_table.size() && _ht->_table[hashi] == nullptr){hashi++;}}}

hashi < _ht->_table.size() && _ht->_table[hashi] == nullptr 这句代码的顺序不可交换,否则会发生越界访问。 

出while循环后,可能是走到end位置了,也可能是找到不为空的桶了,所以要分情况讨论。

Self& operator++()
{if (_node->_next)//如果当前桶不为空{_node = _node->_next;}else //当前桶不为空{//找下一个桶KeyOfT kot;Hash hash;size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();//找映射位置hashi++;while (_ht->_table[hashi] == nullptr && hashi < _ht->_table.size()){hashi++;}if (hashi == _ht->_table.size()) //走到表尾了,就是end的位置{_node = nullptr;}else //找到了不为空的桶{_node = _ht->_table[hashi];}}return *this;
}

解决 _table 为私有成员的问题

 _table是HashTable这个类的私有成员,在HashTable类外不能访问,这里的解决方法就是用友元HashTable类里加上下面这两句话。

template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
friend struct HTIterator;

这是类模板的友元声明,不能只写friend struct HTIterator; 模板的友元声明连模板参数也要写上

2.2 begin 和 end

HashTable类public对迭代器和const迭代器重命名。

public:typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;

HashTable类public实现begin和end。

begin的实现就是找到这个表的第一个节点,也就是第一个不为空的桶。找到之后,我们返回一个迭代器,这个迭代器用一个结点的指针和HashTable的指针(this指针)构造。

Iterator Begin()
{for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur)return Iterator(cur, this);}
}

如果遍历完了都没找到不为空的桶,就返回end。但是这里还可以加一个判断,_n为0的时候,这个表就是一个数据也没有,直接返回end。

Iterator Begin()
{if (_n == 0)return End();for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur)return Iterator(cur, this);}return End();
}

这个end就定义为空指针,但是实现的时候不能直接写return nullptr; 因为我们要返回一个迭代器,要用nullptr去构造这个迭代器。 

Iterator End()
{return Iterator(nullptr, this);
}

现在我们要在UnorderSet.h文件和UnorderMap.h文件里实现begin和end。

//UnorderedSet.h文件
public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::Iterator iterator;iterator begin(){_sht.Begin();}iterator end(){_sht.End();}
//UnorderedMap.h文件
public:typedef typename hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT>::Iterator iterator;iterator begin(){_mht.Begin();}iterator end(){_mht.End();}

test.cpp中测试一下。

#include "UnorderedMap.h"
#include "UnorderedSet.h"int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3, 55, 15 };Unordered_Set<int> us;for (auto i : a){us.insert(i);}auto it = us.begin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

 2.3 const迭代器

把const迭代器实现出来,和普通迭代器实现逻辑一样,返回值不同。

HashTable.h文件中的HashTable类里添加下面代码。

Const_Iterator Begin() const
{if (_n == 0)return End();for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur)return Const_Iterator(cur, this);}return End();
}Const_Iterator End() const
{return Const_Iterator(nullptr, this);
}

此时参数列表括号后面的那个const修饰的是this,也就意味着this是一个const的,而我们前面在定义的时候,这个this类型并不是const的。

为了解决这个问题,我们需要把上面这个地方改为const。

UnorderedSet.h文件中的Unordered_Set类里添加下面代码。

typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::Const_Iterator const_iterator;
const_iterator begin() const
{return _sht.Begin();
}const_iterator end()const
{return _sht.End();
}

UnorderedMap.h文件中的Unordered_Map类里添加下面代码。

typedef typename hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT>::Const_Iterator const_iterator;const_iterator begin() const
{return _mht.Begin();
}const_iterator end() const
{return _mht.End();
}

test.cpp中测试一下。

先测试unordered_set的。

void Print(const Unordered_Set<int>& s) //这里会用到const迭代器
{Unordered_Set<int>::const_iterator cit = s.begin();//auto cit = s.begin();while (cit != s.end())//迭代器遍历{cout << *cit << " ";++cit;}cout << endl;for (auto i : s)//范围for遍历{cout << i << " ";}cout << endl;
}int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3, 55, 15 };Unordered_Set<int> us;for (auto i : a){us.insert(i);}Print(us);return 0;
}

两种遍历方式都是没有问题的。 

 再测试一下unordered_map的。

void PrintMap(const Unordered_Map<string, string>& m)
{Unordered_Map<string, string>::const_iterator cit = m.begin();//auto cit = s.begin();while (cit != m.end())//迭代器遍历{cout << cit->first << ":" << cit->second << endl;++cit;}cout << endl;for (auto s : m)//范围for遍历{cout << s.first << ":" << s.second << endl;}
}int main()
{Unordered_Map<string, string> dic;dic.insert({ "sort", "排序" });dic.insert({ "left", "左边" });dic.insert({ "watermelon", "西瓜" });PrintMap(dic);return 0;
}

依旧是没有问题。 

2.4 Key不可修改的实现

正常情况下,对于set而言,K不可修改,对于map而言,K不可修改,V可修改

unordered_set修改如下。

 unordered_map修改如下。

 我们测试一下。

int main()
{Unordered_Map<string, string> dic;dic.insert({ "sort", "排序" });dic.insert({ "left", "左边" });dic.insert({ "watermelon", "西瓜" });Unordered_Map<string, string>::iterator cit = dic.begin();while (cit != dic.end())//迭代器遍历{//cit->first += 'x'; //K不可修改cit->second += 'x'; //V可修改cout << cit->first << ":" << cit->second << endl;++cit;}cout << endl;return 0;
}

 

3.operator[]的实现

operator[]的实现要用到find和insert,所以我们先把find和insert的返回值稍作修改,insert是返回一个pair,find返回一个迭代器。

HashTable.h文件中要修改的代码。

Iterator Find(const K& key)
{KeyOfT kot;Hash hash;size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return Iterator(cur, this); //找到了}cur = cur->_next;}return End(); //没找到,返回end
}

pair<Iterator, bool> Insert(const T& data)
{KeyOfT kot;Iterator it = Find(kot(data));if (it != End()) //插入的值已存在,插入失败return {it, false};//返回已存在的这个值的迭代器和falseHash hash;if (_n == _table.size()) //扩容{//...}size_t hashi = hash(kot(data)) % _table.size(); //映射位置Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return {Iterator(newnode, this), true};//插入成功,返回 新插入的值的迭代器 和 true
}

 在UnorderedSet.h文件中要修改的代码并补充find的封装。

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

 在UnorderedMap.h文件中要修改的代码并补充find的封装。

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

operator[]的实现和之前是一样的,代码如下。

//在 UnorderedMap.h 文件
V& operator[](const K& key)
{pair<iterator, bool> ret = insert({ key, V() });return ret.first->second;
}

测试一下。

void PrintMap(const Unordered_Map<string, string>& m)
{Unordered_Map<string, string>::const_iterator cit = m.begin();while (cit != m.end())//迭代器遍历{cout << cit->first << ":" << cit->second << endl;++cit;}
}int main()
{Unordered_Map<string, string> dic;dic.insert({ "sort", "排序" });dic.insert({ "left", "左边" });dic.insert({ "watermelon", "西瓜" });PrintMap(dic);cout << endl;dic["left"] = "剩余"; //left已存在,修改dic["right"] = "右边"; //right不存在,插入+修改dic["banana"]; //banana不存在,插入PrintMap(dic);return 0;
}

 这个operator[]就实现好了。

4.最后的修改

由于Key不能取模的问题,我们设计了一个仿函数来解决。

 但是这是在底层实现的,也就是哈希表这层,不好控制,所以我们要把对Hash这个仿函数的控制转移到上一层,也就是unordered_set和unordered_map这层。

做法就是把这里的缺省给去掉,

然后加在Unordered_Set和Unordered_Map类模板上。

 这里增加了一个模板参数后,其他相应的地方也要改。

unordered_set和unordered_map的实现就到这里,我们下篇见~

http://www.dtcms.com/a/271010.html

相关文章:

  • 三、Docker常用命令
  • HUAWEI HiCar6.0的新变化
  • 什么是 AMR 格式?简鹿音频转换器轻松批量转换 AMR 为 MP3
  • windows部署多实例filebeat监控相同路径下文件
  • 前端交互自定义封装类:“双回调自定义信息弹窗”
  • ai之 ubuntu本地安装mineru2.1.0
  • 整合Spring、Spring MVC与MyBatis:构建高效Java Web应用
  • Ubuntu22.04 设置显示存在双屏却无法双屏显示
  • DAS3D: Dual-modality Anomaly Synthesis for 3D Anomaly Detection 论文精读
  • 高通SG882G平台(移远),Ubuntu22编译:1、下载代码
  • 新零售“云化”进化:基于定制开发开源AI智能名片S2B2C商城小程序的探索
  • 中兴通讯首席项目管理专家、PMCoE负责人张宝忠受邀为PMO大会演讲嘉宾
  • redis的命令集合
  • WPF使用WebBrowser 解决href标签target=_blank在浏览器窗口打开新链接而非窗体内部打开的问题
  • Docker高级管理
  • 不深入,则无法共情
  • mysql 可用性的保障机制:主讲主从复制机制
  • Git 详解:从概念,常用命令,版本回退到工作流
  • 【03】MFC入门到精通——MFC 添加控件 设置属性 按钮 文本框
  • 《【第五篇】图片处理自动化:让你的视觉内容更专业!:图片处理基础与批量裁剪》
  • 06每日简报20250709
  • Vue 中mounted 生命周期钩子的执行时机和 v-for 的渲染顺序
  • linux-进程信号的产生与发送
  • 简单易懂的方式聊聊 DDD(领域驱动设计)
  • 汽车功能安全-软件单元验证 (Software Unit Verification)【用例导出方法、输出物】8
  • 分库分表之实战-sharding-JDBC水平分库+分表后:查询与删除操作实战
  • 【话题长廊测试报告】
  • qt绘制饼状图并实现点击即放大点击部分
  • 开发制作模仿参考抄别人的小程序系统
  • Shusen Wang推荐系统学习 --召回 ItemCF