C++ unordered_map实现原理及应用案例详解

一、unordered_map简介

C++ STL中的unordered_map是基于哈希表(hash table)实现的关联容器。哈希表是一种将key-value映射存储的数据结构,它允许常数时间内进行插入、删除和查找操作。

unordered_map的每个元素包含一个key和value,元素之间没有顺序关系。因此,在进行查找操作时,需要通过哈希函数找到对应的桶,对桶中的元素进行搜索。因此,unordered_map的查找、插入、删除操作的时间复杂度都是O(1),相对于普通的红黑树实现的map,高效得多。

以下是unordered_map的常用操作:

    // 声明一个unordered_map
    std::unordered_map<int, std::string> umap;
    
    // 插入key-value
    umap.insert(std::make_pair(1, "value1"));
    umap.insert({2, "value2"});
    
    // 查找元素
    if (umap.find(1) != umap.end()) {
        std::cout << "Found element with key 1" << std::endl;
    } 
    
    // 修改元素
    umap[1] = "new value1";
    
    // 删除元素
    umap.erase(2);
    
    // 遍历unordered_map
    for (const auto& kv : umap) {
        std::cout << kv.first << ": " << kv.second << std::endl;
    }

二、unordered_map的内部实现原理

unordered_map的内部实现主要由哈希函数、哈希表、桶和节点组成。哈希函数用于将key映射到对应的桶中,每个桶保存对应的节点。节点包含key、value以及指向下一个节点的指针。

哈希函数的设计非常重要,它的优秀程度将直接影响unordered_map插入、查找、删除等操作的效率。常见的哈希函数有除留余数法、乘法散列法和随机旋转哈希等。具体实现中,STL使用除留余数法(%运算符)作为哈希函数的默认实现。下面是一个例子:

    std::unordered_map<std::string, int> umap(8);
    std::hash<std::string> hasher;  // 哈希函数
    std::size_t hash_val = hasher("hello world");
    std::size_t bucket_num = hash_val % umap.bucket_count(); // 计算桶的索引
    umap.insert(std::make_pair("hello world", 42));

插入元素时,将先使用哈希函数计算key的哈希值,然后将哈希值对桶的数量取模得到桶的索引。桶的数量最好选择一个质数,可以减少哈希冲突的概率。

如果桶中已经存在一个节点,这种情况称为哈希冲突。STL中使用链式法(chaining)解决哈希冲突,即将节点插入到桶对应链表的末尾。当节点数量太大时,桶中的链表可能会退化成一条很长的链表,时间复杂度可能退化到O(n)。

三、unordered_map的应用案例

unordered_map在实际应用中非常常用,尤其在需要高效的查找和插入操作时大受欢迎。下面是unordered_map的几个应用案例:

1、单词统计、列表去重

    std::unordered_map<std::string, int> word_count;
    std::vector<std::string> words = { "hello", "world", "hello", "foo", "bar", "foo" };
    for (const auto& w : words) {
        ++word_count[w];
    }
    
    for (const auto& kv : word_count) {
        std::cout << kv.first << ": " << kv.second << std::endl;
    }
    
    std::unordered_set<std::string> word_set(words.begin(), words.end());
    for (const auto& w : word_set) {
        std::cout << w << std::endl;
    }

以上代码可以统计输入字符串中每个单词出现的次数,并去重输出。

2、图的邻接表表示

unordered_map可以用于实现图的邻接表(adjacency list)表示法,即用一个unordered_map保存每个节点以及与之相邻的节点集合。下面是一个例子:

    std::unordered_map<int, std::vector<int>> adj_list;
    adj_list[0] = { 1, 2 };
    adj_list[1] = { 0, 2, 3 };
    adj_list[2] = { 0, 1, 3 };
    adj_list[3] = { 1, 2 };

以上代码可以实现一个无向图,每个节点都与若干个相邻节点相连。

3、LRU缓存

unordered_map可以用于实现LRU(Least Recently Used)缓存,即缓存数据时,如果缓存空间满了,则替换最近最少被使用的数据。可以使用两个数据结构:unordered_map和双向链表。unordered_map用于实现key到节点的映射,双向链表用于维护节点的顺序。

使用unordered_map保存key与链表中的节点的映射,使用双向链表维护节点顺序,链表中的元素按照访问时间从旧到新排序。以下是LRU缓存的具体实现:

class LRUCache {
public:
    LRUCache(int capacity) : capacity_(capacity) {}
    
    int get(int key) {
        auto it = cache_.find(key);
        if (it == cache_.end()) {
            return -1;
        }
        touch(it->second);
        return it->second.value;
    }
    
    void put(int key, int value) {
        auto it = cache_.find(key);
        if (it != cache_.end()) {
            touch(it->second);
            it->second.value = value;
        } else {
            if (cache_.size() == capacity_) {
                evict();
            }
            Node* new_node = new Node(key, value);
            cache_[key] = *new_node;
            add(new_node);
        }
    }
    
private:
    struct Node {
        int key;
        int value;
        Node* prev;
        Node* next;
        Node(int key, int value) : key(key), value(value), prev(nullptr), next(nullptr) {}
    };
    
    void touch(Node& node) {
        // 将node从链表中删除
        node.prev->next = node.next;
        node.next->prev = node.prev;
        // 将node加入链表尾部
        add(&node);
    }
    
    void evict() {
        // 删除链表头部元素
        Node* removed = head_.next;
        head_.next = head_.next->next;
        head_.next->prev = &head_;
        cache_.erase(removed->key);
        delete removed;
    }
    
    void add(Node* node) {
        // 将node加入链表尾部
        node->prev = tail_.prev;
        node->next = &tail_;
        tail_.prev->next = node;
        tail_.prev = node;
    }
    
    int capacity_;
    std::unordered_map<int, Node> cache_;
    Node head_; // 哨兵节点,链表头部的前一个节点
    Node tail_; // 哨兵节点,链表尾部的后一个节点
};

以上代码实现了一个带有get和put操作的LRU缓存,可以用于缓存key-value对,保持访问时间越新的元素越靠近链表尾部,访问时间越旧的元素越靠近链表头部。如果缓存空间满了,则将访问时间最早的元素删除。

原创文章,作者:GAWOH,如若转载,请注明出处:https://www.506064.com/n/318151.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
GAWOH的头像GAWOH
上一篇 2025-01-11 16:28
下一篇 2025-01-11 16:28

相关推荐

  • Python数据统计案例的实现

    Python作为一个高级编程语言,拥有着丰富的数据处理库和工具,能够快速、高效地进行各类数据处理和分析。本文将结合实例,从多个方面详细阐述Python数据统计的实现。 一、数据读取…

    编程 2025-04-29
  • Harris角点检测算法原理与实现

    本文将从多个方面对Harris角点检测算法进行详细的阐述,包括算法原理、实现步骤、代码实现等。 一、Harris角点检测算法原理 Harris角点检测算法是一种经典的计算机视觉算法…

    编程 2025-04-29
  • 瘦脸算法 Python 原理与实现

    本文将从多个方面详细阐述瘦脸算法 Python 实现的原理和方法,包括该算法的意义、流程、代码实现、优化等内容。 一、算法意义 随着科技的发展,瘦脸算法已经成为了人们修图中不可缺少…

    编程 2025-04-29
  • 神经网络BP算法原理

    本文将从多个方面对神经网络BP算法原理进行详细阐述,并给出完整的代码示例。 一、BP算法简介 BP算法是一种常用的神经网络训练算法,其全称为反向传播算法。BP算法的基本思想是通过正…

    编程 2025-04-29
  • yarn npm 仓库用法介绍及使用案例

    本文将从多个方面对yarn npm仓库进行详细阐述,并为你提供一些实际使用案例。 一、npm和yarn的比较 npm和yarn都是JavaScript的包管理工具。npm在Java…

    编程 2025-04-27
  • GloVe词向量:从原理到应用

    本文将从多个方面对GloVe词向量进行详细的阐述,包括其原理、优缺点、应用以及代码实现。如果你对词向量感兴趣,那么这篇文章将会是一次很好的学习体验。 一、原理 GloVe(Glob…

    编程 2025-04-27
  • 编译原理语法分析思维导图

    本文将从以下几个方面详细阐述编译原理语法分析思维导图: 一、语法分析介绍 1.1 语法分析的定义 语法分析是编译器中将输入的字符流转换成抽象语法树的一个过程。该过程的目的是确保输入…

    编程 2025-04-27
  • Python财务分析案例

    本文将以一个具体的案例为例,介绍如何使用Python进行财务分析。本文将从多个方面进行阐述。 一、数据收集和清洗 数据收集和清洗是财务分析的第一步。我们需要从不同数据源中收集数据,…

    编程 2025-04-27
  • Python项目案例:人脸识别

    人脸识别是指通过计算机对人脸图像进行分析,识别出人脸上的一些信息,如人脸的位置、大小、姿态、形状、以及其中的眼睛、鼻子、嘴巴等细节,对身份的识别具有重要的应用价值。 一、准备工作 …

    编程 2025-04-27
  • 神经网络代码详解

    神经网络作为一种人工智能技术,被广泛应用于语音识别、图像识别、自然语言处理等领域。而神经网络的模型编写,离不开代码。本文将从多个方面详细阐述神经网络模型编写的代码技术。 一、神经网…

    编程 2025-04-25

发表回复

登录后才能评论