目录
map和set的底层是平衡树和红黑树,本文主要是关于map和set的使用,具体的底层模拟,可以看我关于AVL树和红黑树的文章
1.关联式容器&&键值对
简单描述下。如vector,list,deque,queue等容器,都是序列式容器,因为底层是线性序列的数据结构,存的是数据本身。
而关联式容器也存数据,但存的是<key,value>这样的键值对,在数据检索时比序列式容器效率更高。
键值对,就是同时存储两种数据的结构,这两种数据是一一对应的,形象的说,就像英汉词典一样,中文对应某个英文。
pair<first,second>,就是库里的键值对结构
template <class T1, class T2> struct pair;
pair 默认对first升序,当first相同时对second升序;
make_pair也差不多,但是写法比较简洁。
template <class T1, class T2> pair<V1,V2> make_pair (T1&& x, T2&& y); // see below for definition of V1 and V2
make_pair可以直接构造一个pair,所以简洁一些。
make_pair<a,b>
这是c++98的
但是也可以依赖c++11的隐式类型转换
map<int,int>a;
a.insert({3,3});
2.树形结构(关联式容器)
stl中实现了两类关联式容器,树形结构和哈希结构。树形结构分:map,set,multimap,multiset
这四个共同点是,底层结果都是平衡搜索树(红黑树),容器中元素是有序序列。
2.1set&&multiset
set和mulitiset的区别就是后者支持重复数据(在搜索树中会放左边或右边,不影响)
2.1.1简介
1.内部按一定次序存储元素容器。
2.元素中value也表示它,在set中value就是key,类型是模板实例化决定,每个value唯一
元素不能被修改(元素都是const),但可以插入或删除元素
3.set中元素按期内部比较对象所制定的特定严格弱排序准则进行(可以自己改成严格强,自定义类型要做好重载运算符的工作)
4.set容器通过key访问单个元素速度比unordered_set慢,但它们允许根据顺序对子集直接进行迭代
5.底层是平衡二叉树(红黑树)
6.map/multimap中是真的存了一个键值对<key,value>,而set中只存value,底层结构是<value,value>这样的键值对
7.set插入元素时,只需插入value,不需构造键值对
8.set中的元素不可以重复(可以使用set去重)
9.set迭代器遍历元素,可以得到有序序列(因为底层还是搜索树,而搜索树中序遍历的结果就是有序的,所以迭代器遍历也是走中序遍历)
10.set查找某个元素,时间复杂度:log2 n
2.1.2使用
template < class T, // set::key_type/value_type class Compare = less<T>, // set::key_compare/value_compare class Alloc = allocator<T> // set::allocator_type > class set; 第一个是set容器内部元素的类型。 第二个是内部容器的比较规则。可参考我队列的文章中优先队列的描述 第三个是内存池。
2.1.2.1构造和遍历方式
explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); 上面看起来很抽象,但实际使用就是 set<类型>对象名-----set<int>a; 第一个参数是比较规则(仿函数) template <class InputIterator> set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), 这是用一段迭代区间构造的set set<int>b(a.begin(), a.end()); set (const set& x); 拷贝构造 set<int>c(b); 遍历方式: while (tmp != a.end()) { cout << *tmp << " "; tmp++; } cout << endl; for (auto& s : a) { cout << s << " "; }
2.1.2.2迭代器
提供了begin(),end(),rbegin(),rend(),cbegin(),cend(),crbegin(),crend()
2.1.2.3容量
empty()是用来看容器是否为空,为空则返回true,否则false
size是容器中元素个数
max_size()是set最多能存多大
2.1.2.4修改
insert()
pair<iterator,bool> insert (const value_type& val); 返回的是一对pair键值对, set<int>a; a.insert(1); 实际上,set中底层还是存键值对,这个键值对是<value,value> 而如果插入执行后,返回的也是一对键值对,成功是<该元素在set中的位置(迭代器),true> 失败,说明set中已经有相同值,返回<已有的相同元素在set中的位置(迭代器),false> iterator insert (iterator position, const value_type& val); 指定位置插入数据(但还是会被排序,没多大用 template <class InputIterator> void insert (InputIterator first, InputIterator last); 是插入一对迭代区间内的值。
erase()
void erase (iterator position); 删除指定位置的数据(迭代器) 但这个指定位置必须是有效位置 size_type erase (const value_type& val); 删除set中跟val相同的元素,返回删除的元素个数 在,就删,不在,就没反应 这个返回值主要是为了multiset准备 void erase (iterator first, iterator last); 删除set中一段区间的值
swap()
void swap ( set<Key,Compare,Allocator>& st ); 交换set中的值 set<int>a; a.insert(1); set<int>b; b.insert(2); b.swap(a);
clear()
元素清空
find()
iterator find (const value_type& val) const; 在set中找值为value的元素,找到之后,返回该元素在set中的位置(迭代器) 没找到,直接返回set的end() a.find(3); 对于Mulitiset是返回中序遍历中的第一个
count()
size_type count ( const key_type& x ) const 返回值set中为x的的元素有多少个 主要是为了mulitiset准备
lower_bound(),upper_bound()
iterator lower_bound (const value_type& val) const; 返回大于等于val的一个迭代器位置 iterator upper_bound (const value_type& val) const; 返回大于val的一个迭代器位置 之所以不是>=,是为了配合左闭右开的接口使用
可以用来配合着用来找到一段区间。不管是删除还是遍历等都可以。
其他
key_comp,value_comp,返回比较的仿函数
2.2map
跟multimap的区别,后者允许相同的key存在。value不影响
mulitimap不存在[]重载,因为key有相同
2.2.1简介
1.按特定次序(按key比较)存储 由key和value组合而成的元素
2.map中key用排序和唯一表示元素,value存于key关联的内容。key和value类型可以不同,
key与value放在pair里。整个pair别名为value_type;
typedef pair<const key,T>value_type;
3.map中通过键值访问单个元素通常比unordered_map慢,但map允许根据顺序对元素进行迭代遍历(通过迭代可以得到有序序列,因为也是搜索树,所以中序遍历还是有序)
4.map支持下标访问符,[]中放key,就可以找到对应value
5.底层:平衡搜索树(红黑树)
2.2.2使用
template < class Key, // map::key_type class T, // map::mapped_type class Compare = less<Key>, // map::key_compare class Alloc = allocator<pair<const Key,T> > // map::allocator_type > class map; 第一个是key的类型,T是value的类型 compare是比较的类型即仿函数 默认是小于 自定义类型需要自己传递比较规则,或者重载自定义类型的运算符
2.2.2.1构造&&遍历
explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); 跟set一样,空的map template <class InputIterator> map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); 跟set一样,一段区间构造 map (const map& x); 拷贝构造 遍历没什么,就是常见的迭代器 map<int,int>a; a.insert(pair<int,int>(3,3)); pair<int, int>c(5, 4); a.insert(c); a.insert({ 1,2 }); for (auto& s : a) { cout << s.first << " "; cout << s.second << endl; }
2.2.2.2迭代器
没什么,就是begin(),end(),rbegin,rend,cbegin,cend,crbegin,crend
2.2.2.3容量和访问
empty,map中元素数量是否为空,是返回ture,不是false
size()元素个数
[]&&at
mapped_type& operator[] (const key_type& k) mapped_type& operator[] (key_type&& k); 返回key对应的value的引用 如果key不存在,则构造一个<key,默认value>的键值对插入进去 但at不同,at是直接抛异常,其他没区别 具体插入方式 (*((this->insert(make_pair(k,mapped_type()))).first)).second 结合下面的insert例子 (this->insert(make_pair(k,mapped_type()))).first 这句话,其实就是插入一个新的元素,k是提供的,value直接调用默认value的默认构造函数 依靠insert的机制,如果元素(key是k)是已经存在的,则insert(make_pair(k,mapped_type()))返回的是 一个键值对,其中first是map中已经存在的元素(key是k的)的迭代器 如果不存在,则执行插入操作,并且返回一个键值对,其中first是相应新插入元素的迭代器 不管这两种情况,反正都是会返回一个pair,这个pair的first都是这个元素(key是k)的迭代器 (this->insert(make_pair(k,mapped_type())))就是这个pair ((this->insert(make_pair(k,mapped_type()))).first)就是这个迭代器 (*((this->insert(make_pair(k,mapped_type()))).first)).second *解引用后,因为map的元素也是个键值对,所以解引用后,再访问这个元素的second
[]可以支持插入,查找,查找+修改,插入+修改
2.2.2.4修改
insert()
pair<iterator,bool> insert (const value_type& val); 使用上和set区别不大。 注意,插入是一个键值对 map<int,int>a; a.insert(pair<int,int>(3,3)); pair<int, int>c(5, 4); a.insert(c); a.insert({ 1,2 }); 注意,第一个参数不能重复。就算重复了,也不会插入也不会更新value iterator insert (iterator position, const value_type& val); 指定插入 template <class InputIterator> void insert (InputIterator first, InputIterator last); 一段区间插入 map<int,int>a; a.insert(pair<int,int>(3,3)); pair<int, int>c(5, 4); a.insert(c); pair<map<int,int>::iterator,bool>re= a.insert({ 3,2 }); if (re.second == false) { cout << "已经存在了"; } re.first->second++; //如果已经存在,则是3 4 //如果是新插入,则是3 3
erase()
void erase (iterator position); 删除指定位置(迭代器)上的数据 size_type erase ( const key_type& x ) 删除key键值为x的值 返回删除元素个数 void erase ( iterator first, iterator last ) 删除一段区间值
swap()&&clear()
swap是交换
void swap (map& x);
clear是清空元素
find()
iterator find ( const key_type& x ) 找到key为x的元素的迭代器。找不到返回end() const_iterator find ( const key_type& x ) const 找到key为x的元素的const迭代器。找不到返回cend()
count()
size_type count ( const key_type& x ) const 返回key为x在map中的元素个数,因为map中key唯一 所以要么0要么1
lower_bound&&upper_bound
iterator lower_bound (const key_type& k); const_iterator lower_bound (const key_type& k) const; 返回大于等于key的迭代器 iterator upper_bound (const key_type& k); const_iterator upper_bound (const key_type& k) const; 返回大于key的迭代器
3.例题
3.1随机链表复制
这题可以先看我c语言版的链表中的写法,再看我这里的新的写法。
class Solution { public: Node* copyRandomList(Node* head) { map<Node*,Node*>hash; Node*cur=head,*copyhead=nullptr; Node*copytail=nullptr; while(cur) { if(copytail==nullptr) { copyhead=copytail=new Node(cur->val); } else { copytail->next=new Node(cur->val); copytail=copytail->next; } hash[cur]=copytail; cur=cur->next; } cur=head; Node*x=copyhead; while(cur) { if(cur->random==nullptr) { x->random=nullptr; } else{ x->random=hash[cur->random]; } cur=cur->next; x=x->next; } return copyhead; } };
3.2两个数组的交集
class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { set<int> s(nums1.begin(),nums1.end()); //set<int>s2(nums2.begin(),nums2.end()); //可以再弄个set,然后两个set比对,但是这题数据量小,可以直接用下面的数组标记 int mp[1001]={0}; vector<int>ret; for(auto a:nums2) { if(s.count(a)==1&&mp[a]==0) { ret.push_back(a); mp[a]=1; } } return ret; } }; //事实上还可以不用set,只依靠数组标记。 class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { int m1[1001]={0}; int m2[1001]={0}; vector<int>ret; for(auto a:nums1) { if(m1[a]==0) m1[a]=1; } for(auto a:nums2) { if(m2[a]==0) m2[a]=1; } for(int i=0,j=0;i<=1000;i++,j++) { if(m1[i]==1&&m2[j]==1)ret.push_back(i); } return ret; } };
3.3同时找差集和交集
这边没找到题。只说思想
1.两个数组放入set(依靠set自动排序和去重的特性)
2.两个指针指向两个set的第一个元素3.小的就是差集,相同就是交集(因为排序和去重,小的必然是单独的)
4.小的指针++,相同一起++
利用这个思想,我们可以单独拿来找交集。上面一题就可以这样写
class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { set<int> s(nums1.begin(),nums1.end()); set<int>s2(nums2.begin(),nums2.end()); vector<int>ret; auto l1=s.begin(),l2=s2.begin(); while(l1!=s.end()&&l2!=s2.end()) { if(*l1<*l2) { l1++; } else if(*l1>*l2) { l2++; } else { ret.push_back(*l1); l1++,l2++; } } return ret; } };
3.4前K个高频单词
这个版本思路是利用map记录单词频率。
再理由sort排序。
但是我们要注意,sort是不稳定的排序,这题说单词频率相同按字典序排序。
但事实上,map默认就会对first升序,也就是字典序排序了。
但是,当我们让其再用sort排序。这个字典序也被破坏了,相对位置改变了。
所以一个方案就是我们对仿函数里继续改造,添加字典序排序,比如下面的版本v1
,如果不想改,也可以用stable_sort,稳定排序,采用的是归并排序,归并排序是稳定的排序,也就是v2。v3版本是用优先队列存储,采用v1的方式解决排序问题(也可以v2)
v4不写了,就说思路,主要是排序的问题,字典序可以让第一次排完的数据继续放入set,因为默认升序排序,然后再放入最终答案数组里
v1class Solution { public: struct kp{ public: bool operator()(const pair<string,int>&a,const pair<string,int>&b)const { if(a.second==b.second) { return a.first<b.first; } else { return a.second>b.second; } } }; vector<string> topKFrequent(vector<string>& words, int k) { map<string,int>h1; for(auto a:words) { h1[a]++; } vector<pair<string,int>>v(h1.begin(),h1.end()); sort(v.begin(),v.end(),kp()); vector<string>ret; auto it=v.begin(); while(k--) { ret.push_back(it->first); it++; } return ret; } };
v2
class Solution { public: struct kp{ public: bool operator()(const pair<string,int>&a,const pair<string,int>&b)const { return a.second>b.second; } }; vector<string> topKFrequent(vector<string>& words, int k) { map<string,int>h1; for(auto a:words) { h1[a]++; } vector<pair<string,int>>v(h1.begin(),h1.end()); stable_sort(v.begin(),v.end(),kp()); vector<string>ret; auto it=v.begin(); while(k--) { ret.push_back(it->first); it++; } return ret; } };
v3
这个版本就是用优先队列存储
class Solution { public: struct kp { public: bool operator()(const pair<string, int>& a, const pair<string, int>& b)const { if(a.second==b.second) { return a.first>b.first; } else { return a.second < b.second; } } }; vector<string> topKFrequent(vector<string>& words, int k) { map<string, int>h1; for (auto a : words) { h1[a]++; } priority_queue<pair<string, int>, vector<pair<string, int>>,kp >v(h1.begin(), h1.end()); vector<string>ret; auto it = v.top(); while(k--) { ret.push_back(it.first); v.pop(); it=v.top(); } return ret; } };