C++_哈希表

本篇文章是對C++學習的哈希表部分的學習分享

相信一定會對你有所幫助~

那咱們廢話不多說,直接開始吧!


一、基礎概念

1. 哈希核心思想:

  • 哈希函數的作用:通過此函數建立一個Key與存儲位置之間的映射關系。
  • 理想目標:實現O(1)時間復雜度的查找

2.直接定址法

本質:?關鍵字計算出?個絕對位置或者相對位置

適用場景:Key 范圍集中(如?[0,99]

二、關鍵問題與解決方案

1.哈希沖突:

  • 根本原因:不同 Key 映射到同一位置

  • 負載因子(Load Factor):α = N/M(N為已映射存儲的值,M為哈希表的大小)

    • α↑?→ 沖突概率↑,空間利用率↑

    • α↓?→ 沖突概率↓,空間利用率↓

2. 哈希函數設計原則:

  • 目標:均勻分布、減少沖突

  • 除法散列法 / 除留余數法(重點)

    • h(key) = key % M

    • M?的選擇:避免?2^n?或?10^n,因為?key%M 會僅保留?key 的最后?n?位(二進制或十進制),導致不同?key 可能映射到同一位置。例如:

M=16(2^4)時,63(00111111)和31(00011111)的后4位均為?1111,哈希值均為15。

M=100(10^2)時,112和12312的后兩位均為12,哈希值相同。

  • 理論上建議選擇遠離?2^n?的質數作為哈希表大小?M,以減少沖突。但實踐中可靈活優化,如 Java 的?HashMap?采用?2^16?作為?M,通過位運算((key ^ (key >> 16)) & (M-1))替代取模,既提升效率又讓高位參與計算,分散哈希值。核心在于均勻分布,而非機械套用理論。

  • 其他方法(了解):乘法散列法、全域散列法

3. 非整數Key的處理

有些數據類型無法直接用整形的哈希函數,比如string字符串類型,這時我們便可以嘗試將字符串轉證書(BKDR哈希思路)

size_t hash = 0;
for (char c : str) {hash = hash * 131 + c;  // 質數 131 減少沖突
}

三、 沖突解決策略

1. 開放定址法

線性探測

  • 從發?沖突的位置開始,依次線性向后探測,直到尋找到下?個沒有存儲數據的位置為?,如果? 到哈希表尾,則回繞到哈希表頭的位置
  • 沖突后公式:hashi = (hash0 + i) % M
  • 缺點:易產生聚集(Clustering)

二次探測

  • 從發?沖突的位置開始,依次左右按?次?跳躍式探測,直到尋找到下?個沒有存儲數據的位置為 ?,如果往右?到哈希表尾,則回繞到哈希表頭的位置;如果往左?到哈希表頭,則回繞到哈希表 尾的位置

  • 公式:hashi = (hash0 ± i2) % M
  • 緩解聚集,但可能錯過空位
刪除優化
  • 狀態標記(EXIST?/?EMPTY?/?DELETE

  1. EXIST:當前槽位存儲有效數據。

  2. EMPTY:槽位從未使用過,查找時可終止探測。

  3. DELETE:槽位數據已刪除,但探測鏈不能中斷(需繼續向后查找)。

enum STATE
{EMPTY,DELETE,EXIST
};

擴容機制

1. 負載因子與擴容條件

  • 負載因子(Load Factor)α = 元素數量 / 哈希表大小

  • 擴容閾值:當?α ≥ 0.7?時擴容,以降低沖突概率。

  • 擴容倍數:通常擴容為原大小的?2倍

2. 質數大小的必要性
  • 理論要求:哈希表大小?M?應為質數,使?key % M?分布更均勻(減少聚集)。

  • 問題:若初始?M?是質數(如 7),2倍擴容后(14)不再是質數,可能引發更多沖突。

3.解決方案

  • SGI 版本的質數表:

預定義質數表:按近似2倍遞增的質數序列擴容

//寫出來的28個素數(每一個都差不多為前一個的兩倍)
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_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
};//取素數的函數
inline unsigned long __stl_next_prime(unsigned long n)
{const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}
  • 擴容步驟

    1. 當前大小為?53(質數),負載因子 ≥0.7 時,從表中取下一個質數?97(≈53×1.8)。

    2. 重新哈希所有元素到新表。

  • 例子:在插入函數中,發現負載因子>=0.7后的操作:

  • bool Insert(const pair<K, V>& kv)
    {//Check(_tables);//如果負載因子 >=0.7 時就需要擴容了if ((double)_n / (double)_tables.size() >= 0.7){HashTable<K, V,Hash> newHT(__stl_next_prime(_tables.size()+1));for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);return true;}

    完整代碼實現

  • #pragma once
    #include<iostream>
    #include<vector>
    using namespace std;//寫出來的28個素數(每一個都差不多為前一個的兩倍)
    static const int __stl_num_primes = 28;
    static const unsigned long __stl_prime_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
    };//取素數的函數
    inline unsigned long __stl_next_prime(unsigned long n)
    {const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
    }enum STATE
    {EMPTY,DELETE,EXIST
    };template<class K,class V>
    struct HashData
    {pair<K, V> _kv;STATE _state;
    };template<class K>
    struct HashFunc
    {size_t operator()(const K& key){return (size_t)key;}
    };//因為用string類型的數值做key的情況十分常見,但是在unordered_map中卻沒有在另外寫一個仿函數
    //是因為直接將HashFunc 特化 了
    template<>
    struct HashFunc<string>
    {size_t operator()(const string& s){size_t hashi = 0;for (auto e : s){hashi += e;}return hashi;}
    };template<class K,class V,class Hash = HashFunc<K>>
    class HashTable
    {
    public://構造函數HashTable(size_t size = __stl_next_prime(0)):_n(0), _tables(size){}//將一些非整形的數值強轉成整形一次方便映射關系的計算void Check(vector<HashData<K,V>>& table){double fuzai = _n / table.size();if (fuzai >= 0.7){cout << "負載過大" << endl;}else{cout << "負載正常" << endl;}}bool Insert(const pair<K, V>& kv){//Check(_tables);//如果負載因子 >=0.7 時就需要擴容了if ((double)_n / (double)_tables.size() >= 0.7){HashTable<K, V,Hash> newHT(__stl_next_prime(_tables.size()+1));for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);return true;}Hash hs;size_t hash0 = hs(kv.first) % _tables.size();size_t hashi = hash0;size_t i = 1;//如果映射的位置已經被占用了while (_tables[hashi]._state == EXIST){hashi = (hashi + i) % _tables.size();++i;}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K,V>* Find(const K& key){Hash hs;size_t hash0 = hs(key) % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._kv.first == key && _tables[hashi]._state != DELETE){return &_tables[hashi];}hashi = (hashi + i) % _tables.size();++i;}cout << " 找不到找不到 " ;return nullptr;}bool Erase(const K& key	){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;cout << "成功刪除!" << endl;return true;}else{cout << "刪除失敗奧" << endl;return false;}}private:vector<HashData<K, V>> _tables;size_t _n;
    };
  • 2.鏈地址法

  • 核心思想:沖突位置掛鏈表(桶)

  • 擴容時機:負載因子?α ≥ 1(STL 風格)

  • 極端場景優化

    • 鏈表過長 → 轉紅黑樹(Java 8 HashMap 策略)

  • 擴容技巧

    • 直接移動舊節點(避免重復創建):

// 舊節點重新映射到新表
cur->_next = newTable[hashi];
newTable[hashi] = cur;
  • 例子:在哈希桶版本的Insert函數中發現負載因子過大
  • bool Insert(const pair<K, V>& kv)
    {if (_n == _tables.size()){vector<Node*> newTables(__stl_next_prime(0), 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 = _table[hashi];_tables[hashi] = newnode;++_n;return true;
    }

    完整代碼實現

  • template<class K, class V>
    struct HashNode
    {pair<K, V> _kv;HashNode* _next;HashNode(const pair<K,V>& key):_kv(key),_next(nullptr){}
    };template<class K,class V>
    class HashTable
    {typedef HashNode<K, V> Node;
    public:HashTable(size_t size = __stl_next_prime(0)):_tables(size,nullptr){}~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;}}bool Insert(const pair<K, V>& kv){if (_n == _tables.size()){vector<Node*> newTables(__stl_next_prime(0), 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 = _table[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){size_t hashi = key % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key) {size_t hashi = key % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){if (prev == nullptr){cur->_next = _tables[hashi];}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}
    private:vector<Node*> _tables;size_t _n = 0;
    };

    那么本次關于哈希表的知識分享就此結束了~

    非常感謝你能夠看到這里~

    如果感覺對你有些許的幫助也請給我三連 這會給予我莫大的鼓舞!

    之后依舊會繼續更新C++學習分享

    那么就讓我們

    下次再見~

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

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

相關文章

Mac M芯片 RAG 極簡流程 安裝 ragflow + LM studio

本文基于 【【知識科普】【純本地化搭建】【不本地也行】DeepSeek RAGFlow 構建個人知識庫】 https://www.bilibili.com/video/BV1WiP2ezE5a/?share_sourcecopy_web&vd_source9a55f12dd64d8e30ab6c0efc62844343 1 .docker-compose yml文件修改,指定平臺 platform: linux/…

Rsync+inotify+nfs實現數據實時備份方案

技術棧 NFS是 Network File System的簡寫&#xff0c;即網絡文件系統。NFS的優點是內核直接支持&#xff0c;部署簡單、運行穩定&#xff0c;協議簡單、傳輸效率高。缺點是僅依靠IP地址或主機名來決定用戶能否掛載共享目錄&#xff0c;容易出現單點故障。 rsync是linux系統下的…

Vue ⑥-路由

單頁應用程序 單頁應用程序&#xff0c;即 Single-Page Application&#xff0c;簡稱 SPA&#xff0c;是一種使用 JavaScript、HTML 和 CSS 構建的 Web 應用程序。SPA 的核心是前端路由&#xff0c;它使得用戶在訪問網站時&#xff0c;只需加載一次頁面&#xff0c;然后通過前…

Hadoop復習(九)

Azkaban工作流管理器 選擇 問題 1 判斷題 2 / 2 分 工作流是指具有依賴的一組job任務&#xff0c;被依賴的job任務最后執行 正確 錯誤 問題 2 判斷題 2 / 2 分 Azkaban兼容任何版本的Hadoop 正確 錯誤 問題 3 判斷題 2 / 2 分 獨立服務器模式下&#xff0c;Azkab…

SpringMVC相關知識(二)

一.重定向和轉發 1.ModelandView 設置ModelAndView對象 , 根據view的名稱 , 和視圖解析器跳到指定的頁面 頁面 : {視圖解析器前綴} viewName {視圖解析器后綴} 相關代碼&#xff1a; <!-- 視圖解析器 --> <bean class"org.springframework.web.servlet.vi…

std::ratio 簡單使用舉例

author: hjjdebug date: 2025年 06月 09日 星期一 14:28:40 CST descrip: std::ratio 簡單使用舉例 文章目錄 1. 先看一個簡單的例子 1/2/1/35/62 std::ratio 的手冊頁3. std::ratio_add 到底是什么呢&#xff1f;4. 代碼注釋5. 加深理解.6. 自定義的std::ratio 與 std::ratio_…

Docker 優勢與缺點全面解析:容器技術的利與弊

在當今云計算、微服務、DevOps盛行的時代&#xff0c;Docker 幾乎成了開發者、運維工程師的標配工具之一。自2013年誕生以來&#xff0c;Docker 以其輕量、快速、易移植的特點&#xff0c;徹底改變了應用的構建、交付與部署方式。 但任何技術都有兩面性&#xff0c;Docker 也不…

大語言模型(LLM)中的KV緩存壓縮與動態稀疏注意力機制設計

隨著大語言模型&#xff08;LLM&#xff09;參數規模的增長&#xff0c;推理階段的內存占用和計算復雜度成為核心挑戰。傳統注意力機制的計算復雜度隨序列長度呈二次方增長&#xff0c;而KV緩存的內存消耗可能高達數十GB&#xff08;例如Llama2-7B處理100K token時需50GB內存&a…

排序算法總結(C++)

目錄 一、穩定性二、排序算法選擇、冒泡、插入排序歸并排序隨機快速排序堆排序基數排序計數排序 三、總結 一、穩定性 排序算法的穩定性是指&#xff1a;同樣大小的樣本 **&#xff08;同樣大小的數據&#xff09;**在排序之后不會改變原始的相對次序。 穩定性對基礎類型對象…

使用Redis作為緩存優化ElasticSearch讀寫性能

在現代數據密集型應用中,ElasticSearch憑借其強大的全文搜索能力成為許多系統的首選搜索引擎。然而,隨著數據量和查詢量的增長,ElasticSearch的讀寫性能可能會成為瓶頸。本文將詳細介紹如何使用Redis作為緩存層來顯著提升ElasticSearch的讀寫性能,包括完整的架構設計、詳細…

獲取wordpress某個欄目的內容數量

獲取wordpress某個欄目的內容數量 <?php // 將以下 8 改成你的分類 ID 即可echo get_category(8)->count;?> 在制作wordpress模板時&#xff0c;有時會需要調用某個分類目錄下的所有內容數量&#xff0c;通過這段簡潔的代碼就可以實現。 給WordPress自定義字段加…

uniapp 安卓 APP 后臺持續運行(保活)的嘗試辦法

在移動應用開發領域&#xff0c;安卓系統的后臺管理機制較為復雜&#xff0c;應用在后臺容易被系統回收&#xff0c;導致無法持續運行。對于使用 Uniapp 開發的安卓 APP 來說&#xff0c;實現后臺持續運行&#xff08;保活&#xff09;是很多開發者面臨的重要需求&#xff0c;比…

深度學習——知識提煉

第一部分&#xff1a;引言與背景——為什么需要知識提煉&#xff1f; 一、模型壓縮的背景 隨著深度學習的發展&#xff0c;模型變得越來越大&#xff08;如 ResNet152、BERT、ViT、GPT 等&#xff09;&#xff0c;其參數量動輒數億甚至上百億。這些大模型雖然性能強大&#x…

開源之夏·西安電子科技大學站精彩回顧:OpenTiny開源技術下沉校園,點燃高校開發者技術熱情

開源之夏2025編程活動正在如火如荼的進行中&#xff0c;當前也迎來了報名的倒計時階段&#xff0c;開源之夏組織方也通過高校行系列活動進入各大高校&#xff0c;幫助高校開發者科普開源文化、開源活動、開源技術。 6月4日 開源之夏攜手多位開源技術大咖、經驗型選手走進西安電…

時間復雜度和算法選擇

數據范圍 時間復雜度 算法選擇 n \leq 30 指數級別 O(2^n) 深度優先搜索&#xff08;DFS&#xff09; 剪枝、狀態壓縮動態規劃 n \leq 100 O(n^3) Floyd 算法、動態規劃、高斯消元 n \leq 1000 O(n^2) 、 O(n^2 \log n) 動態規劃、二分…

數據分析實戰2(Tableau)

1、Tableau功能 數據賦能&#xff08;讓業務一線也可以輕松使用最新數據&#xff09; 分析師可以直接將數據看板發布到線上自動更新看板自由下載數據線上修改圖表郵箱發送數據設置數據預警 數據探索&#xff08;通過統計分析和數據可視化&#xff0c;從數據發現問題&#xf…

CentOS7_Linux下安裝Docker和docker-compose

目錄 環境要求安裝步驟1、修改鏡像源配置文件2、卸載舊版本 Docker&#xff08;如有&#xff09;3、安裝依賴工具4、添加 Docker 官方倉庫5、安裝 Docker 引擎6、啟動 Docker 并設置開機自啟7、驗證安裝8、配置鏡像加速器創建配置文件重啟 Docker 生效 9、允許非 root 用戶操作…

ubuntu中使用docker

上一篇我已經下載了一個ubuntu:20.04的鏡像&#xff1b; 1. 查看所有鏡像 sudo docker images 2. 基于本地存在的ubuntu:20.04鏡像創建一個容器&#xff0c;容器的名為cppubuntu-1。創建的時候就會啟動容器。 sudo docker run -itd --name cppubuntu-1 ubuntu:20.04 結果出…

均衡后的SNRSINR

本文主要摘自參考文獻中的前兩篇&#xff0c;相關文獻中經常會出現MIMO檢測后的SINR不過一直沒有找到相關數學推到過程&#xff0c;其中文獻[1]中給出了相關原理在此僅做記錄。 1. 系統模型 復信道模型 n t n_t nt? 根發送天線&#xff0c; n r n_r nr? 根接收天線的 MIMO 系…

佰力博科技與您探討熱釋電測量的幾種方法

熱釋電的測量主要涉及熱釋電系數的測定&#xff0c;這是表征熱釋電材料性能的重要參數。熱釋電系數的測量方法主要包括靜態法、動態法和積分電荷法。其中&#xff0c;積分電荷法最為常用&#xff0c;其原理是通過測量在電容器上積累的熱釋電電荷&#xff0c;從而確定熱釋電系數…