map 學習(下)——C++ 中的 hash_map, unordered_map
一、hash_map
參考《C++ STL中哈希表 hash_map介紹》即可。博主寫的很詳細。
注:
hash_map 不是標準的。筆者寫該文檔時本來想嘗試些一個 hash_map 例程,但發現自己用 Qt + MSVC2010 編譯器出現了編譯錯誤。網上原因好像說是 STL 加入標準C++之時,hash_map系列當時還沒有完全實現,所以很多平臺上雖然安裝了 g++ 編譯器,但不一定有 hash_map 的實現。所以如果有平臺移植的內容,儘量少用 hash_map。
二、unordered_map
以下內容翻譯自《unordered_map - C++ Reference》。
1. 原型
template < class Key, // unordered_map::key_type
class T, // unordered_map::mapped_type
class Hash = hash<Key>, // unordered_map::hasher
class Pred = equal_to<Key>, // unordered_map::key_equal
class Alloc = allocator< pair<const Key,T> > // unordered_map::allocator_type
> class unordered_map;
2. 說明
unordered_map 是一種關聯容器,用於存儲由關鍵值 (Key Value,以下稱爲Key 值) 和映射值 (Mapped Value,以下稱爲映射值) 組成的元素,並且允許根據其 Key 值快速檢索各個元素。
在 unordered_map 容器中,Key 值通常用來唯一標識元素,映射值是與該 Key 值關聯內容的對象。Key 值與映射值的類型可能不同。
在 unordered_map 內部,元素沒有按照其 Key 值與映射值的任何順序進行排序 ,而是根據它們的 Hash 值組織成桶,允許它們通過其 Key 值直接快速訪問單個元素(通常具有常數等級的平均時間複雜度)。
unordered_map 容器與 map 容器相比,通過 Key 值訪問各個元素的速度更快,然而通過其元素子集進行範圍迭代的效率通常較低。
unordered_map 實現了直接訪問操作符 (operator[]),它允許使用 Key 值作爲輸入參數,直接訪問映射值。
容器中的迭代器至少是前向迭代器。
3. 容器屬性
- 關聯性
- 關聯容器中的元素的參考地址指的是其 Key 值,而不是他們在容器中的絕對地址;
- 無序性
- 無序容器使用 Hash 表來組織元素,這些 Hash 表允許無序容器通過 Key 值快速訪問元素;
- 映射
- 每個元素將一個 Key 值與映射值關聯起來,Key 值用於標識其主要內容是映射值的元素;
- 唯一關鍵值
- 容器中不存在同時擁有相同 Key 值的兩個元素;
- 分配器感知
- map 容器使用分配器對象動態處理其存儲需求。
4. 模板參數
- Key
- Key 值的類型。在 unordered_map 中的每個元素都是由其 Key 值唯一指定的。
- 別名爲成員類型 unordered_map::key_type
- T
- 映射值的類型。在 unordered_map 中的每個元素,都存儲了一些數據作爲其映射值。
- 別名爲成員類型 unordered_map::mapped_type(注:不同於 unordered_map::value_type,詳細見下面)
- Hash
- 一個一元函數對象類型,它將與爲 Key 值同類型的對象作爲參數,並以此爲基礎返回類型爲 size_t 的唯一值。它可以使實現函數調用符的類,或是指向函數的指針(具體請詳細參閱示例的構造函數)。它的默認值是
hash <Key>
,它返回一個碰撞概率接近於1.0/std::numeric_limits<sizet>::max() 的 Hash 值。 - unordered_map 對象使用該函數返回的散列值,並在內部組織元素,加速了定位各個元素的過程。
- 別名爲成員類型 unordered_map::hasher
- 一個一元函數對象類型,它將與爲 Key 值同類型的對象作爲參數,並以此爲基礎返回類型爲 size_t 的唯一值。它可以使實現函數調用符的類,或是指向函數的指針(具體請詳細參閱示例的構造函數)。它的默認值是
- Pred
- 一個二元值,它接受兩個 Key 類型的參數,並返回一個布爾值。表達式 pred(a, b) 中,pred 是該類型的對象,a, b 是 Key 值,如果 a 被認爲與 b 等價,則返回 true。它可以使實現了函數調用運算符的類,或者指向函數的指針(具體請詳細參閱示例的構造函數)。它的默認值是
equal_to <Key>
,它返回與等號運算符 operator(a==b) 相同的值。 - unordered_map 對象使用該表達式,來確定兩個元素的 Key 值是否等價。在 unordered_map 容器中,沒有任何兩個元素可以使用該斷定產生 true 值(原句:No two elements in an unordered_map container can have keys that yield true using this predicate. ,也許翻譯的不對)。
- 別名爲成員類型 unordered_map::key_equal
- 一個二元值,它接受兩個 Key 類型的參數,並返回一個布爾值。表達式 pred(a, b) 中,pred 是該類型的對象,a, b 是 Key 值,如果 a 被認爲與 b 等價,則返回 true。它可以使實現了函數調用運算符的類,或者指向函數的指針(具體請詳細參閱示例的構造函數)。它的默認值是
- Alloc(通常使用默認值)
- 用於定義存儲分配模型的分類器對象的類型。默認情況下,使用分配器類模板,它定義了最簡單的內存分配模型,並且與值無關。
- 別名爲成員類型 unordered_map::allocator_type
在 unordered_map 成員函數的參考中,模板函數假定了相同的名稱:Key, T, Hash, Pred, Alloc。
unordered_map 容器元素的迭代器可以訪問 Key 值與映射值。爲此 unordered_map 定義了一個對應的類 value_type,它的第一個值對應於 Key 值類型的常量版本,第二個值對應於映射值(即模板參數 T):
typedef pair<const Key, T> value_type;
unordered_map 容器的迭代器指向該 value_type 的元素。因此對於一個調用 value_type 的迭代器而言,迭代器指向 unordered_map 的一個元素,它的 Key 值與映射值可以分別用下面的方式進行訪問:
unordered_map<Key,T>::iterator it;
(*it).first; // the key value (of type Key)
(*it).second; // the mapped value (of type T)
(*it); // the "element value" (of type pair<const Key,T>)
當然可以用任何其他的直接訪問運算符,如 -> 或 []。例程如下:
it->first; // same as (*it).first (the key value)
it->second; // same as (*it).second (the mapped value)
5. 常用函數
(1) bucket
以下內容譯自《unordered_map::bucket - C++ Reference》
原型
size_type bucket ( const key_type& k ) const;
說明
定位元素所在的桶,返回 Key 值爲輸入參數 k 的元素的所在桶號。
桶是容器內部 Hash 表中的一個槽,槽中的元素根據 Key 值分配元素。桶號的編號從 0 到 (bucket_count - 1)。
桶中單個元素可以通過 unordered_map::begin 和 unordered_map::end 返回的範圍迭代器進行訪問。
例程
下面例程片段摘自後面的程序示例:
for (auto& x : mymap3) {
std::cout << "Element [" << x.first << ":" << x.second << "]";
// 返回元素所在桶號
std::cout << " is in bucket #" << mymap3.bucket(x.first) << std::endl;
}
(2) count
以下內容譯自《unordered_map::count - C++ Reference》
原型
size_type count ( const key_type& k ) const;
說明
使用給定的 Key 值計算元素。
搜索容器中 Key 值爲輸入參數 k 的元素,並返回找到元素的數量。由於 unordered_map 容器不允許存在重複的 Key 值,這說明如果容器中存在具有該 Key 值的元素,則該函數返回 1,否則返回 0。
(3) 其他
其他操作函數基本和 map 相同:
- clear
- 清除 map 中所有元素;
- erase
- 刪除 map 中指定位置的元素;
- insert
- 在 map 指定位置添加 pair 類型的元素;
- find
- 獲取 map 中元素的迭代器;
- begin, end
- map 的正向迭代器的起始位置與終點位置;
6. 示例
(1) 示例 1
以下示例從《C++11中std::unordered_map的使用》挑選,並加以註釋說明。
#include <iostream>
#include <string>
#include <unordered_map>
// reference: http://www.cplusplus.com/reference/unordered_map/unordered_map/at/
typedef std::unordered_map<std::string, std::string> stringmap;
// 將 a, b 融合爲一個 unordered_map
stringmap merge(stringmap a, stringmap b) {
// unordered_map 複製構造函數
stringmap temp(a);
// 範圍插入,將 b 全部插入進 a 中
temp.insert(b.begin(), b.end());
return temp;
}
int main()
{
//============================
// 1. unordered_map 元素計算與基礎遍歷
//============================
// 定義第一個 unordered_map
std::unordered_map<std::string, int> mymap = { { "Mars", 3000 }, { "Saturn", 60000 }, { "Jupiter", 70000 } };
// 對元素進行計算
mymap.at("Mars") = 3396;
mymap.at("Saturn") += 272;
mymap.at("Jupiter") = mymap.at("Saturn") + 9638;
// auto:自動判斷類型
// 基於範圍的 for 循環,遍歷 mymap
for (auto& x : mymap) {
std::cout << x.first << ": " << x.second << std::endl;
}
std::cout << "mymap.size() is " << mymap.size() << std::endl << std::endl;
//============================
// 2. iterator, 迭代器遍歷
//============================
// 定義第二個 unordered_map
std::unordered_map<std::string, std::string> mymap2 = { { "Australia", "Canberra" }, { "U.S.", "Washington" }, { "France", "Paris" } };
std::cout << "mymap2 contains:" << std::endl;
// 遍歷 mymap2
for (auto it = mymap2.begin(); it != mymap2.end(); ++it)
std::cout << " " << it->first << ":" << it->second << std::endl;
std::cout << std::endl;
// mymap2 分配的各桶中的元素
std::cout << "mymap2's buckets contain:\n";
for (unsigned i = 0; i < mymap2.bucket_count(); ++i) {
std::cout << "bucket #" << i << " contains:";
for (auto local_it = mymap2.begin(i); local_it != mymap2.end(i); ++local_it)
std::cout << " " << local_it->first << ":" << local_it->second;
std::cout << std::endl;
}
//============================
// 3. bucker, 桶操作
//============================
// 定義第三個 unordered_map
std::unordered_map<std::string, std::string> mymap3 = {
{ "us", "United States" },
{ "uk", "United Kingdom" },
{ "fr", "France" },
{ "de", "Germany" }
};
// 遍歷 mymap3
for (auto& x : mymap3) {
std::cout << "Element [" << x.first << ":" << x.second << "]";
// 返回元素所在桶號
std::cout << " is in bucket #" << mymap3.bucket(x.first) << std::endl;
}
//============================
// 4. count ,判斷元素是否在容器中
//============================
// 定義第四個 unordered_map
std::unordered_map<std::string, double> mymap4 = {
{ "Burger", 2.99 },
{ "Fries", 1.99 },
{ "Soda", 1.50 } };
// 遍歷 mymap4
for (auto& x : { "Burger", "Pizza", "Salad", "Soda" })
{
// 判斷 x 是否在容器中
if (mymap4.count(x)>0)
std::cout << "mymap4 has " << x << std::endl;
else
std::cout << "mymap4 has no " << x << std::endl;
}
//============================
// 5. erase ,刪除操作
//============================
// 定義第五個 unordered_map
std::unordered_map<std::string, std::string> mymap5;
mymap5["U.S."] = "Washington";
mymap5["U.K."] = "London";
mymap5["France"] = "Paris";
mymap5["Russia"] = "Moscow";
mymap5["China"] = "Beijing";
mymap5["Germany"] = "Berlin";
mymap5["Japan"] = "Tokyo";
// 通過迭代器刪除
mymap5.erase(mymap5.begin());
// 通過 Key 值刪除
mymap5.erase("France");
// 通過迭代器範圍刪除
mymap5.erase(mymap5.find("China"), mymap5.end());
// 基於範圍的 for 循環,遍歷展示刪除後的 mymap
for (auto& x : mymap5)
std::cout << x.first << ": " << x.second << std::endl;
//============================
// 6. find ,搜索操作
//============================
// 定義第六個 unordered_map
std::unordered_map<std::string, double> mymap6 = {
{ "mom", 5.4 },
{ "dad", 6.1 },
{ "bro", 5.9 } };
std::string input;
std::cout << "who? ";
// 輸入 mom, dad, bro 中的一個,否則搜索失敗返回 Not Found
getline(std::cin, input);
// 根據輸入參數 Key 值進行搜索,返回一個迭代器
std::unordered_map<std::string, double>::const_iterator got = mymap6.find(input);
// find 返回值若爲 unordered_map 的尾部,則沒有在容器中找到
if (got == mymap6.end())
std::cout << "not found";
else
std::cout << got->first << " is " << got->second;
std::cout << std::endl;
//============================
// 6. insert ,插入操作
//============================
// 定義第七、八個 unordered_map
std::unordered_map<std::string, double>
myrecipe,
mypantry = { { "milk", 2.0 }, { "flour", 1.5 } };
// 定義插入元素,類型爲 pair 的對象
std::pair<std::string, double> myshopping("baking powder", 0.3);
// 複製插入
myrecipe.insert(myshopping);
// 移動插入
myrecipe.insert(std::make_pair<std::string, double>("eggs", 6.0));
// 範圍插入
myrecipe.insert(mypantry.begin(), mypantry.end()); // range insertion
// 初始化列表插入
myrecipe.insert({ { "sugar", 0.8 }, { "salt", 0.1 } }); // initializer list insertion
std::cout << "myrecipe contains:" << std::endl;
for (auto& x : myrecipe)
std::cout << x.first << ": " << x.second << std::endl;
std::cout << std::endl;
//============================
// 7. 等於運算符 = 操作
//============================
// 初始化列表
stringmap first = { { "AAPL", "Apple" }, { "MSFT", "Microsoft" } };
stringmap second = { { "GOOG", "Google" }, { "ORCL", "Oracle" } };
// 移動
stringmap third = merge(first, second);
// 複製
first = third;
std::cout << "first contains:";
for (auto& elem : first) std::cout << " " << elem.first << ":" << elem.second;
std::cout << std::endl;
return 0;
}
(2) 示例 2
摘選自 Leetcode 問題 Two Sum:給出一個整數數組,返回兩個數的下標值,令其和等於一個指定的目標值。
例:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
有解如下:
#include <unordered_map>
class Solution {
public:
vector<int> twoSum(vector<int> &numbers, int target)
{
//Key is the number and value is its index in the vector.
unordered_map<int, int> hash;
vector<int> result;
for (int i = 0; i < numbers.size(); i++) {
int numberToFind = target - numbers[i];
//if numberToFind is found in map, return them
if (hash.find(numberToFind) != hash.end()) {
result.push_back(hash[numberToFind]);
result.push_back(i);
return result;
}
//number was not found. Put it in the map.
hash[numbers[i]] = i;
}
return result;
}
};
算法本身遍歷一次,花費了 O(n) 的時間複雜度,遍歷過程中的 find() 方法本身花費 O(log n),所以該算法總時間複雜度爲 O(nlog n)。
三、map, hash_map, unordered_map 的區別
參考網址:
《c++中map與unordered_map的區別》
《C++中map和hash_map的區別》
1. 頭文件
- map:
#include <map>
- hash_map:
#include <hash_map>
- unordered_map:
#include <unordered_map>
2. 內部實現機理
- map:
- map 內部實現了一個紅黑樹,該結構具有自動排序的功能,因此map內部的所有元素都是有序的,紅黑樹的每一個節點都代表着map的一個元素,因此,對於map進行的查找,刪除,添加等一系列的操作都相當於是對紅黑樹進行這樣的操作,故紅黑樹的效率決定了map的效率,map只需要提供比較函數(一般爲小於函數)即可完成比較;
- hash_map:
- hash_map 需要提供 hash 函數,以及等於函數;
- unordered_map:
- unordered_map 內部實現了一個 Hash 表,所以其元素的排列順序是雜亂無序的。
3. 優缺點
- map:
- 優點:
- 有序性:這是map結構最大的優點,其元素的有序性在很多應用中都會簡化很多的操作;
- 紅黑樹,內部實現一個紅黑書使得 map 的很多操作在 log n 的時間複雜度下就可以實現,因此效率非常的高;
- 缺點:
- 空間佔用率高,因爲 map 內部實現了紅黑樹,雖然提高了運行效率,但是因爲每一個節點都需要額外保存父節點,子節點以及紅/黑性質,使得每一個節點都佔用大量的空間;
- 適用於具有順序要求的問題;
- 優點:
- hash_map:
- 優點:
- hash_map 查找速度會比map快,而且查找速度基本和數據量大小無關,屬於常數級別(但不能說一定比 map 的 log n 級別快,因爲 hash 函數本身也有耗時);
- 缺點:
- 空間佔用多,如果對內存使用很嚴格,需要認真考慮是否使用 hash_map ;特別是當 hash_map 對象特別多時,更加難以控制;
- 適用於對效率要求較高的環境;
- 優點:
- unordered_map:
- 優點:
- 內部實現了 Hash 表,所以查找速度很快;
- 缺點:
- Hash 表的建立比較比較費時;
- 適用於查找問題;
- 優點: