【C++11】哈希表與無序容器:從概念到應用

文章目錄

  • 一、前言
  • 二、哈希表(Hash Table)
    • 1. 基本概念
    • 2. 哈希函數
    • 3. 沖突解決方法
      • 鏈地址法(Separate Chaining)
      • 開放尋址法(Open Addressing)
    • 4. 性能分析
    • 5. 動態擴容
    • 6. 應用場景
    • 7. 優缺點
  • 二. 無序容器的介紹
    • 1. unordered_set
    • 2. unordered_map
    • 3. unordered_multiset
    • 4. unordered_multimap
    • 5. 總結
  • 三. 無序容器與關聯式容器的對比
    • 1. 存儲方式
    • 2. 時間復雜度
    • 3. 元素順序
    • 4. 適用場景
    • 5. 迭代器
    • 6. 內存使用
  • 四、總結

一、前言

C++11 標準引入了一個非常重要的容器系列——無序容器(unordered containers),包括 unordered_setunordered_mapunordered_multisetunordered_multimap。這些容器與之前的關聯式容器(如 setmapmultisetmultimap)類似,但有一個重要區別:無序容器的元素不會按某種順序(如升序或降序)排列,而是通過哈希表來組織。 因此,訪問、插入和刪除操作的時間復雜度大大降低(平均情況下為常數時間 O(1)),在許多應用場景下提供了更高效的性能。

下面我們會詳細介紹這幾種無序容器,以及與對應的關聯式容器的區別:


二、哈希表(Hash Table)

首先對于這幾個無需容器,其底層都是由哈希表實現的,所以先簡單介紹一下哈希表:

哈希表是一種高效的數據結構,它通過哈希函數將鍵(key)映射到表中一個位置來訪問記錄,以實現快速的插入、刪除和查找操作。

1. 基本概念

核心組成

  1. 鍵(Key):用于查找的標識符
  2. 值(Value):與鍵關聯的數據
  3. 哈希函數(Hash Function):將鍵轉換為數組索引的函數
  4. 數組(桶數組):存儲數據的底層結構
  5. 沖突解決機制:處理多個鍵映射到同一索引的情況

工作原理

  1. 當插入鍵值對時,使用哈希函數計算鍵的哈希值
  2. 將哈希值轉換為數組索引(通常通過取模運算)
  3. 在該索引位置存儲值(或包含鍵值對的節點)

2. 哈希函數

好的哈希函數應具備以下特性:

  • 確定性:相同鍵總是產生相同哈希值
  • 均勻分布:鍵應均勻分布在哈希表中
  • 高效計算:計算速度快

常見哈希函數:

  • 除留余數法:h(k) = k mod m
  • 乘法哈希:h(k) = floor(m * (k*A mod 1)),其中0 < A < 1
  • 針對字符串的哈希函數(如DJB2、FNV等)

3. 沖突解決方法

鏈地址法(Separate Chaining)

  • 每個桶是一個鏈表(或其他數據結構)
  • 沖突元素添加到鏈表末尾
  • 查找時需要遍歷鏈表

開放尋址法(Open Addressing)

  • 所有元素都存放在數組本身中
  • 當沖突發生時,按照某種探測序列尋找下一個空槽
  • 常見探測方法:
    • 線性探測:h(k, i) = (h'(k) + i) mod m
    • 二次探測:h(k, i) = (h'(k) + c?i + c?i2) mod m
    • 雙重哈希:h(k, i) = (h?(k) + i*h?(k)) mod m

4. 性能分析

  • 平均情況
    • 插入、刪除、查找:O(1)
  • 最壞情況
    • 所有鍵映射到同一槽:O(n)

性能取決于:

  1. 哈希函數的質量
  2. 沖突解決方法
  3. 負載因子(load factor):元素數量/表大小

5. 動態擴容

當負載因子超過閾值(通常0.7-0.8)時:

  1. 創建更大的新數組(通常是原大小的2倍左右)
  2. 重新計算所有元素的哈希值并插入新數組
  3. 釋放舊數組空間

6. 應用場景

  • 字典/關聯數組實現
  • 數據庫索引
  • 緩存系統(如Redis)
  • 對象唯一性檢查
  • 頻率統計
  • 快速查找需求場景

7. 優缺點

優點

  • 平均情況下常數時間的操作
  • 靈活的大小(可動態擴容)
  • 適合大數據量處理

缺點

  • 最壞情況下性能較差
  • 哈希函數設計影響性能
  • 不支持有序遍歷(除非使用特殊實現)
  • 內存使用可能不如某些數據結構緊湊

二. 無序容器的介紹

1. unordered_set

unordered_set 是一個無序集合,類似于 set,但它不保證元素的順序。它的每個元素都是唯一的,且基于哈希表進行組織和查找。

特點:

  • 元素唯一,不允許重復。
  • 基于哈希表存儲,查找、插入、刪除的平均時間復雜度為 O(1)。
  • 不保證元素的順序。

示例代碼

#include <iostream>
#include <unordered_set>int main() {// 創建一個 unordered_setstd::unordered_set<int> uset;// 插入元素uset.insert(10);uset.insert(20);uset.insert(30);uset.insert(20);  // 重復的 20,不會插入// 輸出所有元素for (const int& val : uset) {std::cout << val << " ";  // 輸出順序不確定}std::cout << std::endl;// 查找元素if (uset.find(20) != uset.end()) {std::cout << "Found 20!" << std::endl;} else {std::cout << "20 not found!" << std::endl;}// 刪除元素uset.erase(30);// 再次輸出for (const int& val : uset) {std::cout << val << " ";  // 輸出順序不確定}std::cout << std::endl;return 0;
}

輸出示例

10 20 30 
Found 20!
10 20 

2. unordered_map

unordered_map 是一個無序的鍵值對容器,類似于 map,但它不保證元素的順序。它的鍵是唯一的,每個鍵都關聯著一個值。

特點

  • 鍵唯一,值可重復。
  • 基于哈希表存儲,查找、插入、刪除的平均時間復雜度為 O(1)。
  • 不保證元素的順序。

示例代碼

#include <iostream>
#include <unordered_map>int main() {// 創建一個 unordered_mapstd::unordered_map<int, std::string> umap;// 插入鍵值對umap[1] = "one";umap[2] = "two";umap[3] = "three";// 查找元素std::cout << "Key 2: " << umap[2] << std::endl;// 輸出所有鍵值對for (const auto& pair : umap) {std::cout << pair.first << " -> " << pair.second << std::endl;}// 刪除元素umap.erase(1);// 輸出刪除后的結果std::cout << "After deleting key 1:" << std::endl;for (const auto& pair : umap) {std::cout << pair.first << " -> " << pair.second << std::endl;}return 0;
}

輸出示例

Key 2: two
3 -> three
2 -> two
1 -> one
After deleting key 1:
3 -> three
2 -> two

3. unordered_multiset

unordered_multiset 是一個無序集合,允許元素重復。它與 unordered_set 的區別在于,它允許多個相同的元素。

特點

  • 允許重復元素。
  • 基于哈希表存儲,查找、插入、刪除的平均時間復雜度為 O(1)。
  • 不保證元素的順序。

示例代碼

#include <iostream>
#include <unordered_multiset>int main() {// 創建一個 unordered_multisetstd::unordered_multiset<int> umset;// 插入元素umset.insert(10);umset.insert(20);umset.insert(20);  // 重復元素 20umset.insert(30);// 輸出所有元素for (const int& val : umset) {std::cout << val << " ";  // 輸出順序不確定}std::cout << std::endl;// 查找元素if (umset.count(20) > 0) {std::cout << "Found 20!" << std::endl;}// 刪除元素umset.erase(20);  // 刪除所有 20// 輸出刪除后的元素std::cout << "After erasing 20:" << std::endl;for (const int& val : umset) {std::cout << val << " ";  // 輸出順序不確定}std::cout << std::endl;return 0;
}

輸出示例

10 20 30 20 
Found 20!
After erasing 20:
10 30 

4. unordered_multimap

unordered_multimap 是一個無序的鍵值對容器,允許多個相同的鍵,每個鍵可以有多個值。

特點

  • 允許重復的鍵。
  • 基于哈希表存儲,查找、插入、刪除的平均時間復雜度為 O(1)。
  • 不保證元素的順序。

示例代碼

#include <iostream>
#include <unordered_multimap>int main() {// 創建一個 unordered_multimapstd::unordered_multimap<int, std::string> ummap;// 插入鍵值對ummap.insert({1, "one"});ummap.insert({2, "two"});ummap.insert({2, "second two"});  // 重復鍵ummap.insert({3, "three"});// 輸出所有鍵值對for (const auto& pair : ummap) {std::cout << pair.first << " -> " << pair.second << std::endl;}// 查找某個鍵的所有值std::cout << "Values for key 2:" << std::endl;auto range = ummap.equal_range(2);for (auto it = range.first; it != range.second; ++it) {std::cout << it->second << std::endl;}// 刪除元素ummap.erase(2);  // 刪除所有鍵為 2 的元素// 輸出刪除后的結果std::cout << "After deleting key 2:" << std::endl;for (const auto& pair : ummap) {std::cout << pair.first << " -> " << pair.second << std::endl;}return 0;
}

輸出示例

1 -> one
2 -> second two
2 -> two
3 -> three
Values for key 2:
second two
two
After deleting key 2:
1 -> one
3 -> three

5. 總結

  • unordered_set:無序的集合,元素唯一,查找、插入、刪除操作平均時間復雜度為 O(1)。
  • unordered_map:無序的鍵值對容器,鍵唯一,查找、插入、刪除操作平均時間復雜度為 O(1)。
  • unordered_multiset:無序的集合,允許重復元素,查找、插入、刪除操作平均時間復雜度為 O(1)。
  • unordered_multimap:無序的鍵值對容器,允許重復鍵,查找、插入、刪除操作平均時間復雜度為 O(1)。

無序容器非常適合需要快速查找、插入和刪除操作的場景,尤其是在不關心元素順序的情況下。它們比傳統的關聯式容器(如 setmap)具有更高的性能,尤其是在處理大量數據時。


三. 無序容器與關聯式容器的對比

C++ 標準庫中早期的關聯式容器包括 setmapmultisetmultimap,這些容器通常基于平衡二叉樹(如紅黑樹)實現,它們自動按照一定的順序排列元素。無序容器則基于哈希表實現,元素的順序并不固定。下面對比這兩類容器的關鍵區別。

1. 存儲方式

  • 關聯式容器(setmap 等):元素存儲在一個平衡二叉搜索樹中(如紅黑樹)。樹的結構確保了元素總是按一定的順序排列。
  • 無序容器(unordered_setunordered_map 等):元素存儲在哈希表中,哈希表使用哈希函數將元素分配到不同的桶(bucket)中,因此元素的存儲順序不固定。

2. 時間復雜度

  • 關聯式容器:所有基本操作(插入、刪除、查找)在最壞情況下的時間復雜度為 O(log n),因為它們基于平衡二叉樹,樹的深度是 O(log n)。
  • 無序容器:在平均情況下,所有基本操作(插入、刪除、查找)的時間復雜度為 O(1),因為哈希表提供了常數時間的查找、插入和刪除。但是在極端情況下(比如大量哈希沖突時),最壞情況下的時間復雜度可能降為 O(n),這時所有元素可能會存儲在同一個桶中,形成鏈表。

3. 元素順序

  • 關聯式容器:元素按照鍵的順序排列,通常是升序(也可以通過自定義比較器來改變排序方式)。
  • 無序容器:元素的順序是無關緊要的,哈希表中的元素沒有固定順序。

4. 適用場景

  • 關聯式容器:適用于需要元素保持順序的場景,或需要按照某種順序遍歷容器的情況。典型應用包括有序的集合、映射等。
  • 無序容器:適用于不關心元素順序的場景,尤其是需要高效查找、插入和刪除的情況。如果性能要求很高,并且順序無關,unordered_* 容器是更好的選擇。

5. 迭代器

  • 關聯式容器:由于元素有序,迭代器會按照元素的順序進行遍歷。遍歷結果是按從小到大的順序排列。
  • 無序容器:由于哈希表中元素的順序不確定,迭代器遍歷容器時,元素的順序無法預測。

6. 內存使用

  • 關聯式容器:由于需要維護平衡二叉樹,關聯式容器的內存使用相對較高。每個元素不僅存儲其值,還存儲指向父節點、左子節點和右子節點的指針。
  • 無序容器:無序容器基于哈希表實現,通常會根據桶的數量來預分配內存。哈希表可能會使用更多的內存來管理沖突的桶,但通常能保持較低的內存使用。

四、總結

C++11 引入的無序容器(unordered_setunordered_mapunordered_multisetunordered_multimap)為開發者提供了一種更高效的選擇,尤其是在對性能要求高且不關心元素順序的場景中。這些容器的查找、插入和刪除操作通常能在常數時間內完成,大大提高了程序的性能。

與傳統的關聯式容器(如 setmap)相比,無序容器具有更快的平均性能,但缺乏排序功能,因此適用場景有所不同。無序容器更適合那些不依賴元素順序、但需要快速查找、插入和刪除操作的應用,而關聯式容器則適合需要元素有序、能夠按順序遍歷的情況。

總體來說,選擇合適的容器應根據具體的需求,權衡性能、內存使用、元素順序等因素。在許多情況下,無序容器提供了一種更加高效的解決方案。

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

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

相關文章

【智能大數據分析 | 實驗二】Spark實驗:部署Spark集群

【作者主頁】Francek Chen 【專欄介紹】???智能大數據分析??? 智能大數據分析是指利用先進的技術和算法對大規模數據進行深入分析和挖掘&#xff0c;以提取有價值的信息和洞察。它結合了大數據技術、人工智能&#xff08;AI&#xff09;、機器學習&#xff08;ML&#xf…

使用pymongo進行MongoDB的回收

在 PyMongo 中使用 compact 命令進行 MongoDB 碎片回收的完整操作指南如下&#xff1a; 一、核心執行方法 from pymongo import MongoClient import time# 1. 連接到 MongoDB 實例 client MongoClient("mongodb://username:passwordhost:27017/dbname?authSourceadmin&q…

Azure DevOps 使用服務主體配置自托管代理 (Self-hosted Agent) 配置指南

Azure DevOps 使用服務主體配置自托管代理配置指南1. 概述2. 在 Azure AD 中創建服務主體 (SP)3. 授予 Azure DevOps 權限3.1. 組織層級&#xff1a;用戶身份與訪問級別3.2. 組織層級&#xff1a;Agent pools管理員3.3. 在 Linux VM 上安裝和配置代理3.4. 啟動并設置為系統服務…

Java學習第六十四部分——Nginx

目錄 一、前言提要 二、核心特點 三、核心作用 四、架構優勢 五、應用場景 六、常用命令 七、性能對比——Nginx vs Apache 八、典型用戶 九、配置示例 十、Java應用需配合的配置 十一、性能優化策略 十二、常見問題排查 十三、文件結構配置 十四、總結歸納概述 …

幾個常用的Oxygen編輯器插件

Oxygen XML Editor是羅馬尼亞的SyncroSoft公司開發的結構化文檔編輯和發布軟件。 除了Oxygen編輯器帶的功能&#xff0c;它還提供了豐富的插件來提供額外的功能來輔助資料開發人員更高效率、更低成本地開發結構化資料。 本文介紹幾個比較常用和有用的插件。 - 1 - Git Clie…

基于springboot的軟件缺陷管理跟蹤平臺

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業六年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了六年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

【LINUX】Centos 9使用nmcli更改IP

1. 查看連接名稱 nmcli connection show輸出類似&#xff1a; NAME UUID TYPE DEVICE Wired connection 1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ethernet enp1s02. 修改 IP 地址&#xff08;以靜態 IP 為例&#xf…

ConvMixer模型:純卷積為何能夠媲美Transformer架構?深入淺出原理與Pytorch代碼逐行講解實現

ConvMixer 是一個簡潔的視覺模型&#xff0c;僅使用標準的卷積層&#xff0c;達到與基于自注意力機制的視覺 Transformer&#xff08;ViT&#xff09;相似的性能&#xff0c;由此證明純卷積架構依然很強大。核心原理&#xff1a;極簡的卷積設計&#xff1a;它摒棄了復雜的自注意…

教程:如何通過代理服務在國內高效使用 Claude API 并集成到 VSCode

對于許多開發者來說&#xff0c;直接訪問 Anthropic 的 Claude API 存在網絡障礙。本文將介紹一個第三方代理服務&#xff0c;幫助你穩定、高效地利用 Claude 的強大能力&#xff0c;并將其無縫集成到你的開發工作流中。 一、服務介紹 我們使用的是 open.xiaojingai.com 這個…

從零開始:Vue 3 + TypeScript 項目創建全記錄

一次完整的現代前端項目搭建經歷,踩坑與收獲并存 ?? 前言 最近創建了一個新的 Vue 3 項目,整個過程中遇到了不少有趣的選擇和決策點。作為一個技術復盤,我想把這次經歷分享出來,希望能幫助到其他開發者,特別是那些剛接觸 Vue 3 生態的朋友們。 ??? 項目初始化:選擇…

[spring6: @EnableWebSocket]-源碼解析

注解 EnableWebSocket Retention(RetentionPolicy.RUNTIME) Target(ElementType.TYPE) Documented Import(DelegatingWebSocketConfiguration.class) public interface EnableWebSocket {}DelegatingWebSocketConfiguration Configuration(proxyBeanMethods false) public …

Nacos 封裝與 Docker 部署實踐

Nacos 封裝與 Docker 部署指南 0 準備工作 核心概念? 命名空間&#xff1a;用于隔離不同環境&#xff08;如 dev、test、prod&#xff09;或業務線&#xff0c;默認命名空間為public。? 數據 ID&#xff1a;配置集的唯一標識&#xff0c;命名規則推薦為{服務名}-{profile}.{擴…

Vue2——4

組件的樣式沖突 scoped默認情況&#xff1a;寫在組件中的樣式會 全局生效 → 因此很容易造成多個組件之間的樣式沖突問題。1. 全局樣式: 默認組件中的樣式會作用到全局2. 局部樣式: 可以給組件加上 scoped 屬性, 可以讓樣式只作用于當前組件原理&#xff1a;當前組件內標簽都被…

30天打好數模基礎-邏輯回歸講解

案例代碼實現一、代碼說明本案例針對信用卡欺詐檢測二分類問題&#xff0c;完整實現邏輯回歸的數據生成→預處理→模型訓練→評估→閾值調整→決策邊界可視化流程。數據生成&#xff1a;模擬1000條交易數據&#xff0c;其中欺詐樣本占20%&#xff08;類不平衡&#xff09;&…

CDH yarn 重啟后RM兩個備

yarn rmadmin -transitionToActive --forcemanual rm1 cd /opt/cloudera/parcels/CDH/lib/zookeeper/bin/ ./zkCli.sh -server IT-CDH-Node01:2181 查看是否存在殘留的ActiveBreadCrumb節點 ls /yarn-leader-election/yarnRM #若輸出只有[ActiveBreadCrumb]&#xff08;正常應…

HTML5音頻技術及Web Audio API深入解析

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;音頻處理在IT行業中的多媒體、游戲開發、在線教育和音樂制作等應用領域中至關重要。本文詳細探討了HTML5中的 <audio> 標簽和Web Audio API等技術&#xff0c;涉及音頻的嵌入、播放、控制以及優化。特別…

每日面試題13:垃圾回收器什么時候STW?

STW是什么&#xff1f;——深入理解JVM垃圾回收中的"Stop-The-World"在Java程序運行過程中&#xff0c;JVM會通過垃圾回收&#xff08;GC&#xff09;自動管理內存&#xff0c;釋放不再使用的對象以騰出空間。但你是否遇到過程序突然卡頓的情況&#xff1f;這可能與G…

【系統全面】常用SQL語句大全

一、基本查詢語句 查詢所有數據&#xff1a; SELECT * FROM 表名;查詢特定列&#xff1a; SELECT 列名1, 列名2 FROM 表名;條件查詢&#xff1a; SELECT * FROM 表名 WHERE 條件;模糊查詢&#xff1a; SELECT * FROM 表名 WHERE 列名 LIKE 模式%;排序查詢&#xff1a; SELECT *…

Spring之SSM整合流程詳解(Spring+SpringMVC+MyBatis)

Spring之SSM整合流程詳解-SpringSpringMVCMyBatis一、SSM整合的核心思路二、環境準備與依賴配置2.1 開發環境2.2 Maven依賴&#xff08;pom.xml&#xff09;三、整合配置文件&#xff08;核心步驟&#xff09;3.1 數據庫配置&#xff08;db.properties&#xff09;3.2 Spring核…

C++STL系列之set和map系列

前言 set和map都是關聯式容器&#xff0c;stl中樹形結構的有四種&#xff0c;set&#xff0c;map&#xff0c;multiset,multimap.本次主要是講他們的模擬實現和用法。 一、set、map、multiset、multimap set set的中文意思是集合&#xff0c;集合就說明不允許重復的元素 1……