先說結論:
map<key, value> a
,
和vector< int >b
,的預設初始化都是初始化為空的容器,而
unordered_map<key, value>c
, 則不是空的
一. 在做力扣1207題時遇到的問題
原題:
标準答案裡的unordered_map是采用的預設初始化,因為我之前一般用vector比較多,而且vector要是空的,直接下标通路會出錯,除非元素是一個一個push_back進去,否則一般都會給vector指定一個大小,并且元素初始化為0,即:
vector< int> vec(nums.size(), 0);
但是這unordered_map居然不用指定初始化時的大小:
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
unordered_map<int, int> occur;//次數統計的數組
for (const auto& x: arr) {
occur[x]++;//x就是arr的元素,成為key,occur[x]就是出現的次數,成為value
}
unordered_set<int> times;//查重數組
for (const auto& x: occur) {
times.insert(x.second);
}
return times.size() == occur.size();
}
};
這段代碼的解題邏輯就是把數組元素和對應的次數插入unordered_map數組,這樣key就是輸入數組arr的各個元素,value就是對應元素出現的次數。
然後把出現次數,即map的value插入unordered_set數組,因為set數組和unordered_set都不允許有重複元素出現(畢竟key和value是同一個元素,而除了multiset,key是唯一的),是以最後看一下unordered_set的大小和次數統計數組unordered_map大小是否一樣就行。
二. map和unordered_map初始化的差別和底層原理
2.1 map
map預設初始化直接就是一個空的map,然後用下标通路的話(map的下标運算符接受一個是以,下标就是key,是以multimap也不能用下标通路,因為其一個關鍵字可能對應多個值),如果關鍵字不在map中,會為它建立一個元素插入到map中,map底層資料結構是紅黑樹,示例如下:
map<string, size_t> word_count;//empty map
word_count["Anna"] = 1;//插入一個關鍵字為Anna的元素,關聯值進行值初始化;然後将1賦予它
該示例來自
C++ primer P387
,其實具體過程應該如下:
(1)在word_count 中搜尋關鍵字為Anna的元素,未找到;
(2)将一個新的 關鍵字-值 (key-value)對插入到word_count中,關鍵字是一個const string,儲存Anna。值value将進行初始化,在本例中因為value是整型意味着值為0
(3)提取出新插入的元素,并将值1賦予它。
也就是說,在map中通路不存在的key的鍵值對,首先是新增一個,key-預設值,然後該鍵值對再被等号指派。
2.2 unordered_map
而unordered_map則與上面map的機制完全不同。因為unordered_map底層資料結構是hash table,即哈希表,該哈希表由vector和linked-list連結清單構成,vector用于放buckets主體,有連結清單是因為STL中的hash table采用開鍊法防止哈希沖突,用于存放那些具有相同索引即沖突的元素。下圖來自
《STL源碼剖析》P253,hashtable章節
。
unordered_map上的所有操作都是直接移植hashtable這一資料結構的。
對于unordered_map<key, value>來說,key被hash function做映射(如除法取餘),然後得到相應下标,在vector中存放value,如果後面某元素經過哈希映射後得到的下标和前面一樣,那就是發生哈希沖突,是以需要在該處對外連接配接連結清單來存放這些沖突的鍵值對,這個連結清單不是list也不是slist,而是自定義的linked-list,也就是上圖的bucket list。
是以unordered_map也是動态擴容的啊,而且他并不是初始化為空!而是按照一組質數,共28個:[53,97,193,…429496729],會根據存入元素的個數N在這組質數裡選大于或等于N的數,是以這裡直接按預設初始化,裡面的vector應該大小為53,而不是0;——當然具體也不一定就是53,版本不一樣
我們使用vector容器,vector< int> m,此時m是初始化為空的,size == 0, capacity == 0。——見primer P319例子
是以hashtable設定預設初始大小,應該是為了提高效率,減少動态擴容的次數
那麼在上述預設初始化情況下,vector裡面全是0,一開始
occur[x]++;
首先哈希函數對x做映射——取模,然後vector中對應下标的值處+1,如果不同的x哈希映射後下标一樣,那就得用開鍊法,沖突的vector處就隻放置指針了,指針指向一個連結清單,連結清單裡放這兩個元素(連結清單的每個節點分别是一個指針和一個value),比如11和18都應該放在下标4處的,那下标4處就應該放一個指向連結清單的指針:
當x往後周遊,直到超出53時,就進行動态擴容了,按照那個28個數字的清單[53,97,193,…429496729]來選擇下一步擴容到多大。
我們也可以手動設定unordered_map初始大小:
這樣就不需要擴容了。
那為什麼可以隻指定一個參數呢?下圖可以看到,unordered_map構造函數的後面三個都有預設值,是以我們隻需要指定第一個參數就好了。
是以代碼最後被改成:
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
unordered_map<int, int>occur(arr.size());//指定大小
for(const auto& x : arr){
occur[x]++;
}
unordered_set<int>unique;
for(const auto& x : occur){
unique.insert(x.second);
}
return occur.size() == unique.size();
}
};