C++:set和map详解版
目录
set
1.set类的介绍
2.set迭代器
3.set构造
4.set增删查
增insert
插入单个key
迭代器区间插入
支持列表插入
查find
删除erase
删除某个位置
给个值给他删
支持迭代器区间删除 [首元素,尾元素)
count,为mulset准备的
lower_bound
upper_bound
multiset:只是与set有略微的差异
两个题目
map
在插入之前先介绍一下返回值pair类型是啥
插入insert
1.有名pair对象插入
2.匿名的pair插入
3.make_pair函数模板插入,返回一个构造的pair对象
4.C++11,支持多参数隐式类型转换,用{}包裹的多个值,通过构造函数隐式转换成类对象
遍历
支持列表初始化
以下方式可以统计统计水果次数
mutimap
相关的两个题
序列式容器:保证插入数据的顺序性,逻辑结构是线性的,如vector,list,两个位置的值一般没有紧密联系,位置交换后仍然是序列式容器
关联式容器:逻辑结构是非线性的,两个位置之间有紧密联系,交换一下,它的存储结构就被破坏。比如map/set,unordered_map/unordered_set.
本篇文章围绕map/set来讲解,底层是红黑树,红黑树是平衡二叉搜索树。map是key的搜索场景的结构,set对应key/value的搜索场景。
set
set是平衡二叉搜索树,增删查的效率是高度次logN,迭代器遍历走中序,所以是有序的
set容器中,每个键值key必须唯一,且并不支持修改(会破坏底层结构),但是可以进行插入,移除
1.set类的介绍
template < class T, // set::key_type/value_typeclass Compare = less<T>, // set::key_compare/value_compareclass Alloc = allocator<T> // set::allocator_type> class set;
T是key的类型,一般我们不需要传后两个模板参数,一个是仿函数(默认是key小于比较),一个是空间配置器
这里key的类型T写两个key_type,value_type是为了和后面的map匹配
重点介绍:
2.set迭代器
是一个双向迭代器,支持++,--
3.set构造
无参的默认构造
explicit set (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
迭代器区间构造
set (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& = allocator_type());
拷贝构造
set (const set& x);
初始化列表构造
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
4.set增删查
增insert
由于set的键值唯一性,插入前需要判断是否这个元素是唯一的,如果不是,不插入该元素,并返回指向已有元素的迭代器(如果函数有返回值的话)
插入单个key
pair<iterator,bool> insert (const value_type& val);
返回的是一个pair类型对象,它包含两个成员,一个是插入元素的迭代器,另一个是否插入成功
先忽略返回值pair,等下再详细讲解
template <class T1, class T2>
struct pair {T1 first; // 第一个元素T2 second; // 第二个元素
};//大致这样
int main()
{// 去重+升序排序set<int> s;// 去重+降序排序(给⼀个⼤于的仿函数,降序)//set<int, greater<int>> s;//1.插入单个值s.insert(5);s.insert(2);s.insert(7);s.insert(5);//相同的值5插入失败auto it = s.begin();while (it != s.end()){//*it = 1;//不支持修改cout << *it << " ";++it;}cout << endl;return 0;
}
迭代器区间插入
template <class InputIterator> void insert (InputIterator first, InputIterator last);
InputIterator可以是一个模板参数,代表一种迭代器类型------不限制具体是哪种容器的迭代器
set<int> s = { 5 };
vector<int> x = { 1,2,3,4 };
//迭代器区间插入
s.insert(x.begin(), x.end());
for (auto e : s)
{cout << e << " ";
}
cout << endl;
支持列表插入
void insert (initializer_list<value_type> il);
//2.插入一段列表值
s.insert({ 1,2,3,4,2 });
查find
iterator find (const value_type& val);
如果找到与val值相等的元素,则返回指向该元素的迭代器,否则,返回end()
set中的find表示遍历这棵树,最多查找高度次O(logN)
而算法库中的find是一个个比较,O(n)
删除erase
删除某个位置
iterator erase (const_iterator position);
// 删除最⼩值s.erase(s.begin());
给个值给他删
size_type erase (const value_type& val);
int num = s.erase(0);
if (num == 0)
{cout << "不存在!" << endl;
}
for (auto e : s)
{cout << e << " ";
}
cout << endl;
通过返回值判断是否删除成功/失败,返回删除了几个数0/1,不用bool的原因是为了兼容muliset(可能删除>=0个相同的数)
可以认为这个erase底层就是一个find+迭代器,有就删,没找到就不动
auto it = s.find(0);if (it != s.end())//如果没找到,find会返回end(){s.erase(it);}else//没找到就不动{cout << " 不存在! " << endl;}for (auto e : s){cout << e << " ";}cout << endl;
erase后迭代器it失效吗?
想想二叉搜索树,
1.删除叶子节点/只有一个孩子的节点
则直接删除这个节点,it被释放,成为野指针,不能被访问,即迭代器it失效
2.删除的节点有两个孩子
拿其他节点的值替换待删除结点的值,也认为它失效,因为it指向的迭代器即使能访问,访问替换后的节点,但它的意义已经变了。也认为迭代器失效。
支持迭代器区间删除 [首元素,尾元素)
iterator erase (const_iterator first, const_iterator last);
count,为mulset准备的
在容器中搜索与 key 相等的元素,并返回匹配的数量。0/1
判断key在不在比较方便
if (s.count(2)){cout << 2 << "在" << endl;}
lower_bound
在有序的set中,定位第一个值>=key的元素,并返回指向这个元素的迭代器
iterator lower_bound (const value_type& val) const;
upper_bound
定位第一个值>key的元素,并返回指向这个元素的迭代器
iterator upper_bound (const value_type& val) const;
比如要把一段区间删除[30,50],10,20,30,40,50,60,70,80,90
用erase迭代器区间删除的话,需要类似这样 erase(30的迭代器,50的下一个迭代器)
int main()
{std::set<int> myset;for (int i = 1; i < 10; i++)myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90for (auto e : myset){cout << e << " ";}cout << endl;//迭代器区间删除[30,50]--[30,60)auto itlow = myset.lower_bound(30);//返回>=30的位置 auto itup = myset.upper_bound(50);//返回>50的位置myset.erase(itlow, itup);for (auto e : myset){cout << e << " ";}cout << endl;//10,20,60,70,80,90return 0;
}
当然也是按照搜索树的规则去找
multiset:只是与set有略微的差异
multiset相比于set不会去重,即允许键值冗余
与set的差异体现在:
find:好多个x,找中序的第一个x ,++后还能的得到相同的x,即能找到所有的x
find找中序的第一个--左子树的第一个,右子树不用管、找到3继续向左子树找3,知道左子树找不到,此节点就是中序第一个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{cout << *pos << " ";++pos;
}
cout << endl;
insert:相等的key插到右面还是左边都一样,最后都会旋转到左边
erase:可以删除多个相同的key
删除迭代器 pos
指向的元素后,通过 erase
的返回值获取 “下一个有效迭代器”
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{pos = s.erase(pos);
}
cout << endl;
it = s.begin();
while (it != s.end())
{cout << *it << " ";++it;
}
cout << endl;
s.erase(2);会全部删除2
count:用来统计相同key的个数0/>0
两个题目
1.环形链表
2.交集
map
template < class Key, // map::key_typeclass T, // map::mapped_typeclass Compare = less<Key>, // map::key_compareclass Alloc = allocator<pair<const Key,T> > // map::allocator_type> class map;
key_type是k关键字key的类型,mapped_type是值value的类型,只按照key来比较,默认升序,与value无关;
支持增删查改
改:支持修改value,不支持改key
在插入之前先介绍一下返回值pair类型是啥
value_type是一个pair类型,pair是一个类模板
template<class Key,class T>
class mymap
{//对pair<const Key, T>起别名value_typetypedef pair<const Key, T> value_type;//mymap里面定义了一个模板,参数为T1,T2,对应pair声明中的Key,Ttemplate<class T1, class T2>struct pair{typedef T1 first_type;typedef T2 second_type;T1 first;//keyT2 second;//value};
};
pair就是把key,value封装,多封了一层
插入insert
pair<iterator,bool> insert (const value_type& val);
返回了pair<iterator,bool>,包含两个成员变量iterator first,bool second
1.有名pair对象插入
//1有名
map<string, string> dict;
pair<string, string> v1("win", "赢");
dict.insert(v1);
2.匿名的pair插入
//2匿名
dict.insert(pair<string, string>("success", "成功"));
3.make_pair函数模板插入,返回一个构造的pair对象
make_pair
//3make_pair函数模板
dict.insert(make_pair("sort", "排序"));
用函数模板构造一个pair进行返回
4.C++11,支持多参数隐式类型转换,用{}包裹的多个值,通过构造函数隐式转换成类对象
void insert (initializer_list<value_type> il);
//4多参数隐式类型转换
dict.insert({ "apple", "苹果" });
遍历
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{cout << *it << "";++it;
}
cout << endl;
不可以这样写,对迭代器进行解引用返回的是value_type,是pair对象,而std::cout不支持输出pair类型,即不支持同时返回两个值
咋遍历,打印数据不能*it,先对迭代器解引用,通过结点指针再去取节点里面的数据,key和value
1.解引用+.
while (it != dict.end()){cout << (*it).first << " " << (*it).second << endl;++it;}
2.迭代器中重载了operator->
// 模拟迭代器的简化实现
template <class T>
struct iterator {T* ptr; // 重载 operator->T* operator->() const{return ptr; //返回指向元素的指针 }
};
因此能支持->访问
while (it != dict.end())
{cout << it->first << " " << it->second << endl;++it;
}
cout << endl;
本质上是两个->,第一个是运算符重载返回的指针,第二个是对指针进行解引用
while (it != dict.end())
{cout << it.operator->()->first << " " << it.operator->()->second << endl;++it;
}
cout << endl;
如果插入时,key相等,但value不相等,会不会更新value?不会
可以修改value,不支持改key
map<string, string>::iterator it = dict.begin();while (it != dict.end()){it->first += 'x';//errorit->second += 'x';++it;}
支持列表初始化
map<string, string> dict = { {"win", "赢"},{"success", "成功"},{"apple", "苹果"} };
还可以这样,不写有名,匿名,直接走隐式类型转换pair<string,string>
erase只跟key有关,insert根key/value有关,find只跟key有关
以下方式可以统计统计水果次数
map<string int>
苹果:6
string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠" };
map<string, int> countMap;
for (const auto& str : arr)
{auto ret = countMap.find(str);if (ret == countMap.end()){countMap.insert({ str, 1 });}else{ret->second++;}
}
for (const auto& e : countMap)
{cout << e.first << ":" << e.second << endl;
}
cout << endl;
2.还有另一种方法,直接借助【】统计,count[str]++
(*((this->insert(make_pair(k,mapped_type()))).first)).second
即我给map里的key,返回对应的value,然后对value进行修改
功能:插入+查找+修改
key不存在,即插入,返回映射值的引用
插入失败也充当了查找的功能,方便给【】使用
operaotr的内部实现,通过insert实现
mapped_type& operator[](const key_type& k)
{pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
两个pair,底层+返回值,是个类模板
count[str]++,咋统计次数的,pair ret ; iterator it=ret.first ; it->second++
总结:
- str不存在,插入+修改
- str存在时,查找+修改(++次数
- 查找的前提是得确定 key 在,否则就是插入了
map<string, string> dict;
dict.insert(make_pair("apple", "苹果"));//key不存在时,插入功能{"orange",string()}
dict["orange"];//key不存在,插入+修改
dict["banana"] = "香蕉";//key存在,查找+修改
dict["apple"] = "好吃的苹果";//查找,确定key在
cout << dict["apple"] << endl;//插入
cout << dict["grape"] << endl;
mutimap
插入一定成功
erase连续删除key,中序第一个开始
不支持[],因为不知道返回哪个value
相关的两个题
1.链表的复制
2.前k、个高频单词