哈希表的實現(下)

目錄

前言

?開散列概念

開散列實現

Insert

優化

?Find

Erase?


前言

上一章節我們用閉散列實現了一下哈希表,但存在一些問題,比如空間浪費比較嚴重,如果連續一段空間都已經存放值,那么在此位置插入新值的時候就會一直挪動,時間浪費也比較嚴重,當然可以通過二次探測來解決此問題,但今天我們來講解一下更優秀的解決方案。

?開散列概念

開散列法又叫鏈地址法(開鏈法),首先對關鍵碼集合用散列函數計算散列地址,具有相同地
址的關鍵碼歸于同一子集合,每一個子集合稱為一個桶,各個桶中的元素通過一個單鏈表鏈
接起來,各鏈表的頭結點存儲在哈希表中

像這樣,我們在插入的時候,遇到哈希沖突,不用將它向后移動找到空位置,而是通過鏈表的形式,將他們一一連接起來,從而達到不影響其他位置的效果。開散列中每個桶中放的都是發生哈希沖突的元素。這樣就能有效解決查找和插入的問題。

開散列實現

開散列的實現需要一個指針數組,通過這個數組將插入值進行鏈接,就像上面的圖片。

	template<class K, class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};

首先先定義一個節點,節點里面包含下一個位置的指針,一個_kv,接下來開始實現。

	template<class K, class V>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10);}~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;}}private:vector<Node*> _tables;size_t _n = 0;};

在HashTable中定義一個指針數組vector<Node*> _tables,統計插入個數_n,在構造函數中先開10大小的空間,析構函數中一次遍歷該數組,如果不為空,依次進行delete,直到為空為止,然后將該數組位置置為nullptr。

Insert

在實現成基本結構后,我們來進行insert的實現。再拿出這個圖來鞭尸。

?當插入15時,我們找到5的位置,之后在此位置進行頭插操作,即可將15鏈接在這個桶上。

同樣和閉散列一樣,這種方式也需要考慮擴容的情況,但閉散列的空間利用率較低,負載因子控制在0.7左右,而開散列這種方式可以設置為1。

實現insert的方式有兩種,大家可以先自己看一下哪一個更優秀。

1:

		bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 負載因子最大到1if (_n == _tables.size()){size_t newSize = _tables.size() * 2;HashTable<K, V> newHT;newHT._tables.resize(newSize);// 遍歷舊表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);}size_t hashi = kv.first % _tables.size();Node* newnode = new Node(kv);// 頭插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}

2:

bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;if (_n == _tables.size()){vector<Node*> newTables;newTables.resize(_tables.size() * 2, nullptr);// 遍歷舊表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){Node* next = cur->_next;// 挪動到映射的新表size_t hashi = cur->_kv.first % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = kv.first % _tables.size();Node* newnode = new Node(kv);// 頭插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}

?第一種方法用了閉散列實現時的方式,直接復用一下insert,這樣完成遍歷后就可以得到擴容后的哈希表,第二種方法選擇直接進行鏈接,然后將完成鏈接后的位置置空,同樣也可以完成擴容操作。

那么哪種情況會更好呢?答案是第二種,因為我們發現insert會new一個新節點然后再將其鏈接到哈希表中,而第二種情況是直接將現成的節點插入到擴容后的哈希表中,減少了空間的浪費,所以這種方法更優。

在第二種方法中,我們首先定義一個新的指針數組,然后依次遍歷舊表,將舊表中的值重新通過新的哈希函數進行轉換,得到的位置就是新插入的位置,然后將它頭插到表中即可,再將舊表位置置為空,完成便利后,我們只需要交換新舊表,這樣_tables就是擴完容后的哈希表。

優化

通常,我們的K為整形,但K為string時就不能轉換為關鍵字,這時就要用一個仿函數來進行完善。

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 11:46繼續
//HashFunc<string>
template<>
struct HashFunc<string>
{size_t operator()(const string& key){// BKDRsize_t hash = 0;for (auto e : key){hash *= 31;hash += e;}cout << key << ":" << hash << endl;return hash;}
};

?我們特化了一個仿函數,當K為string時,直接調用下面的仿函數,里面用到了BKDR算法,它能夠將字符串轉化為關鍵字,并且盡可能的避免了哈希沖突的情況,這種方法被廣泛應用。這樣我們只需要在哈希表中增加一個模板即可完成string這種情況下轉為關鍵字的情況。

template<class K, class V, class Hash = HashFunc<K>>

?Find

		Node* Find(const K& key){Hash hf;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return NULL;}

將要尋找的key轉換為關鍵字,該關鍵字可以映射哈希表中的一個位置,這時找到該位置,依次遍歷該鏈表,若能找到返回該節點指針,若到空還是不能找到,返回空。

Erase?

		bool Erase(const K& key){Hash hf;size_t hashi = hf(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}

?因為我們的節點里面只有next的指針,是一個單項鏈表,所以我們在刪除時要定義一個prev來指向前一個節點的位置。和前面類似,先通過哈希函數進行轉換,轉換為關鍵字后找到哈希表中對應的位置,進行查找,則會出現三種情況:

1.找到了該位置,前驅節點prev為空,說明該節點就是頭節點,只需要將哈希表中該位置鏈接到cur的next節點即可。

2.找到了該位置,前驅節點prev不為空,說明前面還有值,那么將prev的next更新為cur的next

即可完成鏈接。

3.循環結束后還沒返回,說明沒有此值。

erase還可以先用find查詢一下是否存在,若存在繼續進行刪除操作,不存在直接返回false。

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

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

相關文章

再談Linux 進程:進程等待、進程替換與環境變量

目錄 1.進程等待 為什么需要進程等待&#xff1f; 相關系統調用&#xff1a;wait()和waitpid() wait(): waitpid(): 解析子進程狀態&#xff08;status&#xff09; 2.進程替換 為什么需要進程替換&#xff1f; 相關系統調用&#xff1a;exec函數家族 3.環境變量 ?…

基于深度學習的無線電調制識別系統

基于深度學習的無線電調制識別系統 本項目實現了一個基于深度學習的無線電調制識別系統&#xff0c;使用LSTM&#xff08;長短期記憶網絡&#xff09;模型對不同類型的 無線電信號進行自動分類識別。該系統能夠在不同信噪比(SNR)條件下&#xff0c;準確識別多種調制類型&#…

Python 爬蟲之requests 模塊的應用

requests 是用 python 語言編寫的一個開源的HTTP庫&#xff0c;可以通過 requests 庫編寫 python 代碼發送網絡請求&#xff0c;其簡單易用&#xff0c;是編寫爬蟲程序時必知必會的一個模塊。 requests 模塊的作用 發送網絡請求&#xff0c;獲取響應數據。 中文文檔&#xf…

隨機森林(Random Forest)學習

隨機森林是一種基于集成學習的機器學習算法&#xff0c;屬于Bagging&#xff08;Bootstrap Aggregating&#xff09;方法的一種擴展。它通過組合多個決策樹來提升模型的泛化能力和魯棒性&#xff0c;廣泛用于分類、回歸和特征選擇任務。 1.隨機森林核心思想 1.1少數服從多數 在…

從 0 到 1!Java 并發編程基礎全解析,零基礎入門必看!

寫在前面 博主在之前寫了很多關于并發編程深入理解的系列文章&#xff0c;有博友反饋說對博主的文章表示非常有收獲但是對作者文章的某些基礎描述有些模糊&#xff0c;所以博主再根據最能接觸到的基礎&#xff0c;為這類博友進行掃盲&#xff01;當然&#xff0c;后續仍然會接…

el-input寬度自適應方法總結

使用 style 或 class 直接設置寬度 可以通過內聯樣式或 CSS 類來直接設置 el-input 的寬度為 100%&#xff0c;使其自適應父容器的寬度 <template><div style"width: 100%;"><el-input style"width: 100%;" v-model"input">…

解決 Supabase “permission denied for table XXX“ 錯誤

解決 Supabase “permission denied for table” 錯誤 問題描述 在使用 Supabase 開發應用時&#xff0c;你可能會遇到以下錯誤&#xff1a; [Nest] ERROR [ExceptionsHandler] Object(4) {code: 42501,details: null,hint: null,message: permission denied for table user…

java每日精進 5.20【MyBatis 聯表分頁查詢】

1. MyBatis XML 實現分頁查詢 1.1 實現方式 MyBatis XML 是一種傳統的 MyBatis 使用方式&#xff0c;通過在 XML 文件中編寫 SQL 語句&#xff0c;并結合 Mapper 接口和 Service 層實現分頁查詢。分頁需要手動編寫兩條 SQL 語句&#xff1a;一條查詢分頁數據列表&#xff0c;…

linux taskset 查詢或設置進程綁定CPU

1、安裝 taskset larkubuntu&#xff1a;~$ sudo apt-get install util-linux larkubuntu&#xff1a;~$ taskset --help 用法&#xff1a; taskset [選項] [mask | cpu-list] [pid|cmd [args...]] 顯示或更改進程的 CPU 關聯性。 選項&#xff1a; -a&#xff0c; --all-tasks…

Python應用字符串格式化初解

大家好!在 Python 編程中&#xff0c;字符串格式化是一項基礎且實用的技能。它能讓你更靈活地拼接字符串與變量&#xff0c;使輸出信息更符合需求。本文將為和我一樣的初學者詳細介紹 Python 字符串格式化的常用方法。 定義: 字符串格式化就是將變量或數據插入到字符串中的特定…

EasyRTC嵌入式音視頻通信SDK一對一音視頻通信,打造遠程辦公/醫療/教育等場景解決方案

一、方案概述? 數字技術發展促使在線教育、遠程醫療等行業對一對一實時音視頻通信需求激增。傳統方式存在低延遲、高畫質及多場景適配不足等問題&#xff0c;而EasyRTC憑借音視頻處理、高效信令交互與智能網絡適配技術&#xff0c;打造穩定低延遲通信&#xff0c;滿足基礎通信…

SEO長尾詞優化精準布局

內容概要 長尾關鍵詞作為SEO策略的核心要素&#xff0c;其價值在于精準捕捉細分需求與低競爭流量入口。相較于短尾詞的高泛化性&#xff0c;長尾詞通過語義擴展與場景化組合&#xff0c;能夠更高效地匹配用戶搜索意圖&#xff0c;降低優化成本的同時提升轉化潛力。本文將從詞庫…

【MySQL】第7節|Mysql鎖機制與優化實踐以及MVCC底層原理剖析

鎖等待分析 我們通過檢查InnoDB_row_lock相關的狀態變量來分析系統上的行鎖的爭奪情況 示例場景 假設有兩個用戶同時操作賬戶表 accounts&#xff08;主鍵為 id&#xff09;&#xff1a; 1. 用戶A&#xff1a;執行轉賬&#xff0c;鎖定賬戶 id1 并等待3秒&#xff1a; BEG…

基于規則引擎與機器學習的智能Web應用防火墻設計與實現

基于規則引擎與機器學習的智能Web應用防火墻設計與實現 引言&#xff1a;智能防御的必然選擇 在2023年OWASP最新報告中&#xff0c;傳統Web應用防火墻&#xff08;WAF&#xff09;對新型API攻擊的漏報率高達67%&#xff0c;而誤報導致的正常業務攔截損失每年超過2.3億美元。面…

GIM發布新版本了 (附rust CLI制作brew bottle流程)

GIM 發布新版本了&#xff01;現在1.3.0版本可用了 可以通過brew upgrade git-intelligence-message升級。 初次安裝需要先執行 brew tap davelet/gim GIM 是一個根據git倉庫內文件變更自動生成git提交消息的命令行工具&#xff0c;參考前文《GIM: 根據代碼變更自動生成git提交…

PyQt5高效布局指南:QTabWidget與QStackedWidget實戰解析

&#x1f50d; 問題背景 當界面控件過多時&#xff0c;直接平鋪會導致窗口擁擠、用戶體驗下降。PyQt5提供了兩種高效容器控件&#xff1a; QTabWidget&#xff1a;選項卡式布局&#xff0c;支持直接切換不同功能模塊QStackedWidget&#xff1a;堆棧式布局&#xff0c;需配合導…

《2.2.1順序表的定義|精講篇》

上一節學習了線性表的邏輯結構&#xff0c;線性表需要實現哪些基本運算/操作&#xff1f;在本節中&#xff0c;我們將學習順序表的定義、順序表的特性&#xff0c;以及如何用代碼來實現順序表。下個小節我們會介紹基于順序存儲&#xff08;這種存儲結構&#xff09;如何用代碼具…

【 大模型技術驅動智能網聯汽車革命:關鍵技術解析與未來趨勢】

大模型技術驅動智能網聯汽車革命&#xff1a;關鍵技術解析與未來趨勢 關鍵詞總結&#xff1a; 大模型技術&#xff1a;LLM、VLM、MLLM、Transformer架構核心場景&#xff1a;智能駕駛、智能座艙、智能網聯關鍵技術&#xff1a;端到端系統、BEVOCC網絡、多模態融合、強化學習挑…

Rocketmq broker 是主從架構還是集群架構,可以故障自動轉移嗎

RocketMQ Broker的架構與故障轉移機制 RocketMQ的Broker架構同時采用了主從架構和集群架構&#xff0c;并且支持故障自動轉移。下面詳細說明&#xff1a; 一、架構類型 1. 集群架構 RocketMQ天然支持分布式集群部署 一個RocketMQ集群包含多個Broker組(每組有主從) 不同Bro…

從零開始建立個人品牌并驗證定位變現性的方法論——基于開源AI大模型、AI智能名片與S2B2C商城生態的實證研究

摘要&#xff1a;本文提出一種融合開源AI大模型、AI智能名片與S2B2C商城小程序源碼的"最小測試閉環"方法論&#xff0c;通過技術賦能實現個人品牌定位的精準驗證與變現路徑優化。以某美妝領域自由職業者為例&#xff0c;其通過開源AI大模型完成能力圖譜構建與資源匹配…