在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。
其定义方式如下:
void test_unordered_map1()
{
// 构造一个空的key为int,value为double的unordered_map
unordered_map<int, double> um1;
// 给um1赋上值
um1.insert(make_pair(1, 1.1));
um1.insert(make_pair(2, 2.2));
um1.insert(make_pair(3, 3.3));
um1.insert(make_pair(4, 4.4));
// 拷贝构造
unordered_map<int, double> um2(um1);
// 迭代器区间拷贝um2的一段
unordered_map<int, double> um3(um2.begin(), um2.end());
// for循环打印一下um3,um3没问题则um1和um2都没问题
for (auto& e : um3)
{
cout << e.first<< "=>" << e.second << " ";
}
cout << endl;
}
成员函数 | 功能 |
---|---|
insert | 插入键值对 |
erase | 删除指定key的值的键值对 |
size | 获取容器中元素的个数 |
find | 查找指定key值的键值对 |
empty | 判断容器是否为空 |
clear | 清空当前容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定key值的元素的个数 |
[] | 运算符重载的[]功能很强大,有插,改、找等功能 |
begin() | 获取容器中第一个元素的正向迭代器 |
end() | 获取容器中最后一个元素的下一个元素的正向迭代器的 |
重点讲一下[]:
1、若当前容器中已经存在着键值为key的键值对,则返回该键值对value的引用。
2、若当前容器中没有键值为key的键值对,则先插入键值对
下面直接看代码,关于上述所有的代码操作:
void test_unordered_map2()
{
// 构造一个空的key为int,value为string的unordered_map
unordered_map<int, string> um1;
// 插入方法一:构造匿名对象插入
um1.insert(pair<int, string>(1, "111"));
um1.insert(pair<int, string>(2, "222"));
um1.insert(pair<int, string>(3, "333"));
// 插入方法二:调用make_pair插入
um1.insert(make_pair(4, "444"));
um1.insert(make_pair(5, "555"));
um1.insert(make_pair(6, "666"));
// 插入方法三:用operator[]
um1[7] = "777";
um1[8] = "888";
um1[9] = "999";
um1[10] = "000";
// 遍历方式一:利用迭代器进行遍历打印
//unordered_map::iterator it = um1.begin();
auto it = um1.begin();
while (it != um1.end())
{
cout << (*it).first << "=>" << (*it).second << " ";
++it;
}
cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000
// 遍历方法二:利用for循环进行遍历打印
for (auto& e : um1)
{
cout << e.first<< "=>" << e.second << " ";
}
cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000
// 删除操作1:根据键值对key删除
um1.erase(5);
// 删除操作2:根据迭代器进行删除
unordered_map<int, string>::iterator rit = um1.find(7); // 顺带使用键值对key就可以用find函数了
if (rit != um1.end())
{
um1.erase(rit);
}
// 遍历打印一下,用for循环方便快捷一点
for (auto& e : um1)
{
cout << e.first << "=>" << e.second << " ";
}
cout << endl; // 1=>111 2=>222 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000
// 修改键值对:通过find获得迭代器进行修改
auto pos = um1.find(1);
if (pos != um1.end())
{
pos->second = "11/11";
}
// 修改键值对:通过operator[]运算符重载进行修改
um1[2] = "22/22";
// 打印一下
for (auto& e : um1)
{
cout << e.first << "=>" << e.second << " ";
}
cout << endl; // 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000
// 判空
cout << um1.empty() << endl; // 0 -- 不空
// 计算容器的大小
cout << um1.size() << endl; // 8个
// 计算容器中键值对的大小
cout << um1.count(3) << endl; // 1
// 交换两容器中的数据
unordered_map<int, string> tmp{{11, "123"}, { 22, "345" }};
um1.swap(tmp);
for (auto& e : tmp)
{
cout << "tmp=>" << " ";
cout << e.first << "=>" << e.second << " ";
}
cout << endl; // tmp=> 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000
for (auto& e : um1)
{
cout << "um1=>" << " ";
cout << e.first << "=>" << e.second << " ";
}
cout << endl; // um1=> 11=>123 22=>345
// 清空
um1.clear();
for (auto& e : um1)
{
cout << e.first << "=>" << e.second << " ";
}
cout << endl;
}
这个容器与unordered_map基本一致,这两个的区别在于multimap允许键值对的冗余,也就是可以允许key和value有不同的值。
void test_unordered_map3()
{
unordered_multimap<int, string> ummp1;
ummp1.insert(make_pair(2023, "yes"));
ummp1.insert(make_pair(2023, "no"));
ummp1.insert(make_pair(2023, "before"));
ummp1.insert(make_pair(2023, "now"));
for (auto& e : ummp1)
{
cout << e.first << "=>" << e.second << " ";
}
cout << endl;
}
还有三个不同:
1、unordered_map和unordered_multimap的find函数:
find函数 | 功能 |
---|---|
unordered_map | 返回键值为key的键值对的迭代器 |
unordered_multimap | 返回底层哈希表中第一个找到的键值为key的键值对的迭代器 |
2、count函数功能
count函数 | 功能 |
---|---|
unordered_map | 键值为key的键值对存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multimap | 返回键值为key的键值对的个数(find成员函数不可替代) |
3、operator[]函数功能
我们在unordered_multimap中是没有这个operator[]重载的,因为这个容器中是可以冗余的,所以我们不确定找的是哪一个,会导致很多的错误,所以我们的unordered_multimap是没有operator[]这个的!
1、unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。
2、在unordered_set中,元素的值同时也是唯一地标识它的key。
3、在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。
4、unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。
5、它的迭代器至少是前向迭代器。
void test_unordered_set1()
{
// 构造一个空壳的us1的unordered_set的容器
unordered_set<int> us1;
// 插入几个值
us1.insert(1);
us1.insert(2);
us1.insert(3);
us1.insert(4);
// 拷贝构造
unordered_set<int> us2(us1);
// 迭代器区间构造
unordered_set<int> us3(us2.begin(), us2.end());
// for循环打印一下
for (auto& e : us3)
{
cout << e << " ";
}
cout << endl;
}
成员函数 | 功能 |
---|---|
insert | 插入指定元素 |
erase | 删除指定元素 |
size | 获取容器中元素的个数 |
find | 查找指定元素 |
empty | 判断容器是否为空 |
clear | 清空当前容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定元素的个数 |
[] | 运算符重载的[]功能很强大,有插,改、找等功能 |
begin() | 获取容器中第一个元素的正向迭代器 |
end() | 获取容器中最后一个元素的下一个元素的正向迭代器的 |
void test_unordered_set2()
{
// 先构造一个空的容器
unordered_set<int> us1;
// 插入元素(只有这一种插入法)
us1.insert(1);
us1.insert(2);
us1.insert(3);
us1.insert(1);
us1.insert(4);
us1.insert(5);
// 遍历容器第一种方法:迭代器遍历
unordered_set<int>::iterator it = us1.begin();
while (it != us1.end())
{
cout << *it << " ";
++it;
}
cout << endl; // 1 2 3 4 5
// 遍历容器第二种方法:for循环
for (auto& e : us1)
{
cout << e << " ";
}
cout << endl; // 1 2 3 4 5
// 删除元素的方式一:直接找到值进行删除
us1.erase(1);
// 删除元素的方法二:利用迭代器进行删除
unordered_set<int>::iterator pos = us1.find(2);
if (pos != us1.end())
{
us1.erase(pos);
}
// 打印一下
for (auto& e : us1)
{
cout << e << " ";
}
cout << endl; // 3 4 5
// 判断容器是否为空
cout << us1.empty() << endl; // 0
// 获取值为3的个数
cout << us1.count(3) << endl; // 1
// 查看当前容器的容量
cout << us1.size() << endl; // 3
// 交换数据
unordered_set<int> tmp{99, 88, 77, 66};
us1.swap(tmp);
// 打印一下
for (auto& e : us1)
{
cout << e << " ";
}
cout << endl; // 99 88 77 66
// 打印一下
for (auto& e : tmp)
{
cout << e << " ";
}
cout << endl; // 3 4 5
// 清空
us1.clear();
// 打印一下
for (auto& e : us1)
{
cout << e << " ";
}
cout << endl; //
}
大致实现的功能与unordered_map相同,但唯一不同的一点是在于这个多功能的set是允许值进行重复的!
void test_unordered_set3()
{
unordered_multiset<int> ums1;
ums1.insert(1);
ums1.insert(2);
ums1.insert(4);
ums1.insert(3);
ums1.insert(1);
ums1.insert(5);
ums1.insert(2);
ums1.insert(7);
for (auto& e : ums1)
{
cout << e << " ";
}
cout << endl; // 1 1 2 2 3 4 5 7
}
这个多功能的set是相较于普通set来讲的count函数是返回的个数,而普通set的count函数是如果存在则返回1,不存在则返回0。
这个多功能set相较于普通set来讲的find函数是返回底层哈希表中第一个找到的键值为val的元素的迭代器,而普通set则是返回简单的key。
性能测试来一波:
#include
#include
#include
#include
using namespace std;
int main()
{
int N = 1000;
vector<int> v;
v.reserve(N);
srand((unsigned int)time(NULL));
//随机生成N个数字
for (int i = 0; i < N; i++)
{
v.push_back(rand());
}
//将这N个数插入set容器
set<int> s;
clock_t begin1 = clock();
for (auto e : v)
{
s.insert(e);
}
clock_t end1 = clock();
//将这N个数插入unordered_set容器
unordered_set<int> us;
clock_t begin2 = clock();
for (auto e : v)
{
us.insert(e);
}
clock_t end2 = clock();
//分别输出插入set容器和unordered_set容器所用的时间
cout << "set insert: " << end1 - begin1 << endl;
cout << "unordered_set insert: " << end2 - begin2 << endl;
//在set容器中查找这N个数
clock_t begin3 = clock();
for (auto e : v)
{
s.find(e);
}
clock_t end3 = clock();
//在unordered_set容器中查找这N个数
clock_t begin4 = clock();
for (auto e : v)
{
us.find(e);
}
clock_t end4 = clock();
//分别输出在set容器和unordered_set容器中查找这N个数所用的时间
cout << "set find: " << end3 - begin3 << endl;
cout << "unordered_set find: " << end4 - begin4 << endl;
//将这N个数从set容器中删除
clock_t begin5 = clock();
for (auto e : v)
{
s.erase(e);
}
clock_t end5 = clock();
//将这N个数从unordered_set容器中删除
clock_t begin6 = clock();
for (auto e : v)
{
us.erase(e);
}
clock_t end6 = clock();
//分别输出将这N个数从set容器和unordered_set容器中删除所用的时间
cout << "set erase: " << end5 - begin5 << endl;
cout << "unordered_set erase: " << end6 - begin6 << endl;
return 0;
}