目录
一、关联式容器和键值对
在学习如何使用set和map之前,我们先来了解什么是关联式容器
- vector、list、deque、 forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
- 而关联式容器也是用来存储数据,但他存储的不是数据本身,里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
而键值对是一个用来表示一一对应关系的数据结构,它一般包含两个成员变量key和value,key代表键值,value表示与key对应的信息。我们通常用pair来表示一个键值对的数据结构。
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
二、set的介绍
- set它是一个底层由二叉搜素树来实现的容器,它具有排序+去重的功能。我们在使用迭代器去遍历它的时候,可以得到一个有序的数组。它里面的元素是value,底层是一个<value,value>的键值对,我们平常在使用的时候,只需要传一个value即可。
- 它的value是都是用const修饰的,所以它的元素不能被修改,但可以进行插入和删除。我们在插入的时候插入多个相同的value,它会帮我们自动去重。
- set搜素的数据的时间复杂度为log_2(N);默认按照小于来比较。
三、set的使用
通过查文献,我们看到set的模板参数里面有一个T变量,一个compare,默认是用less表示我们的变量按从小到大的顺序排列,而Alloc是一个适配器,默认是使用allocator,用于分配和释放内存。
这个通常已经满足了我们很大部分的需求,所以我们一般使用的时候可以不用关心
- set的初始化
#include<iostream>
#include<set>
using namespace std;
int main ()
{
//默认构造
std::set<int> first;
//范围构造
int myints[]= {10,20,30,40,50};
std::set<int> second (myints,myints+5);
//拷贝构造
std::set<int> third (second);
//迭代器区间初始化
std::set<int> fourth (second.begin(), second.end());
return 0;
}
2.set的插入
#include<iostream>
#include<set>
using namespace std;
void test_set()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(9);
s.insert(7);
s.insert(4);
s.insert(6);
s.insert(1);
set<int>::iterator it = s.begin();
while(it != s.end())
{
cout<<*it<<" ";
++it;
}
cout<<endl;
}
int main()
{
test_set();
return 0;
}
运行结果可以看到,我们原本插入两个1,最后输出了一个1,无序的插入,通过迭代器遍历可以得到一个有序的数列。
我们也可以通过范围for来遍历,因为范围for的底层就是用迭代器实现的。
#include<iostream>
#include<set>
using namespace std;
void test_set()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(9);
s.insert(7);
s.insert(4);
s.insert(6);
for(const auto& a : s)
{
cout << a << " ";
}
cout << endl;
}
int main()
{
test_set();
return 0;
}
运行的结果也是一样的。
3.set的一些其他功能
查找功能
find如果找到了,就返回那个节点的迭代器,如果没有找到,那就返回一个end();
所以我们可以这样子使用。
#include<iostream>
#include<set>
using namespace std;
void test_set()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(9);
s.insert(7);
s.insert(4);
s.insert(6);
set<int>::iterator ret = s.find(6);
if(ret != s.end())
cout << *it << endl;
//或者
if(s.find(5) != s.end())
{
cout << "找到了"<<endl;
}
}
int main()
{
test_set();
return 0;
}
统计次数(count)
这个主要是multiset更实用,multiset就是一个不去重+排序。因为set有去重功能了,所以每个元素出现的次数不是0,就是1。
找边界(lower_bound, upper_bound, equal_range)
lower_bound通常和upper_bound配合使用,lower_bound是左边界,upper_bound是有边界。它是一个左闭右开的区间[lower_bound,upper_bound)。
#include<iostream>
#include<set>
using namespace std;
void test_set()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(9);
s.insert(7);
s.insert(4);
s.insert(6);
for (auto a : s)
{
cout << a << " ";
}
cout << endl;
set<int>::iterator itlow, itup;
itlow = s.lower_bound(4);
itup = s.upper_bound(7);
while (itlow != itup)
{
cout << *itlow << " ";
itlow++;
}
}
int main()
{
test_set();
return 0;
}
lower_bound(4)是找比4大于或等于的位置,upper_bound(7)是找比7大的位置,因为它是一个左闭右开的区间,要让找到的数字位于区间内,所以upper_bound就会往后找。
假如我们来一个lower_bound(3),那么会找到4,因为我们元素里面没有3,所以会找比3大于等于的数字,这时候找到的是4.lower_bound(7),要找比7大的位置,所以找到9位置。
#include<iostream>
#include<set>
using namespace std;
void test_set()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(9);
s.insert(7);
s.insert(4);
s.insert(6);
for (auto a : s)
{
cout << a << " ";
}
cout << endl;
set<int>::iterator itlow, itup;
itlow = s.lower_bound(3);
cout << *itlow << endl;
itup = s.upper_bound(7);
cout << *itup << endl;
}
int main()
{
test_set();
return 0;
}
四、map的介绍
1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
素。
2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的
内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型
value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;从这里我们可以看到,key是用一个const修饰的,所以map的key值不能被修改,但是value可以修改。
3. 在内部,map中的元素总是按照键值key进行比较排序的。
4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
我认为map主要应用于处理一些具有映射关系的问题。
五。map的使用
- map的初始化
- 直接初始化
map<int,int> mymap;
- 拷贝构造
map<int,int> mymap1; map<int,int> mymap2(mymap1);
- 迭代器区间初始化
map<int,int> mymap1; map<int,int> mymap2(mymap1.begin(),mymap1.end());
2.map的插入
我们知道map里实际放的是一个pair的数据结构,所以我们在插入时,插入的应当是一个pair,这里有四种插入的方式。
map<string,int> mymap1;
mymap1.insert(pair<string,int>("hello",1));
mymap1.insert(make_pair("A",1));
mymap1.insert({"world",1});
map<string,int> mymap2;
mymap2.insert(mymap1.begin(),mymap1.end());
这里的第三种插入方式实际上是一种隐式类型转化,它是c++11支持的,这是一种多参数的隐式类型转化。
这里最推荐使用第二种,因为第二种是最通用的。
3.map重载了[]
这个[]我认为是map的最精华所在,它和我们平常的[]不太一样,map的[]里面放的是key值,找到的是key对应的value值。
map<string,int> mymap1;
mymap1["hello"] = 1;
mymap1["world"] = 1;
mymap1["hello"]++;
我们可以用[]来插入数据,或者对key对应的value值做修改。就比如上面的代码,
mymap1["hello"],hello不存在,那就会调用insert,将hello插入,然后再根据创建的hello节点把value的位置找到,然后赋值修改成1;如果【】里的key存在,则会直接找到value然后做修改。
它是通过这样的一个返回值实现的。
(*((this->insert(make_pair(k,mapped_type()))).first)).second
我们观察发现它这里和insert有关,它使用了insert的返回值,所以我们探索一下insert的返回值。
通过查文献,大致意思是
1.key在map里面,返回pair<树里面的key所在的节点的iterator,false>
2.key不在map里面,返回pair<新插入的key所在节点的iterator,true>
所以我们在使用insert的时候,返回key的iterator,
(*(pair<key的iterator,true或者false>)).first)).second
然后再调用它的first
(*(iteraotor)).second
然后是解引用iterator,找到pair<string,int>
pair<string,int> .second
这样就找到了value值。
所以我们的map重载的[]可以完成插入和对key对应的value进行修改。
总结:
💦💦如果有写的有什么不好的地方,希望大家指证出来,我会不断的改正自己的错误。💯💯如果感觉写的还可以,可以点赞三连一波哦~🍸🍸后续会持续为大家更新
🔥🔥你们的支持是我最大的动力,希望在往后的日子里,我们大家一起进步!!!🔥🔥