map、unordered_map和vector的初始化和底层机理的差别

先说结论: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<int, int>occur(arr.size());//注意千万不能像vector手动初始化那样写成(size(), 0),这样key都是0了,而unordered_map中key是不能出现相同的

这样就不需要扩容了。

那为什么可以只指定一个参数呢?下图可以看到,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();
    }
};
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值