一、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