C++哈希表之閉散列方法的模擬實現(xiàn)詳解
哈希
概念
可以不經(jīng)過任何比較,直接從表中得到要搜索的元素。 關(guān)鍵在于通過某種函數(shù),使元素的存儲位置與它的關(guān)鍵碼之間能夠建立 一一映射的關(guān)系。這樣就可以通過o(1)的時間復(fù)雜度來尋找到元素。
例如數(shù)據(jù)集合{1,7,4,5,9,6},哈希函數(shù)hash(key)=key&capacity
沖突
hash(7)=7 hash(17)=7,兩個不同的數(shù)通過哈希函數(shù)映射到了一個位置,產(chǎn)生了沖突。哈希函數(shù)設(shè)計的越精妙,產(chǎn)生沖突的可能性就越低,但無法避免。
解決方法:
- 閉散列(開放定址法)
- 開散列(拉鏈法)
閉散列
閉散列,(開放定址法)發(fā)生沖突時,如果哈希表沒有被填滿,則表內(nèi)一定還有其他空閑位置,可以把沖突值放到下一個沒有被占用的空余位置上。
如何找到下一個沒有被占用的空位?答:采用線性探測方法。從發(fā)生沖突的位置開始,依次向后探測,直到尋找到下一個空位置為止。
線性探測
線性探測的插入
如:在上述的哈希表中插入元素44,由于下標(biāo)為4的位置放入了元素4,于是從該位置往后++,找到第一個不為空的位置,將44放入。
線性探測的刪除
在尋找要刪除的元素時,依然會根據(jù)存放在哈希表的下標(biāo)開始尋找,比如在上述哈希表中尋找4,在4下標(biāo)位置直接就可以找到該元素。但如果直接將其刪除,那后續(xù)尋找元素44時,就會因為4下標(biāo)沒有元素,而認(rèn)為元素44不存在于這張哈希表。所以我們需要設(shè)置一個狀態(tài)來表示刪除。
哈希表閉散列的模擬實現(xiàn)
我們寫在一個自定義類域 Closehash 里面
準(zhǔn)備工作
哈希表中元素狀態(tài)
namespace Closehash { //哈希表中元素的狀態(tài) enum State { EMPTY, EXIT, DELETE }; }
存儲類型用pair即可,但是數(shù)據(jù)中要包含狀態(tài),我們進(jìn)行一次封裝
//由于數(shù)據(jù)需要一個狀態(tài),所以需要將pair<K,V>封裝一層 template<class K,class V> struct HashDate { pair<K, V>_kv; State _state; };
開始(畫餅)構(gòu)建哈希表的內(nèi)容
template<class K,class V> class HashTable { public: bool Insert(const pair<K,V>& kv); HashDate<K, V>* find(const K& key); bool Erase(const K& key); private: vector<HashDate<K,V>> _tables; size_t _size = 0; };
閉散列的插入
bool Insert(const pair<K, V>& kv) { //if (Find(kv.first)) return false; //Find實現(xiàn)了再去掉注釋 if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7)//相當(dāng)于存了70% { //開始擴(kuò)容 size_t newsize = _tables.size()== 0 ? 10 : _tables.size() * 2; HashTable<K, V> newHash; newHash._tables.resize(newsize); for (auto e: _tables)//注意_tables是HashDate類型 里面有_kv 和_state { if (e._state == EXIST) { newHash.Insert(e._kv); } } //資本家拷貝方法 _tables.swap(newHash._tables); } //走到這里擴(kuò)容完成 或者空間足夠大 size_t hashi = kv.first % _tables.size();//尋找在表中對應(yīng)的下標(biāo)是什么 while (_tables[hashi]._state==EXIST) { hashi++; //走到頭了得回來 hashi%=_tables.size(); } _tables[hashi]._kv = kv; _tables[hashi]._state = EXIST; _size++; return true; }
測試用例
void TestHT1() { int a[] = { 1, 11, 4, 15, 26, 7, 44 }; HashTable<int, int> ht; for (auto e : a) { ht.Insert(make_pair(e, e)); } ht.Print(); }
添加個99以驗證擴(kuò)容功能
閉散列的查找
HashDate<K, V>* Find(const K& key) { if (_tables.size() == 0) return nullptr; size_t hashi = key % _tables.size(); while (_tables[hashi]._state != EMPTY) { if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key) { return &_tables[hashi]; } hashi++; hashi% _tables.size(); } return nullptr; }
測試用例
cout << ht.Find(4)->_kv.first << endl;
閉散列的刪除
bool Erase(const K& key) { HashDate<K,V>* ret = Find(key); if (ret) { ret->_state = DELETE; --_size; return true; } else { return false; } }
模擬實現(xiàn)的閉散列中的問題與改進(jìn)
上述測試用例中使用的是pair<int,int>那我要是用pair<string,int>呢?我的key還可以直接對數(shù)組長度取模嗎?
文檔中對這一問題采用了仿函數(shù)的解決方法,我們這里也按照該方法模擬一個。
template<class K> struct HashFunc { size_t operator()(const K& key) { return (size_t)key; } }; // 特化 template<> struct HashFunc<string> { // BKDR size_t operator()(const string& key) { size_t val = 0; for (auto ch : key) { val *= 131; val += ch; } return val; } }; template<class K,class V,class Hash=HashFunc<K>> class HashTable { public: bool Insert(const pair<K,V>& kv); HashDate<K, V>* find(const K& key); bool Erase(const K& key); private: vector<HashDate<K,V>> _tables; size_t _size = 0; };
在每次求 在哈希表中位置的前面添加
Hash hash;
size_t hashi = hash(kv.first) % _tables.size()
測試用例
void TestHT2() { string arr[] = { "蘋果", "西瓜", "蘋果", "西瓜", "蘋果", "蘋果", "西瓜", "蘋果", "香蕉", "蘋果", "香蕉" }; //HashTable<string, int, HashFuncString> countHT; HashTable<string, int> countHT; for (auto& str : arr) { auto ptr = countHT.Find(str); if (ptr) { ptr->_kv.second++; } else { countHT.Insert(make_pair(str, 1)); } } }
測試用例沒加打印...讓我來回看了好幾遍代碼...蠢到無語
到此這篇關(guān)于C++哈希表之閉散列方法的模擬實現(xiàn)詳解的文章就介紹到這了,更多相關(guān)C++哈希表實現(xiàn)閉散列內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Opencv 視頻轉(zhuǎn)為圖像序列的實現(xiàn)
今天小編就為大家分享一篇Opencv 視頻轉(zhuǎn)為圖像序列的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12C++中使用FFmpeg適配自定義編碼器的實現(xiàn)方法
本文介紹了在C++中使用FFmpeg庫進(jìn)行自定義編碼器適配的實現(xiàn)方法。文章通過具體的代碼示例,介紹了FFmpeg的基本使用方法和自定義編碼器的實現(xiàn)過程,幫助讀者了解如何在C++中進(jìn)行音視頻編碼和解碼的開發(fā)工作,并能夠?qū)崿F(xiàn)自定義的編碼器適配2023-04-04C語言?超詳細(xì)模擬實現(xiàn)單鏈表的基本操作建議收藏
單鏈表是后面要學(xué)的雙鏈表以及循環(huán)鏈表的基礎(chǔ),要想繼續(xù)深入了解數(shù)據(jù)結(jié)構(gòu)以及C語言,我們就要奠定好這塊基石!接下來就和我一起學(xué)習(xí)吧2022-03-03探討register關(guān)鍵字在c語言和c++中的差異
建議不要用register關(guān)鍵字定義全局變量,因為全局變量的生命周期是從執(zhí)行程序開始,一直到程序結(jié)束才會終止,而register變量可能會存放在cpu的寄存器中,如果在程序的整個生命周期內(nèi)都占用著寄存器的話,這是個相當(dāng)不好的舉措2013-10-10