網(wǎng)站圖片分辨率福州百度分公司
前言:
? ? ? ?前面我們學(xué)習(xí)了unordered_map和unordered_set容器,比較了他們和map、set的查找效率,我們發(fā)現(xiàn)他們的效率比map、set高,進(jìn)而我們研究他們的底層是由哈希實(shí)現(xiàn)。哈希是一種直接映射的方式,所以查找的效率很快。與學(xué)習(xí)紅黑樹和map、set的思路一樣,我們現(xiàn)在學(xué)完了unordered_map和unordered_set,本章將模擬實(shí)現(xiàn)底層結(jié)構(gòu)來封裝該容器!
? ? 作者建議在閱讀本章前,可以先去看一下前面的紅黑樹封裝map和set——紅黑樹封裝map和set
這兩篇文章都重在強(qiáng)調(diào)泛型編程的思想,上一篇由于是初認(rèn)識(shí),作者講解的會(huì)更詳細(xì)一點(diǎn)~
目錄
(一)如何復(fù)用一個(gè)哈希桶
1、結(jié)點(diǎn)的定義:
2、兩個(gè)容器各自的模板參數(shù)類型?編輯
3、改造哈希桶
(二)哈希桶的迭代器的模擬實(shí)現(xiàn)
1、begin()和end()的模擬實(shí)現(xiàn)
2、operator*和operator->及operator!=和operator==的模擬實(shí)現(xiàn)?
3、operator ++的模擬實(shí)現(xiàn)
(三)迭代器和改造哈希桶的總代碼
(四)封裝unordered_map和unordered_set
(一)如何復(fù)用一個(gè)哈希桶
我們學(xué)習(xí)過知道,unordered_map和unordered_set容器存放的結(jié)點(diǎn)并不一樣,為了讓它得到復(fù)用,我們就需要對(duì)哈希桶進(jìn)行改造,將哈希桶改造的更加泛型一點(diǎn),既符合Key模型,也符合Key_Value模型。
1、結(jié)點(diǎn)的定義:
?所以我們這里還是和封裝map和set
時(shí)一樣,無論是Key
還是Key_Value
,都用一個(gè)類型T來接收,這里高維度的泛型哈希表中,實(shí)現(xiàn)還是用的是Kye_Value
模型,K是不能省略的,同樣的查找和刪除
要用,故我們可以引出兩個(gè)容器各自模板參數(shù)類型。
2、兩個(gè)容器各自的模板參數(shù)類型
如何取到想要的數(shù)據(jù):
- 我們給每個(gè)容器配一個(gè)仿函數(shù)
- 各傳不同的仿函數(shù),拿到想要的不同的數(shù)據(jù)
同時(shí)我們?cè)俳o每個(gè)容器配一個(gè)哈希函數(shù)。
3、改造哈希桶
通過上面1和2,我們可以把各自存放的數(shù)據(jù)泛化成data:
這樣我們哈希桶的模板參數(shù)算是完成了
- 哈希函數(shù)我們可以自由選擇并傳
- 仿函數(shù)在各自容器的封裝中實(shí)現(xiàn),用于比較時(shí)我們可以取出各自容器想要的數(shù)據(jù)
我們把上一篇文章封裝的哈希桶拿來改造:
//K --> 鍵值Key,T --> 數(shù)據(jù)
//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
template<class K, class T, class KeyOfT, class HashFunc>
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(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}~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;}}size_t GetNextPrime(size_t prime){const int PRIMECOUNT = 28;static const size_t primeList[PRIMECOUNT] ={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};//獲取比prime大那一個(gè)素?cái)?shù)size_t i = 0;for (i = 0; i < PRIMECOUNT; i++){if (primeList[i] > prime)return primeList[i];}return primeList[i];}pair<iterator, bool> Insert(const T& data){HashFunc hf;KeyOfT kot;iterator pos = Find(kot(data));if (pos != end()){return make_pair(pos, false);}//負(fù)載因子 == 1 擴(kuò)容 -- 平均每個(gè)桶掛一個(gè)結(jié)點(diǎn)if (_tables.size() == _n){//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];//再對(duì)每個(gè)桶挨個(gè)遍歷while (cur){Node* next = cur->_next;size_t hashi = hf(kot(cur->_data)) % newSize;//轉(zhuǎn)移到新的表中cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}//將原表置空_tables[i] = nullptr;}newTable.swap(_tables);}}size_t hashi = hf(kot(data));hashi %= _tables.size();//頭插到對(duì)應(yīng)的桶即可Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;//有效數(shù)據(jù)加一_n++;return make_pair(iterator(newnode, this), true);}iterator Find(const K& key){if (_tables.size() == 0){return iterator(nullptr, this);}KeyOfT kot;HashFunc hf;size_t hashi = hf(key);//size_t hashi = HashFunc()(key);hashi %= _tables.size();Node* cur = _tables[hashi];//找到指定的桶之后,順著單鏈表挨個(gè)找while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}//沒找到返回空return iterator(nullptr, this);}bool Erase(const K& key){if (_tables.size() == 0){return false;}HashFunc hf;KeyOfT kot;size_t hashi = hf(key);hashi %= _tables.size();//單鏈表刪除結(jié)點(diǎn)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;}
private://指針數(shù)組vector<Node*> _tables;size_t _n = 0;
};
主要改造的地方就是上述所注意的地方:
- 比較時(shí)需要調(diào)用各自的仿函數(shù)
- 調(diào)用外部傳的哈希函數(shù)
還有對(duì)擴(kuò)容的二次思考:
研究表明:除留余數(shù)法,最好模一個(gè)素?cái)?shù)
- 通過查STL官方庫我們也發(fā)現(xiàn),其提供了一個(gè)取素?cái)?shù)的函數(shù)
- 所以我們也提供了一個(gè),直接拷貝過來
-
- 這樣我們?cè)跀U(kuò)容時(shí)就可以每次給素?cái)?shù)個(gè)桶
-
- 在擴(kuò)容時(shí)加了一條判斷語句是為了防止素?cái)?shù)值太大,過分?jǐn)U容容易直接把空間(堆)干崩了
(二)哈希桶的迭代器的模擬實(shí)現(xiàn)
1、begin()和end()的模擬實(shí)現(xiàn)
- 以第一個(gè)桶中第一個(gè)不為空的結(jié)點(diǎn)為整個(gè)哈希桶的開始結(jié)點(diǎn)
- 以空結(jié)點(diǎn)為哈希桶的結(jié)束結(jié)點(diǎn)
2、operator*和operator->及operator!=和operator==的模擬實(shí)現(xiàn)?
這兩組和之前實(shí)現(xiàn)的一模一樣,大家自行理解。
3、operator ++的模擬實(shí)現(xiàn)
注:
- 這里要在哈希桶的類外面訪問其私有成員
- 我們要搞一個(gè)友元類
- 迭代器類是哈希桶類的朋友
- 這樣就可以訪問了
?
思路:
- 判斷一個(gè)桶中的數(shù)據(jù)是否遍歷完
- 如果所在的桶沒有遍歷完,在該桶中返回下一個(gè)結(jié)點(diǎn)指針
- 如果所在的桶遍歷完了,進(jìn)入下一個(gè)桶
- 判斷下一個(gè)桶是否為空
- 非空返回桶中第一個(gè)節(jié)點(diǎn)
- 空的話就遍歷一個(gè)桶
- 后置++和之前一眼老套路,不贅述
注意:
unordered_map和unordered_set是不支持反向迭代器的,從底層結(jié)構(gòu)我們也能很好的理解(單鏈表找不了前驅(qū))所以不支持實(shí)現(xiàn)迭代器的operator- -
最后注意一點(diǎn),我們需要知道哈希桶大小,所以不僅要傳結(jié)點(diǎn)地址,還要傳一個(gè)哈希桶,這樣才能知道其大小,除此,由于哈希桶改造在后面,所以我們要在前面聲明一下:
(三)迭代器和改造哈希桶的總代碼
#include<vector>
#include<string>
#include<iostream>
using namespace std;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){//BKDRsize_t hash = 0;for (auto ch : key){hash = hash * 131 + ch;}return hash;}
};namespace 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 KeyOfT, class HashFunc>class __HTIterator{typedef HashNode<T> Node;typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;public:Node* _node;__HTIterator() {};//編譯器的原則是向上查找(定義必須在前面,否則必須先聲明)HashTable<K, T, KeyOfT, HashFunc>* _pht;__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){}Self& operator++(){if (_node->_next){_node = _node->_next;}else//當(dāng)前桶已經(jīng)走完了,要走下一個(gè)桶{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();hashi++;//找下一個(gè)不為空的桶 -- 訪問到了哈希表中私有的成員(友元)for (; hashi < _pht->_tables.size(); hashi++){if (_pht->_tables[hashi]){_node = _pht->_tables[hashi];break;}}//沒有找到不為空的桶,用nullptr去做end標(biāo)識(shí)if (hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};//K --> 鍵值Key,T --> 數(shù)據(jù)//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;template<class K, class T, class KeyOfT, class HashFunc>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(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}~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;}}size_t GetNextPrime(size_t prime){const int PRIMECOUNT = 28;static const size_t primeList[PRIMECOUNT] ={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};//獲取比prime大那一個(gè)素?cái)?shù)size_t i = 0;for (i = 0; i < PRIMECOUNT; i++){if (primeList[i] > prime)return primeList[i];}return primeList[i];}pair<iterator, bool> Insert(const T& data){HashFunc hf;KeyOfT kot;iterator pos = Find(kot(data));if (pos != end()){return make_pair(pos, false);}//負(fù)載因子 == 1 擴(kuò)容 -- 平均每個(gè)桶掛一個(gè)結(jié)點(diǎn)if (_tables.size() == _n){//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];//再對(duì)每個(gè)桶挨個(gè)遍歷while (cur){Node* next = cur->_next;size_t hashi = hf(kot(cur->_data)) % newSize;//轉(zhuǎn)移到新的表中cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}//將原表置空_tables[i] = nullptr;}newTable.swap(_tables);}}size_t hashi = hf(kot(data));hashi %= _tables.size();//頭插到對(duì)應(yīng)的桶即可Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;//有效數(shù)據(jù)加一_n++;return make_pair(iterator(newnode, this), true);}iterator Find(const K& key){if (_tables.size() == 0){return iterator(nullptr, this);}KeyOfT kot;HashFunc hf;size_t hashi = hf(key);//size_t hashi = HashFunc()(key);hashi %= _tables.size();Node* cur = _tables[hashi];//找到指定的桶之后,順著單鏈表挨個(gè)找while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}//沒找到返回空return iterator(nullptr, this);}bool Erase(const K& key){if (_tables.size() == 0){return false;}HashFunc hf;KeyOfT kot;size_t hashi = hf(key);hashi %= _tables.size();//單鏈表刪除結(jié)點(diǎn)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;}private://指針數(shù)組vector<Node*> _tables;size_t _n = 0;};
}
(四)封裝unordered_map和unordered_set
有了上面的哈希桶的改裝,我們這里的對(duì)map和set的封裝就顯得很得心應(yīng)手了。
unordered_map的封裝:
#include "HashTable.h"namespace zc
{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 Bucket::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:Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;};void test_map(){unordered_map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("left", "左邊"));dict.insert(make_pair("left", "下面"));dict["string"];dict["left"] = "上面";dict["string"] = "字符串";unordered_map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << it->first << " " << it->second << endl;++it;}cout << endl;for (auto e : dict){cout << e.first << " " << e.second << endl;}}}
這里unordered_map中的operator[ ]我們知道其原理之后,模擬實(shí)現(xiàn)就非常方便,直接調(diào)用插入函數(shù),控制好參數(shù)和返回值即可。
對(duì)unordered_set的封裝:
#include "HashTable.h"#include "HashTable.h"namespace zc
{template<class K, class HashFunc = DefaultHash<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://2.48typedef typename Bucket::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:Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;};struct Date{Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}bool operator==(const Date& d) const{return _year == d._year&& _month == d._month&& _day == d._day;}int _year;int _month;int _day;};struct DateHash{size_t operator()(const Date& d){//return d._year + d._month + d._day;size_t hash = 0;hash += d._year;hash *= 131;hash += d._month;hash *= 1313;hash += d._day;//cout << hash << endl;return hash;}};void test_set(){unordered_set<int> s;//set<int> s;s.insert(2);s.insert(3);s.insert(1);s.insert(2);s.insert(5);s.insert(12);unordered_set<int>::iterator it = s.begin();//auto it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;unordered_set<Date, DateHash> sd;sd.insert(Date(2022, 3, 4));sd.insert(Date(2022, 4, 3));}
}
最后大家可以利用代碼中給的測試函數(shù)進(jìn)行測試!
感謝你的閱讀!