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/zh-tw/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

發表回復

登錄後才能評論