【C++高階六】哈希與哈希表

【C++高階六】哈希與哈希表

  • 1.什么是哈希?
  • 2.unordered系列容器
  • 3.哈希表
    • 3.1將key與存儲位置建立映射關系
      • 3.1.1直接定址法
      • 3.1.2除留余數法(產生哈希沖突)
    • 3.2解決哈希沖突的方法
      • 3.2.1閉散列(開放定址法)
      • 3.3.2開散列(鏈地址法)(哈希桶)
  • 4.實現哈希表(除留余數法建立映射)
    • 4.1閉散列實現
      • 4.1.1狀態與結構
      • 4.1.2 Insert
      • 4.1.3負載因子和擴容
      • 4.1.4 Find
      • 4.1.5 Erase
      • 4.1.6 完整代碼
    • 4.2 開散列實現
      • 4.2.1 狀態與結構
      • 4.2.2 Insert
      • 4.2.3 負載因子和擴容
      • 4.2.4 Find
      • 4.2.5 Erase
      • 4.2.6 仿函數
      • 4.2.7 完整代碼

1.什么是哈希?

理想中的搜索方法:不經過任何比較和遍歷就可以直接從表中搜索想要的元素
如果構造一種存儲結構,并通過某種函數能使元素的存儲位置與它自身關鍵碼之間建立映射的關系,那么在查找時只要通過函數就可以快速找到該元素

當向該結構中插入元素時,根據待插入元素的關鍵碼,以函數計算出該元素的存儲位置并按此位置進行存儲
當向該結構中搜索元素時,用函數對元素的關鍵碼進行同樣的計算求得元素的存儲位置,在結構中按此位置取元素比較,若關鍵碼相等則搜索成功
該方式即為哈希(散列)方法,哈希方法中使用的轉換函數稱為哈希(散列)函數,構造出來的結構稱為哈希表(Hash Table)(或者稱散列表)

2.unordered系列容器

unordered_mapunordered_setmapset是什么關系?
在這里插入圖片描述
在內部,unordered_map沒有對<kye, value>按照任何特定的順序排序

其實unordered_map和map使用起來沒什么區別,那么什么時候應該用unordered系列呢?答案是你只關心鍵值對的內容而不關心是否有序時就選擇unordered系列

3.哈希表

3.1將key與存儲位置建立映射關系

3.1.1直接定址法

在這里插入圖片描述

每一個值都有一個唯一位置,適用于范圍比較集中的數據

3.1.2除留余數法(產生哈希沖突)

在這里插入圖片描述

范圍不集中,分布分散
key值跟存儲位置的關系是取模出來的,不同的值有可能映射到相同的位置,造成哈希沖突,如:5和55取模后都為5

3.2解決哈希沖突的方法

3.2.1閉散列(開放定址法)

當發生哈希沖突時,如果哈希表未被裝滿,說明哈希表中必然還有空位置,則可以用線性探測把key存放到沖突位置中的下一個位置,若有兩個取模相同的值,則將先進來的占住當前取模位置,后進來的向后探測,若有空位置則放入
在這里插入圖片描述

key%len,len是表的長度

3.3.2開散列(鏈地址法)(哈希桶)

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

在這里插入圖片描述

4.實現哈希表(除留余數法建立映射)

4.1閉散列實現

4.1.1狀態與結構

整體實現全部放入名為開放地址法Open_Address的命名空間中
在這里插入圖片描述

假設要刪除55,因為55取余后為5,所以先去位置為5的地方去找,沒有找到則繼續向后尋找,到空才結束
但是把33刪除后,再次查找13時,由于提前遇到空,則查找直接結束,不會向后尋找 所以找到后不能直接刪除,會影響繼續查找

設置三種狀態: 空、存在、刪除

enum Status//狀態
{EMPTY,//空EXIST,//存在DELETE//刪除
};template<class K,class V>
struct HashData
{pair<K, V> _kv;//kv值Status _status = EMPTY;//狀態默認為空
};

state默認設為空,不然有可能造成映射位置沒有數據,但狀態為存在的情況

4.1.2 Insert

key值跟存儲位置的關系是取模出來的(key%len
len為 _tables.size() 還是 _tables.capacity()

假設為capacity,若當前位置為空,將值填入并將狀態設置為存在,就會造成越界,在vector中operator[]會做越界檢查,下標是否小于size

插入:

bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _table.size();_table[hashi]._kv = kv;_table[hashi]._status = EXIST;
}

線性探測:

while (_table[hashi]._status == EXIST)
{hashi++;hashi %= _table.size();//防止越界,可以回到0
}

完整代碼:

bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _table.size();while (_table[hashi]._status == EXIST){hashi++;hashi %= _table.size();//防止越界,可以回到0}_table[hashi]._kv = kv;_table[hashi]._status = EXIST;
}

4.1.3負載因子和擴容

哈希表沖突越多效率就越低,若表中位置都滿了就需要擴容,所以提出負載因子的概念
負載因子 = 填入表的元素個數 / 表的長度,用于表示 表儲存數量的百分比
填入表的元素個數 越大,表示沖突的可能性越大,所以在開放定址法時,應該控制在0.7-0.8以下,超過就會擴容

if (_num * 10 / _table.size() == 7)
{size_t new_num = _table.size() * 2;HashTable<K, V> new_HT;for (size_t i = 0; i < _table.size();i++)//遍歷舊表{if (_table[i]._status == EXIST){new_HT.Insert(_table[i]._kv);}}_table.swap(new_HT._table);
}

創建new_HT,將其中的_tables的size進行擴容,通過復用insert的方式,完成對新表的映射,最后交換舊表的_tables與new_HT的_tables ,以達到更新數據的目的

4.1.4 Find

若當前位置的狀態為存在或者刪除,則繼續找, 遇見空就結束
若在循環中找到了,則返回對應位置的地址,若沒找到則返回nullptr
刪除只是把要刪除的數據的狀態改為DELETE,但是數據本身還是在的,所以Find還是可以找到的

HashData<K, V>* Find(const K& key)
{size_t hashi = key % _table.size();while (_table[hashi]._status != EMPTY){if (_table[hashi]._status == EXIST && _table[hashi]._kv.first == key){return &_table[hashi];}hashi++;hashi %= _table.size();}return nullptr;
}

4.1.5 Erase

bool Erase(const k& key)
{HashData<K, V>* temp = Find(key);if (temp){temp->_status = DELETE;_num--;return true;}else{return false;}
}

4.1.6 完整代碼

using namespace std;
namespace Open_Address//閉散列(開放定址法)
{enum Status//狀態{EMPTY,//空EXIST,//存在DELETE//刪除};template<class K,class V>struct HashData{pair<K, V> _kv;//kv值Status _status = EMPTY;//狀態默認為空};template<class K,class V>class HashTable{public:HashTable(){_table.resize(10);}bool Insert(const pair<K, V>& kv){//負載因子等于0.7時擴容if (_num * 10 / _table.size() == 7){size_t new_num = _table.size() * 2;HashTable<K, V> new_HT;for (size_t i = 0; i < _table.size();i++)//遍歷舊表{if (_table[i]._status == EXIST){new_HT.Insert(_table[i]._kv);}}_table.swap(new_HT._table);}//線性探測size_t hashi = kv.first % _table.size();while (_table[hashi]._status == EXIST){hashi++;hashi %= _table.size();//防止越界,可以回到0}_table[hashi]._kv = kv;_table[hashi]._status = EXIST;}HashData<K, V>* Find(const K& key){size_t hashi = key % _table.size();while (_table[hashi]._status != EMPTY){if (_table[hashi]._status == EXIST && _table[hashi]._kv.first == key){return &_table[hashi];}hashi++;hashi %= _table.size();}return nullptr;}bool Erase(const k& key){HashData<K, V>* temp = Find(key);if (temp){temp->_status = DELETE;_num--;return true;}else{return false;}}private:vector<HashData<K, V>> _table;//指針數組size_t _num;//存儲的數據個數};
}

4.2 開散列實現

4.2.1 狀態與結構

整體實現全部放入名為哈希桶Hash_Bucket的命名空間中

template<class K,class V>
struct HashNode
{HashNode(const pair<K,V>& kv):_kv(kv),_next(nullptr){}HashNode<K, V>* _next;//指向下一個節點pair<K, V> _kv;//記錄數據
};

4.2.2 Insert

在同一個桶中數據不分先后
在這里插入圖片描述
創建一個新節點newnode,使newnode的next連接到當前映射位置,再讓newnode成為桶的頭節點

size_t hashi = kv.first % _table.size();
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;

4.2.3 負載因子和擴容

負載因子越大,沖突的概率越高,查找效率越低,空間利用率越高
負載因子越小,沖突的概率越低,查找效率越高,空間利用率越低

在這里插入圖片描述

在這里插入圖片描述
原表的節點重新計算位置后移動到新表中
由于新表的size大小為20,所以12可以找到對應位置的桶 ,而102沒有對應大小的桶,所以取模來到對應2位置處,與2形成鏈式結構
遍歷舊表中的數據,若數據為空,就往后遍歷
若數據不為空,則將其移動到新表中 ,進行頭插

if (_num == _table.size())
{size_t newsize = _table.size() == 0 ? 10 : _table.size()*2;vector<Node*> new_table;new_table.resize(newsize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* temp = _table[i];while (temp){Node* next = temp->_next;//挪動到新表size_t hashi = _table[i] % new_table.size();temp->_next = new_table[i];new_table[hashi] = temp;temp = next; }_table[i] = nullptr;}_table.swap(new_table);
}

4.2.4 Find

使用temp記錄當前映射位置,遍歷當前位置的單鏈表 ,查詢是否有key值的存在,若有則返回temp,若沒有則返回空

Node* Find(const K& key)
{if (_table.size() == 0){return nullptr;}size_t i = key % _table.size();Node* temp = _table[i];while (temp){if (temp->_kv.first == key){return temp;}temp = temp->_next;}return nullptr;
}

4.2.5 Erase

在這里插入圖片描述
假設要刪除3,則 需讓表的位置指向要刪除節點的下一個
在這里插入圖片描述
假設要刪除13,則需找到刪除節點的前一個節點,使其指向要刪除節點達到后一個節點

bool Erase(const K& key)
{size_t i = key % _table.size();Node* prev = nullptr;//記錄前一個結點Node* temp = _table[i];while (temp){if (temp->_kv.first == key){if(prev == nullptr)//刪除頭節點{_table[i] = temp->_next;}else{prev->_next = temp->_next;}delete temp;return true;}prev = temp;temp = temp->_next;}return false;
}

4.2.6 仿函數

在這里插入圖片描述
在這里插入圖片描述

若為string類型,使用insert無法計算對應的hashi值,所以需要加入仿函數
在這里插入圖片描述
加入全局仿函數:

template<class K>
struct HashFunc//仿函數
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>//仿函數特化
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;//乘31或者131都行,為了避免數字重復hash += e;}return hash;}
};

再次使用 HashTable時不用傳入仿函數也能調用string 類型

4.2.7 完整代碼

namespace Hash_Bucket//開散列(哈希桶)
{template<class K,class V>struct HashNode{HashNode(const pair<K,V>& kv):_kv(kv),_next(nullptr){}HashNode<K, V>* _next;//指向下一個節點pair<K, V> _kv;//記錄數據};template<class K,class V,class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:bool Insert(const pair<K,V>& kv){//負載因子等于1時擴容Hash hash;if (_num == _table.size()){size_t newsize = _table.size() == 0 ? 10 : _table.size()*2;vector<Node*> new_table;new_table.resize(newsize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* temp = _table[i];while (temp){Node* next = temp->_next;//挪動到新表size_t hashi = hash(temp->_kv.first) % new_table.size();temp->_next = new_table[i];new_table[hashi] = temp;temp = next; }_table[i] = nullptr;}_table.swap(new_table);}size_t hashi = hash(kv.first) % _table.size();Node* newnode = new Node(kv);//頭插newnode->_next = _table[hashi];_table[hashi] = newnode;_num++;return true;}Node* Find(const K& key){if (_table.size() == 0){return nullptr;}size_t i = hash(key) % _table.size();Node* temp = _table[i];while (temp){if (temp->_kv.first == key){return temp;}temp = temp->_next;}return nullptr;}bool Erase(const K& key){size_t i = key % _table.size();Node* prev = nullptr;//記錄前一個結點Node* temp = _table[i];while (temp){if (temp->_kv.first == key){if(prev == nullptr)//刪除頭節點{_table[i] = temp->_next;}else{prev->_next = temp->_next;}delete temp;return true;}prev = temp;temp = temp->_next;}return false;}private:vector<Node*> _table;//指針數組size_t _num = 0;//存儲的數據個數};
}

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

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

相關文章

Vue 3 +Ant Design Vue 父容器樣式不影響子級,隔離

公共樣式文件 common.scss.zz-ant-status-bar {div {font-size: 12px;padding: 0 8px;} }頁面代碼<div class"zz-ant-status-bar"><a-row><a-col :span"6" ><a-progress :percent"progress.percent" size"small"…

k8s 簡介及部署方法以及各方面應用

Kubernetes 簡介及部署方法Kubernetes&#xff08;簡稱 K8s&#xff09;是一個開源的容器編排平臺&#xff0c;用于自動化容器化應用的部署、擴展、管理和運維。它由 Google 基于內部的 Borg 系統經驗開發&#xff0c;2014 年開源后由云原生計算基金會&#xff08;CNCF&#xf…

Class A 包含字段 x Class B 也包含字段 x,如果判斷List<A> lista 和 List<B> listb 有相同的 x?

要判斷兩個不同類型的對象列表 List<A> 和 List<B> 是否包含相同的 x字段值&#xff08;即兩個列表中至少有一個 x是相同的&#xff09;&#xff0c;你可以使用 Java 8 的 Stream API 來實現。import java.util.List; import java.util.Set; import java.util.stre…

SpringBoot整合Camunda工作流

什么是工作流&#xff1f;概述 工作流是將一組任務組織起來以完成某個經營過程&#xff1a;定義了任務的觸發順序和觸發條件&#xff0c;每個任務可以由一個或多個軟件系統完成&#xff0c;也可以由一個或一組人完成&#xff0c;還可以由一個或多個人與軟件系統協作完成&#x…

2025年09月計算機二級Java選擇題每日一練——第四期

計算機二級中選擇題是非常重要的&#xff0c;所以開始寫一個每日一題的專欄。 答案及解析將在末尾公布&#xff01; 今日主題&#xff1a;面向對象特性 1、有兩個類 A 和 B 的定義如下&#xff1a; class A{final int x10;public void show(){System.out.print(x " &quo…

《Nature》新文解讀:電化學輔助核聚變的實驗驗證與機制分析

前言一篇于2025年8月發表在《Nature》期刊上的重磅研究&#xff0c;由加拿大不列顛哥倫比亞大學&#xff08;UBC&#xff09;Curtis P. Berlinguette教授領導的跨學科團隊完成&#xff0c;首次在實驗上證實&#xff1a;通過電化學方法向鈀金屬靶中加載氘&#xff0c;可顯著提升…

【基礎-判斷】用戶在長視頻、短視頻、直播、通話、會議、拍攝類應用等場景下,可以采用懸停適配在折疊屏半折態時,上屏進行瀏覽下屏進行交互操作

用戶在長視頻、短視頻、直播、通話、會議、拍攝類應用等場景下,可以采用懸停適配在折疊屏半折態時,上屏進行瀏覽下屏進行交互操作。 解釋如下: ? 1. 懸停態適配機制的核心設計 HarmonyOS 針對折疊屏半折態(懸停態)提供了分屏交互框架,其核心邏輯是: 上屏(Upper Scre…

nodejs安裝后 使用npm 只能在cmd 里使用 ,但是不能在poowershell使用,只能用npm.cmd

nodejs安裝后 使用npm 只能在cmd 里使用 &#xff0c;但是不能在poowershell使用&#xff0c;只能用npm.cmdnodejs版本&#xff1a;22.18.0 剛安裝好nodejs&#xff0c;在 PowerShell 中無法執行 npm&#xff0c;但能執行npm.cmd&#xff0c;這通常是因為 PowerShell 的執行策略…

【鏈表 - LeetCode】2. 兩數相加

誰都逃不掉 LeetCode &#xff01;&#xff01;哈哈哈~~~ 開刷&#xff1a;&#xff09; 2025年08月22日 題目&#xff1a;2. 兩數相加 - 力扣&#xff08;LeetCode&#xff09; 知識點&#xff1a;鏈表 /*** Definition for singly-linked list.* struct ListNode {* in…

WG-Tools 在線開發者工具推薦:完全免費、無廣告干擾、無需安裝、即開即用

WG-Tools 在線開發者工具箱全面探秘: 一站式效率提升平臺前言一. WG-Tools 平臺介紹 &#x1f6e0;?平臺概覽技術架構亮點二. 功能模塊詳細介紹 &#x1f3af;&#x1f4dd; 文本處理工具 (Text Tools)1. JSON工具2. XML工具3. 文本對比4. 正則表達式工具5. Markdown編輯器6. …

四十二、【核心功能強化】用例管理與調試:批量刪除與在線請求測試

四十二、【核心功能強化】用例管理與調試:批量刪除與在線請求測試 前言 準備工作 第一部分:后端實現 1. 修改 `TestCaseViewSet` (`api/views.py`) 2. 后端 API 權限: 第二部分:前端實現 1. 更新 `api/testcase.ts` API 服務 2. 改造 `TestCaseListView.vue` (用例列表頁面…

從H.264到AV1:音視頻技術演進與模塊化SDK架構全解析

引言 過去二十年&#xff0c;音視頻技術經歷了從 文件點播 → 流媒體 → 實時直播 → 互動協作 的深刻演變。早期的視頻更多停留在娛樂與媒體分發層面&#xff0c;而如今&#xff0c;它已經成為數字化社會的“實時交互基座”。從 安防監控的秒級告警、工業巡檢的遠程操作&…

Kubernetes 調度器 詳解

1. 調度器在 K8s 中的位置與核心流程API Server ←→ etcd ←→ kube-scheduler ←→ kubelet創建&#xff1a;用戶提交 Pod 描述&#xff08;YAML/Helm/Operator&#xff09;。監聽&#xff1a;調度器通過 Watch 機制捕獲到 spec.nodeName"" 的 Pod。過濾&#xff1…

51.Seata-TCC模式

前面兩種XA模式和TA模式,都是用了加鎖。 TCC模式則不會加鎖,性能更好。 TCC模式跟AT模式非常相似, 1.AT模式下,第一階段直接提交事務。 2.TCC模式下,第一階段不是提交事務,而是資源的預留凍結。 不同的是二階段TCC通過人工編碼來實現數據恢復。 需要實現三個方法 …

什么是數據分類分級?數據分類分級技術實現路徑及產品推薦

什么是數據分類分級&#xff1f; 數據分類分級是指按照一定的原則、方法和標準&#xff0c;對數據進行系統化的類別劃分和級別確定。具體而言&#xff0c;數據分類是依據數據的屬性、特征、來源、用途等維度&#xff0c;將數據劃分為不同的類別&#xff0c;如按照業務領域可分為…

深度學習——神經網絡

在當今人工智能蓬勃發展的時代&#xff0c;深度學習和神經網絡已經成為最受關注的技術領域之一。從智能手機的人臉識別到自動駕駛汽車的環境感知&#xff0c;從醫療影像分析到金融風險預測&#xff0c;這些技術正在深刻改變我們的生活和工作方式。本文將帶您了解深度學習和神經…

uniapp image標簽展示視頻第一幀

?vframe/jpg/offset/1/ 加到視頻后面獲取第一幀圖片 ?vframe/jpg/offset/1/w/400/h/300 設置寬高 ?imageView2/0/w/2000/interlace/1 設置圖片分辨率 2000 // 后面的 /1/ 是第幾幀 <image class"thumb" :src"videoUrl?vframe/jpg/offset/1/" mode…

前端本地模糊搜索1.0 按照匹配位置加權

需求背景 公司項目為Saas ERP系統&#xff0c;客戶需要快速開單需要避免接口帶來的延遲問題。所以需要將商品數據保存在本地。所以本地搜索 權重 這一套組合拳需要前端自己實現。 搜索示例 示例1&#xff1a;輸入&#xff1a;"男士真皮錢包"進行模糊匹配優先匹配完全…

Linux學習-網絡編程2

1.tcp可能出現粘包解決&#xff1a;要讓消息之間有邊界1.結束標志 \r\n2.固定長度3.協議結構體2.recv和sendrecv原型&#xff1a;ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能&#xff1a;從sockfd接收信息 參數&#xff1a;sockfd&#xff1a;要…

【普通地質學】構造運動與地質構造

名詞解釋走向&#xff1a;傾斜的層面與水平面的交線走向線&#xff0c;走向線兩端延伸的方向即為走向&#xff1b;構造運動&#xff1a;由于地球內部動力引起的組成巖石圈物質的機械運動&#xff0c;也可稱地殼運動或巖石圈運動&#xff1b;按方向分為垂直運動和水平運動&#…