前言
前面我们对map和set的使用进行了介绍,以及对其底层(红黑树)也进行了探索和实现,并基于红黑树封装实现了迷你版的map和set!本期我们来介绍另外两个关联式的容器即unordered_map和unordered_set!
本期内容介绍
• unordered_map常用接口的介绍
• unordered_multimap和unordered_map的区别
• unordered_set常用接口的介绍
• unordered_multiset和unordered_set的区别
• 在OJ中的使用
在C++98中,STL提供了以红黑树为底层结构的树形关联式容器即map和set, 他们中的元素是有序的(走中序),他们的查询效率可以达到log(N);但是如果节点但非常多的话效率也不是很理想,因此C++11新引入了两个底层以哈希结构的关联式容器即unordered_map和unordered_set其查找效率可以到到O(1)!下面就来介绍一下这两容器:
• unordered_map常用接口的介绍
OK, 我们还是老样子,先来看看使用文档:
unordered_map和unordered_multimap的头文件都是unordered_map
为了更好的理解文档,我们有必要先搞清楚其内部的成员类型:
key_type是存储元素pair的第一个模板参数,mapped_type(它实际上就是T类型)是存储元素pair的第二个模板参数;vlaue_type就是存储的pair<K, V>类型, size_type就是size_t
OK,下面我们来看看文档:
• unordered_map是关联式容器,存储的是一个键值对<K,V>,可以快速的通过K查找!
• unordered_map存储的键值对的K和V可以是不同的!
• unordered_map存储的元素不是有序的,他是基于哈希桶(后面介绍)实现的!
• unordered_map通过K访问元素的效率比map快!
• unordered_map实现了[ ]可以直接使用K访问V!
• unordered_map的迭代器是单向的!
• unordered_map的时间复杂度是恒定的O(1)
• 构造、拷贝构造、赋值拷贝、析构函数
unordered_map<int, int> umap1;//空构造
unordered_map<string, int> umap2;//K和V可以不同
//迭代器区间构造
vector<pair<int, int>> v = { {1,1}, {2,2}, {3,3}, {4,4} };
unordered_map<int, int> umap3(v.begin(), v.end());
//拷贝构造
unordered_map<int, int> umap4(umap3);
//列表初始化初始化
unordered_map<double, string> umap5({ {9.9,"aaa"}, {6.6, "bbb"}, {1.1, "cp"} });
//赋值拷贝
unordered_map<double, string> umap6;
umap6 = umap5;//上面的umap5
//列表初始化对象拷贝构造
unordered_map<int, int> umap7;
umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };
析构函数就不介绍了,还是和以前一样:清理资源释放空间!
• 容量
• empty
判断是否为空
• size
获取当前容器中元素的个数
void Test_unordered_map2()
{
//列表初始化对象拷贝构造
unordered_map<int, int> umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };
bool ept = umap7.empty();
cout << ept << endl; // 0 -> 不为空
size_t sz = umap7.size();
cout << sz << endl; // 4
}
• 迭代器
unordered_map的迭代器是单向的迭代器!
// 验证const迭代器 --> 如果如果范围for可以这里是const对象,就说明const迭代器没问题
void Print(const unordered_map<double, string>& umap)
{
for (const auto& e : umap)
{
cout << e.first << "->" << e.second << endl;
}
cout << endl;
}
void Test_unordered_map3()
{
//列表初始化初始化
unordered_map<double, string> umap5({ {9.9,"aaa"}, {6.6, "bbb"}, {1.1, "cp"} });
//迭代器遍历
unordered_map<double, string>::iterator it = umap5.begin();
while (it != umap5.end())
{
cout << it->first << "->" << it->second << endl;
++it;
}
cout << endl;
Print(umap5);
}
• 元素访问
[ ] 和 at 都是通过K来访问V
void Test_unordered_map4()
{
unordered_map<string, int> umap;
vector<string> v = { "hehe", "haha","cpdd", "hehe", "haha", "cp", "cpdd", "yyds"};
// [] 正常访问
for (auto& s : v)
umap[s]++;
for (auto& e : umap)
cout << e.first << "->" << e.second << endl;
cout << "---------------------" << endl;
// at 正常访问
for (auto& s : v)
umap.at(s)++;
for (auto& e : umap)
cout << e.first << "->" << e.second << endl;
}
• [ ] 和 at 的区别:[ ]是暴力检查(断言),而at是"温柔"的检查(抛异常)
• 元素查找
• find
查找元素,找i到了,返回该元素的迭代器,否则返回end
• count
获取key的数量,如果key存在返回1,否则返回0
void Test_unordered_map5()
{
unordered_map<int, int> umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };
unordered_map<int, int>::iterator ret = umap7.find(3);
cout << ret->first << "->" << ret->second << endl;
size_t cnt = umap7.count(3);
cout << "count: " << cnt << endl;
}
• 元素修改
• insert
插入元素
void Test_unordered_map6()
{
unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
//插入一个pair<K,V>
umap.insert({ 6,6 });
//在hint迭代器位置插入一个pair
umap.insert(--umap.end(), { 0 ,0 });
//插入一段迭代器区间
unordered_map<int, int> umap1 = { {11,11}, {22,22}, {33,33}, {44,44} };
umap.insert(umap1.begin(), umap1.end());
//插入一个
umap.insert({ {12,12}, {23,23}, {32,32} });
}
• erase
删除元素
void Test_unordered_map7()
{
unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
//删除迭代器位置的元素
umap.erase(umap.begin());
//删除K对应的元素
umap.erase(2);
//删除一段迭代器区间
umap.erase(++umap.begin(), --umap.end());
}
• clear
清空unordered_map
void Test_unordered_map8()
{
unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
cout << umap.size() << endl;
umap.clear();
cout << umap.size() << endl;
}
• swap
两个unordered_map交换
unordered_map<int, int> umap1 = { {1,1}, {2,2}, {3,3}, {4,4} };
unordered_map<int, int> umap2 = { {10,10}, {20,20}, {30,30}, {40,40} };
umap1.swap(umap2);
剩下的这些就是得了解底层才可以看明白:
这里不在介绍了,后面介绍了底层就明白了!
• unordered_multimap和unordered_map的区别
他这里和unordered_map唯一的区别就是可以存在重复key的元素, 所以不再支持[]了!其他的接口都是一模一样的:这里就不再一一介绍了!
下面来看看,可重复的例子:
void Test_unordered_multimap()
{
unordered_map<string, int> m1;
unordered_multimap<string, int> m2;
m1.insert({ "香蕉", 5 });
m1.insert({ "苹果", 3 });
m1.insert({ "香蕉", 1 });
m1.insert({ "西瓜", 6 });
for (auto& e : m1)
cout << e.first << " : " << e.second << endl;
cout << "----------------------" << endl;
m2.insert({ "香蕉", 5 });
m2.insert({ "苹果", 3 });
m2.insert({ "香蕉", 1 });
m2.insert({ "西瓜", 6 });
for (auto& e : m2)
cout << e.first << " : " << e.second << endl;
}
OK,unordered_map就介绍道这里,下面我们来介绍一下unordered_set!
• unordered_set的介绍
OK,先来看一下内部成员类型:
OK,下面来看看文档:
• unordered_set是存储唯一元素的容器,可以通过V访问元素
• unordered_set中的元素不可修改,但是支持插入和删除的操作
• unordered_set存的元素是无序的,是基于哈希桶实现的
• unordered_set通过K访问元素比set快
• unordered_set的迭代器是单向的
• unordered_set的时间复杂度恒定为O(1)
• 构造、拷贝构造、复制拷贝和析构
void Test_unordered_set1()
{
//空构造
unordered_set<int> us1;
//迭代器区间构造
vector<string> v = { "aaa", "bbb", "ccc" };
unordered_set<string> us2(v.begin(), v.end());
//拷贝构造
unordered_set<int> us3(us1);
//列表初始化对象构造
unordered_set<int> us4 = { 1,2,3,4,5,6 };
}
//赋值拷贝
unordered_set<int> us5;
us5 = us4;//上面的us4
//列表初始化对象赋值拷贝
unordered_set<int> us6;
us6 = { 1,2,3,4,5,6 };
析构还是和以前一样,释放资源,清理空间,自动调用,不在多介绍了~!
• 容量
• empty
判断是否为空
• size
获取当前容器中的元素个数
void Test_unordered_set2()
{
//列表初始化构造
unordered_set<int> us4 = { 1,2,3,4,5,6 };
bool ept = us4.empty();
cout << "empty : " << ept << endl;
size_t sz = us4.size();
cout << "size : " << sz << endl;
}
• 迭代器
注意:unordered_set的迭代器是单向的迭代器
void Print(const unordered_set<int>& us)
{
for (const auto& e : us)//验证const 迭代器
{
cout << e << endl;
}
cout << endl;
}
void Test_unordered_set3()
{
//列表初始化构造
unordered_set<int> us4 = { 1,2,3,4,5,6 };
//普通迭代器
unordered_set<int>::iterator it = us4.begin();
while (it != us4.end())
{
cout << *it << endl;
++it;
}
cout << endl;
Print(us4);
}
• 元素查找
• find
查找元素,找到了返回该元素的迭代器,否则返回end
• count
获取key的数量,如果key存在返回1,否则返回0
void Test_unordered_set4()
{
//列表初始化构造
unordered_set<int> us4 = { 1,2,3,4,5,6 };
unordered_set<int>::iterator ret = us4.find(1);
cout << *ret << endl;
size_t cnt = us4.count(1);
cout << cnt << endl;
}
• 修改
• insert
OK,上面unordered_map只是给出了例子没有验证,这里顺便验证一下:
void Print(const unordered_set<int>& us)
{
for (const auto& e : us)
{
cout << e << " ";
}
cout << endl;
}
void Test_unordered_set5()
{
//列表初始化构造
unordered_set<int> us4 = { 1,2,3,4,5,6 };
Print(us4);
cout << "-----------------------" << endl;
//插入一个val -> 插入 0
us4.insert(0);
Print(us4);
cout << "-----------------------" << endl;
//在迭代器位置插入一个val,再--end位置插入66
us4.insert(--us4.end(), 66);
Print(us4);
cout << "-----------------------" << endl;
//插入一段迭代器区间
vector<int> nums = { 11,22,33 };
us4.insert(nums.begin(), nums.end());
Print(us4);
cout << "-----------------------" << endl;
//插入一个列表初始化对象
us4.insert({ 99,88,77 });
Print(us4);
}
这里你可能想为什么我在--end插入的是66,结果在第二个位置呢?首先一开始我们就说过unordered_map个unordered_set是无序的,具体为什么下一期介绍了底层的哈希表就知道了!
• erase
void Test_unordered_set6()
{
//列表初始化构造
unordered_set<int> us4 = { 1,2,3,4,5,6, 7, 8, 9,10,11 };
Print(us4);
cout << "-----------------------" << endl;
//删除某个迭代器位置的元素
us4.erase(++us4.begin());
Print(us4);
cout << "-----------------------" << endl;
//删除某个K对应的元素
us4.erase(4);
Print(us4);
cout << "-----------------------" << endl;
//删除一段迭代器区间
us4.erase(++us4.begin(), --us4.end());
Print(us4);
}
• clear
清空当前容器
• swap
交换两个容器的内容
void Test_unordered_set7()
{
//列表初始化构造
unordered_set<int> us1 = { 1,2,3,4,5,6, 7, 8, 9,10,11 };
unordered_set<int> us2 = {10,20,30 };
Print(us1);
cout << "-----------------------" << endl;
us1.clear();
cout << endl;
cout << "--------- 交换前:---------" << endl;
Print(us1);
Print(us2);
us1.swap(us2);
cout << "--------- 交换后:---------" << endl;
Print(us1);
Print(us2);
}
OK,unorsered_set的常用接口就这些,剩下的就是底层的了,得了解底层才可以理解!这里不再介绍了,等下一期介绍了底层就明白了!
• unordered_multiset和unordered_set的区别
unordered_multiset和unordered_set的唯一区别就是允许重复元的存在!
OK,这里不在一一介绍每个接口的使用了,和unordered_set的一样!这里还是演示一下可以存在重复元素的例子:
void print(const unordered_multiset<int>& us)
{
for (const auto& e : us)
{
cout << e << " ";
}
cout << endl;
}
void Test_unordered_set8()
{
//列表初始化构造
unordered_multiset<int> us1 = { 1,2,3,4,5,6,7,4,2,1 };
print(us1);
}
• 正OJ中的使用
思路:先将数组中的元素放到unordered_map中,然后再次遍历数组,让每个元素去查找unordered_map该元素的次数是n(数组长度的一半)就返回!最后没找到就返回-1
class Solution
{
public:
int repeatedNTimes(vector<int>& nums)
{
int n = nums.size() / 2;
unordered_map<int, int> m;
for(auto& e : nums)
m[e]++;
for(auto& e : nums)
if(n == m[e])
return e;
return -1;//取值范围是>=0的,所以走到这里是没有找到
}
};
思路:将一个数组先放到unordered_set中,然后遍历另一个数组,如果元素存在将该元素添加到返回数组,然后将哈希表中的元素删除,最后返回结果数组即可
class Solution
{
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//返回数组
vector<int> ret;
//将一个数组放到unordered_set中
unordered_set<int> s(nums1.begin(), nums1.end());
//遍历另一个数组,如果该元素存在就是交集元素
for(auto& e : nums2)
{
if(s.count(e))
{
ret.push_back(e);
s.erase(e);//将交集元素,添加到返回数组中后就再哈希表中删除
}
}
//返回
return ret;
}
};
这就是哈希表再OJ中的用法!下面这几道题,可以自己尝试一下:
OK,本期内容就到这里,好兄弟,我们下期再见~!
结束语:我们风雨兼程绝不空手而归!