哈希封裝unordered_map和unordered_set的模擬實現

文章目錄

  • (一)認識unordered_map和unordered_set
  • (二)模擬實現unordered_map和unordered_set
    • 2.1 實現出復用哈希表的框架
    • 2.2 迭代器iterator的實現思路分析
    • 2.3 unordered_map支持[]
  • (三)結束語

(一)認識unordered_map和unordered_set

unordered_map和unordered_set這兩個容器是C++11之后才更新的。unordered_map的底層復用了哈希表來實現key/value的結構,而unordered_set的底層也是復用了哈希表,但實現的是key的結構。這兩個容器的使用其實跟map和set的使用是一致的,只是map和set的底層復用的是紅黑樹,unordered_set和unordered_map這兩個容器的使用如下代碼:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>using namespace std;int main()
{//unordered_mapunordered_map<int, int> myunmap = { {4,4},{2,2},{8,8},{11,11},{2,2} };for (auto& e : myunmap){cout << e.first  << ":" << e.second << endl;}//unordered_setunordered_set<int> myunset = { 3,4,2,1,7,0,5,6,9,8,3,3 };for (auto& e : myunset){cout << e << " ";}cout << endl << endl;//mapmap<int, int> mymap = { {4,4},{2,2},{8,8},{11,11},{2,2} };for (auto& e : mymap){cout << e.first << ":" << e.second << endl;}//setset<int> myset = { 3,4,2,1,7,0,5,6,9,8,3,3 };for (auto& e : myset){cout << e << " ";}cout << endl;return 0;
}

打印結果:
在這里插入圖片描述
從代碼和打印結果來看,unordered_map和unordered_set這兩個容器跟map、set一樣都支持initializer_list初始化,但是從打印結果來看,unordered_map和unordered_set支持去重+不排序,而map和set支持去重+排序,這就是它們底層復用的封裝不同所導致的,map和set的底層是紅黑樹,迭代器在遍歷時走中序遍歷,所以打印出來的數據是有序的,而unordered_map和unordered_set底層是哈希表,哈希表是通過哈希桶來將值一個個存儲進去的,迭代器遍歷時是遍歷一個個哈希桶,所以底層復用的不同,實現出來的效果也略有不同,但是這幾個容器的使用方式完全是類似的。

(二)模擬實現unordered_map和unordered_set

2.1 實現出復用哈希表的框架

  • 首先我們得知道,哈希表既能被unordered_map復用又能被unordered_set復用,而unordered_map的數據結構是pair<key,value>,而unordered_set的數據結構就是一個key,由于這兩個容器的存儲的數據結構不同,所以哈希表在實現時應該使用泛型參數T來接收unordered_map和unordered_set傳過來的數據結構的類型,才能實例化出不同的哈希表,提供給這兩個容器使用
  • 哈希表在實現時還需要使用一個K參數來接收unordered_map和unordered_set的關鍵字,因為這兩個容器的find/erase等一些接口都是通過關鍵字來實現的。unordered_set傳給泛型T的就是關鍵字,但還是需要再傳一遍,就是為了要與unordered_map保持兼容,unordered_map傳給泛型T的是一堆pair值,實現find/erase等接口時就不方便,所以為了保持兼容,哈希表在實現時還需要使用一個K參數來接收unordered_map和unordered_set的關鍵字
  • 因為哈希表實現了泛型T,不知道傳過來的數據是k,還是pair<k,v>,那么insert內部進行插入時要用k對象轉換成整型取模和比較相等,因為pair的value不參與計算取模,且默認支持的是key和value一起比較相等,所以在任何時候只需要比較k對象,那么我們就需要在unordered_map和unordered_set層分別實現一個MapOfKey和SetOfKey的仿函數來傳給哈希表中的KeyOfT,然后在哈希表中通過KeyOfT仿函數取出T類型對象中的k對象,再轉換成整型取模和k比較相等

代碼實現如下:
步驟1:實現哈希表

#pragma once
#include <iostream>
#include <vector>
using namespace std;//將關鍵字轉成可以取模,如string本身取不了模,利用ASCII碼值即可,用仿函數實現
template<class K>
class HashFunc
{
public://全部轉成無符號的整型,這樣才能取模const size_t operator()(const K& key){return (size_t)key;}
};
//使用庫里面的unordered_map和unordered_set時,我們不用給string的取模進行仿函數的實現,因為庫里面為string的仿函數進行了特化
template<>
class HashFunc<string>
{
public:size_t operator()(const string& str){size_t ret = 0;for (auto& s : str){ret += s;ret *= 131;}return ret;}
};namespace li //模擬實現時防止與庫里面的沖突,用了命名空間
{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 Hash>class HashTable{typedef HashNode<T> Node;public:inline unsigned long _stl_next_prime(unsigned long n){static const int _stl_num_primes = 28;static const unsigned long _stl_primes_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_primes_list;const unsigned long* last = _stl_primes_list + _stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);//lower_bound()在迭代器的范圍內取>=n的最小值return pos == last ? *(pos - 1) : *(pos);}HashTable(){//提前開好第一個質數大小的空間_tables.resize(_stl_next_prime(1),nullptr);}~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 T& data){Hash hs;KeyOfT kot;if(find(kot(data)))return false;//負載因子為1就擴容if (_n == _tables.size()){unsigned long newsize = _stl_next_prime(_tables.size() + 1);vector<Node*> newtables(newsize, nullptr);for (int i = 0; i < _tables.size(); i++){//遍歷舊表,將數據挪到新表Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = hs(kot(cur->_data)) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}//算出key在哈希表中映射的存儲空間size_t hashi = hs(kot(data)) % _tables.size();//頭插Node* cur = new Node(data);cur->_next = _tables[hashi];_tables[hashi] = cur;++_n;return true;}bool find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (hs(kot(cur->_data)) == hs(key)){return true;}cur = cur->_next;}return false;}bool erase(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (hs(kot(cur->_data)) == hs(key)){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables;size_t _n = 0;};
}

步驟2:封裝unordered_map和unordered_set的框架,解決獲取關鍵字(KeyOfT)的問題

//unordered_map.h
#pragma once
#include "HashTable.h"
namespace Unordered_map
{template<class K, class V,class Hash = HashFunc<K>>class unordered_map{struct MapOfKey{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _hash.insert(kv);}bool find(const K& key){return _hash.find(key);}bool erase(const K& key){return _hash.erase(key);}private:li::HashTable<K, pair<const K, V>,MapOfKey,Hash> _hash;};
}
//unordered_set.h
#pragma once
#include "HashTable.h"
namespace Unordered_set
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetOfKey{const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _hash.insert(key);}bool find(const K& key){return _hash.find(key);}bool erase(const K& key){return _hash.erase(key);}private:li::HashTable<K,const K,SetOfKey,Hash> _hash;};
}

2.2 迭代器iterator的實現思路分析

  • iterator的實現就是用一個類型去封裝結點的哈希結點的指針,再通過重載運算符實現迭代器像指針一樣訪問的行為,這里哈希表的迭代器是一個單項迭代器
  • 重載運算符中,operator++如何實現呢,iterator中有一個結點的指針,該指針指向哈希桶里的一個結點,若桶下面還有結點,則將迭代器指向下一個結點即可。若桶的下面沒有結點,則需要找到哈希桶,為了能找到下一個桶,我們需要在迭代器中增加一個哈希表指針,讓該指針指向哈希表,這樣的話若當前桶走完了,要找到下一個桶就方便了,直接用key值計算出當前桶的位置,依次往后找不為空的桶即可,若往后找到的桶都為空,遍歷到哈希尾,就可返回一個nullptr

代碼如下:
步驟1:實現哈希表的迭代器,重載運算符

template<class K, class T, class KeyOfT, class Hash>
class HashTable;template<class K, class T, class KeyOfT, class Hash>
struct HashTableIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> ht;typedef HashTableIterator<K, T, KeyOfT, Hash> Self;Node* _node; //結點的指針const ht* _ht; //哈希表指針HashTableIterator(Node* node,const ht* ht):_node(node),_ht(ht){ }T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self operator++(){if (_node->_next){//說明桶的下面還有結點_node = _node->_next;}else{KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();hashi++;//找下一個不為空的桶while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;}else{hashi++;}}if (hashi == _ht->_tables.size()){//沒有不為空的桶_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};

步驟2:begin()返回第一個桶中第一個結點指針構造的迭代器,end()返回的迭代器可以用空來表示

template<class K,class T,class KeyOfT,class Hash>
class HashTable
{typedef HashNode<T> Node;template<class K, class T, class KeyOfT, class Hash>friend struct HashTableIterator; //友元聲明,方便迭代器訪問哈希表中的私有成員public:typedef HashTableIterator<K, T, KeyOfT, Hash> Iterator;Iterator Begin(){for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}
};

上面兩個步驟實現的迭代器是可以修改的,我們知道unordered_map和unordered_set的關鍵字是不可以被修改的,所以我們需要把unordered_set的第二個模板參數改成const K,unordered_map的第二個模板參數改成pair<const K,V>,代碼如下:

步驟3:封裝哈希迭代器實現unordered_map和unordered_set的迭代器

//unordered_map.h
template<class K, class V,class Hash = HashFunc<K>>
class unordered_map
{
public:typedef typename li::HashTable<K, pair<const K, V>, MapOfKey, Hash>::Iterator iterator;iterator begin(){return _hash.Begin();}iterator end(){return _hash.End();}
private:li::HashTable<K, pair<const K, V>,MapOfKey,Hash> _hash;
};
//unordered_set.h
template<class K,class Hash = HashFunc<K>>
class unordered_set
{
public:typedef typename li::HashTable<K,const K, SetOfKey, Hash>::Iterator iterator;iterator begin(){return _hash.Begin();}iterator end(){return _hash.End();}
private:li::HashTable<K,const K,SetOfKey,Hash> _hash;
};
li::HashTable<K, pair<const K, V>,MapOfKey,Hash> _hash; //unordered_map
li::HashTable<K,const K,SetOfKey,Hash> _hash; // unordered_set

步驟4:實現const迭代器
增加迭代器的模板參數

template<class K, class T, class KeyOfT, class Hash>
class HashTable; //對哈希表聲明template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hash>
struct HashTableIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> ht;typedef HashTableIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node; //結點的指針const ht* _ht; //哈希表指針HashTableIterator(Node* node,const ht* ht):_node(node),_ht(ht){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}
};

步驟5:實現const Begin()和const End()

template<class K,class T,class KeyOfT,class Hash>
class HashTable
{typedef HashNode<T> Node;template<class K, class T, class KeyOfT, class Hash>friend struct HashTableIterator; //友元聲明,方便迭代器訪問哈希表中的私有成員public:typedef HashTableIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HashTableIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;ConstIterator Begin() const{for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}
};

步驟6:封裝哈希const_iterator實現unordered_map和unordered_set的const_iterator

//unordered_map.h
template<class K, class V,class Hash = HashFunc<K>>
class unordered_map
{
public:typedef typename li::HashTable<K, pair<const K, V>, MapOfKey, Hash>::ConstIterator const_iterator;const_iterator begin() const{return _hash.Begin();}const_iterator end() const{return _hash.End();}
private:li::HashTable<K, pair<const K, V>,MapOfKey,Hash> _hash;
};
//unordered_set.h
template<class K,class Hash = HashFunc<K>>
class unordered_set
{
public:typedef typename li::HashTable<K,const K, SetOfKey, Hash>::ConstIterator const_iterator;const_iterator begin() const{return _hash.Begin();}const_iterator end() const{return _hash.End();}
private:li::HashTable<K,const K,SetOfKey,Hash> _hash;
};

2.3 unordered_map支持[]

unordered_map要支持[]主要需要修改insert返回值支持,修改HashTable中insert的返回值為pair<Iterator,bool> insert(const T& data),有了insert支持unordered_map的[]就很好實現了

代碼如下:

pair<Iterator,bool> insert(const T& data)
{Hash hs;KeyOfT kot;Iterator it = find(kot(data)); //find函數的返回值也要進行修改,修改成Iteratorif (it != End())return { it,false };//負載因子為1就擴容if (_n == _tables.size()){unsigned long newsize = _stl_next_prime(_tables.size() + 1);vector<Node*> newtables(newsize, nullptr);for (int i = 0; i < _tables.size(); i++){//遍歷舊表,將數據挪到新表Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = hs(kot(cur->_data)) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}
//unordered_map.h
V& operator[](const K& key)
{pair<iterator, bool> ret = _hash.insert(make_pair(key, V()));return ret.first->second;
}

既然哈希表中的inser的返回值修改了,那么對應的unordered_map和unordered_set的insert函數的返回值也要進行修改

(三)結束語

用哈希表來模擬實現這兩個容器,就得先學習底層的哈希表,不了解哈希表的友友可以看我的上一篇文章https://blog.csdn.net/muzi_liii/article/details/147519118?spm=1001.2014.3001.5501。該模擬實現簡易的unordered_map和unordered_set就完成了。一起學習吧!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/80690.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80690.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/80690.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java學習-Java基礎

1.重寫與重載的區別 重寫發生在父子類之間,重載發生在同類之間構造方法不能重寫,只能重載重寫的方法返回值,參數列表,方法名必須相同重載的方法名相同,參數列表必須不同重寫的方法的訪問權限不能比父類方法的訪問權限更低 2.接口和抽象類的區別 接口是interface,抽象類是abs…

BG開發者日志0427:故事的起點

1、4月26日晚上&#xff0c;BG項目的gameplay部分開發完畢&#xff0c;后續是細節以及試玩版優化。 開發重心轉移到story部分&#xff0c;目前剛開始&#xff0c; 確切地說以前是長期擱置狀態&#xff0c;因為過去的四個月中gameplay部分優先開發。 --- 2、BG這個項目的起點…

頭歌實訓之游標觸發器

&#x1f31f; 各位看官好&#xff0c;我是maomi_9526&#xff01; &#x1f30d; 種一棵樹最好是十年前&#xff0c;其次是現在&#xff01; &#x1f680; 今天來學習C語言的相關知識。 &#x1f44d; 如果覺得這篇文章有幫助&#xff0c;歡迎您一鍵三連&#xff0c;分享給更…

【深度學習】多頭注意力機制的實現|pytorch

博主簡介&#xff1a;努力學習的22級計算機科學與技術本科生一枚&#x1f338;博主主頁&#xff1a; Yaoyao2024往期回顧&#xff1a;【深度學習】注意力機制| 基于“上下文”進行編碼,用更聰明的矩陣乘法替代笨重的全連接每日一言&#x1f33c;: 路漫漫其修遠兮&#xff0c;吾…

java16

1.API續集 可以導入別人寫好的clone的jar包 注意&#xff1a;方法要有調用者&#xff0c;如果調用者是null就會報錯 2.如何導入別人寫好的jar包 復制jar包然后粘貼在lib里面&#xff0c;然后右鍵點擊jar包再點擊下面的add 3.關于打印java中的引用數據類型

PostgreSQL的擴展 credcheck

PostgreSQL的擴展 credcheck credcheck 是 PostgreSQL 的一個安全擴展&#xff0c;專門用于強制實施密碼策略和憑證檢查&#xff0c;特別適合需要符合安全合規要求的數據庫環境。 一、擴展概述 1. 主要功能 強制密碼復雜度要求防止使用常見弱密碼密碼過期策略實施密碼重復使…

MyBatis中的@Param注解-如何傳入多個不同類型的參數

mybatis中參數識別規則 默認情況下,MyBatis 會按照參數位置自動分配名稱:param1, param2, param3, ...或者 arg0, arg1。 // Mapper 接口方法 User getUserByIdAndName(Integer id, String name); 以上接口在XML中只能通過param1或者arg0這樣的方式來引用,可讀性差。 &l…

DIFY教程第一集:安裝Dify配置環境

一、Dify的介紹 https://dify.ai/ Dify 是一款創新的智能生活助手應用&#xff0c;旨在為您提供便捷、高效的服務。通過人工智能技術&#xff0c; Dify 可以實現語音 助手、智能家居控制、日程管理等功能&#xff0c;助您輕松應對生活瑣事&#xff0c;享受智慧生活。簡約的…

5、Rag基礎:RAG 專題

RAG 簡介 什么是檢索增強生成? 檢索增強生成(RAG)是指對大型語言模型輸出進行優化,使其能夠在生成響應之前引用訓練數據來源之外的權威知識庫。大型語言模型(LLM)用海量數據進行訓練,使用數十億個參數為回答問題、翻譯語言和完成句子等任務生成原始輸出。在 LLM 本就強…

GAMES202-高質量實時渲染(homework1)

目錄 Homework1shadow MapPCF(Percentage Closer Filter)PCSS(Percentage Closer Soft Shadow) GitHub主頁&#xff1a;https://github.com/sdpyy1 作業實現:https://github.com/sdpyy1/CppLearn/tree/main/games202 Homework1 shadow Map 首先需要完成MVP矩陣的構造&#xf…

JDK(Ubuntu 18.04.6 LTS)安裝筆記

一、前言 本文與【MySQL 8&#xff08;Ubuntu 18.04.6 LTS&#xff09;安裝筆記】同批次&#xff1a;先搭建數據庫&#xff0c;再安裝JDK&#xff0c;后面肯定就是部署Web應用&#xff1a;典型的單機部署。“麻雀雖小五臟俱全”&#xff0c;善始善終&#xff0c;還是記下來吧。…

軟件測試之接口測試常見面試題

一、什么是(軟件)接口測試? 接口測試&#xff1a;是測試系統組件間接口的一種測試方法 接口測試的重點&#xff1a;檢查數據的交換&#xff0c;數據傳遞的正確性&#xff0c;以及接口間的邏輯依賴關系 接口測試的意義&#xff1a;在較早期開展&#xff0c;在軟件開發的同時…

Lua 第11部分 小插曲:出現頻率最高的單詞

在本章中&#xff0c;我們要開發一個讀取并輸出一段文本中出現頻率最高的單詞的程序。像之前的小插曲一樣&#xff0c;本章的程序也十分簡單但是也使用了諸如迭代器和匿名函數這樣的高級特性。 該程序的主要數據結構是一個記錄文本中出現的每一個單詞及其出現次數之間關系的表。…

軟件項目進度管理活動詳解

目錄 1. 活動定義&#xff08;Activity Definition&#xff09; 2. 活動排序&#xff08;Activity Sequencing&#xff09; 3. 活動資源估算&#xff08;Activity Resource Estimating&#xff09; 4. 活動歷時估算&#xff08;Activity Duration Estimating&#xff09; …

docker 國內源和常用命令

Ubuntu | Docker Docs 參考docker官方安裝docker # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt…

身份與訪問管理(IAM):零信任架構下的認證授權技術與實戰

身份與訪問管理&#xff08;IAM&#xff09;&#xff1a;零信任架構下的認證授權技術與實戰 在網絡安全防御體系中&#xff0c;身份與訪問管理&#xff08;Identity and Access Management, IAM&#xff09;是守護數字資產的“數字門禁系統”。隨著遠程辦公和多云架構的普及&a…

Maven進階知識

一、Maven 坐標 &#xff08;一&#xff09;概念 在 Maven 中坐標是構件的唯一標識&#xff0c;其元素包括 groupId、artifactId、version、packaging、classifier。其中 groupId、artifactId、version 是必定義項&#xff0c;packaging 默認為 jar。 &#xff08;二&#x…

網絡原理 ——TCP 協議

TCP 報文結構 TCP 頭部 20字節&#xff08;無選項&#xff09;&#xff0c;關鍵字段&#xff1a; 字段長度&#xff08;bit&#xff09;說明源端口16發送方端口目的端口16接收方端口序列號&#xff08;seq&#xff09;32數據字節的編號確認號&#xff08;ack&#xff09;32期…

C#使用sftp遠程拷貝文件

需要下載 的包&#xff1a;Core.Renci.SshNet 下載依賴包的時候需要注意版本&#xff0c;高版本的.net環境不支持會用不了&#xff0c;我用的.net5,所以下載的2021.10.2 功能的核心式創建一個SftpClient&#xff0c;并傳入所需要的參數&#xff1a;遠程IP地址&#xff0c;端口…

文本預處理(NLTK)

1. 自然語言處理基礎概念 1.1 什么是自然語言處理 自然語言處理( Natural Language Processing, NLP)是計算機科學領域與人工智能領域中的一個重要方向。它研究能實現人與計算機之間用自然語言進行有效通信的各種理論和方法。自然語言處理是一門融語言學、計算機科學、數學于…