map和set的使用
map和set属于STL中的关联式容器,与序列式容器不同,关联式容器中的数据之间存在联系,进行插入和删除操作需要调整容器内部的结构。
-
STL中的序列式容器:vector,deque,list
-
STL中的适配器模式:stack,queue
-
STL中的关联式容器:map,set
map和set的底层是一个红黑树。
set的使用
set的底层是一个key模型的二叉搜索树,专门用来查找数据,判断数据在不在set中。
template <class T,class Compare=less<T>,class Alloc=allocator<T>>
class set;
- 第一个模板参数表示set中要存放的数据类型。
- 第二个模板参数是仿函数,用来比较大小,set的底层是一个搜索树,默认左子树的结点值都小于根,右子树的所有结点值都大于根。当Compare为
greater<T>
时,左子树的所有结点都大于根,右子树的所有结点都小于根,此时中序遍历是降序。set默认的less<T>
中序遍历是升序。当然也可以自定义仿函数传入,根据指定的比较方式进行比较。 - 第三个模板参数是空间配置器(内存池)
构造函数
set支持迭代器区间的构造函数,在c++11中还支持列表初始化。
template <class InputIterator>
set(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());//迭代器区间构造
set (const set& x);//拷贝构造,set的拷贝构造是深拷贝
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());//c++11支持initializer_list列表构造
析构函数
set的底层是二叉搜索树,其中的结点均是在堆区开辟空间,因此set的析构函数需要进行递归delete结点
operator=
set的operator=进行的是深拷贝操作,当搜索树比较大时,深拷贝的代价也比较大,因此慎用operator=
迭代器
set的迭代器底层较复杂,但是使用很简单。其begin()是指向中序的第一个结点,迭代器的++是让该迭代器指向中序的下一个结点。
int main()
{
set<int> s;
set<int>::iterator it=s.begin();
it++;
return 0;
}
set的迭代器支持范围for
int main()
{
int arr[] = { 9,13,24,5,76,3,5,9 };
set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));
set<int>::iterator it = s.begin();
while (it != s.end())
cout << *it++ << ' ';
cout << endl;
for (int i : s)
cout << i << ' ';
cout << endl;
return 0;
}
set的反向迭代器rbegin()可以认为指向中序遍历的最后一个结点。
empty,size,maxsize
empty:判断是否为空
size:set中结点的个数
maxsize:set中能存放的最大结点的个数
insert
insert支持迭代器区间插入,支持插入一个数据,支持在迭代器位置插入一个数据。
pair<iterator,bool> insert(const value_type& val);
iterator insert(iterator pos,const value_type& val);
template<class inputiterator>
void insert(inputiterator first,inputiterator last);
第一个构造函数中的pair是键值对,在STL中是一个模板类,pair的key是一个set的迭代器,value是一个bool值
template<class T1,class T2>//T1是key的类型,T2是value的类型
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){}
};
int main()
{
set<int> s = { 1,7,4,7,9,3 };
int x = 10;
pair<set<int>::iterator,bool> ret=s.insert(x);
if (ret.second == false)
{
cout << "ret.second=" << ret.second << endl;
printf("插入%d失败\n", x);//原因是set不允许键值对冗余
}
else
{
printf("插入%d成功,返回了指向%d这个结点的迭代器(存放在pair中)\n", x,x);
cout << "ret.second=" << ret.second << endl;
//ret.first是迭代器,不能直接使用
cout << "*ret.first=" << *ret.first << endl;
}
return 0;
}
set的pair<iterator,bool> insert(const value_type& val);
设置了pair作为返回值是为了和map对应。
set的iterator insert(iterator pos,const value_type& val);
的返回值是指向新插入的val的迭代器。如果val在set中已经存在,那么返回指向val的迭代器。参数pos是为了加快insert的速度而传入的一个参数,如果知道val大概要插入到搜索树的哪个位置,就可以传入指向该位置的迭代器。
using namespace std;
int main()
{
set<int> s = { 1,9,14,6,3,54,2,0 };
set<int>::iterator ret1=s.insert(s.begin(),9);
cout << "*ret=" << *ret1 << endl;
for (const auto& i : s)
cout << i << ' ';
cout << endl;
set<int>::iterator ret2 = s.insert(s.begin(), 4);
cout << "*ret=" << *ret2 << endl;
for (const auto& i : s)
cout << i << ' ';
cout << endl;
return 0;
}
erase
void erase (iterator pos);//给定迭代器,删除迭代器指向的结点
bool erase (const value_type& val);//给指定值,set中存在该值,则删除对应结点,返回true.否则返回false
void erase (iterator first, iterator last);//删除一段迭代器区间
void erase (iterator pos);
如果传入的迭代器不合法,在debug模式下会断言检查报错。
int main()
{
set<int> s = { 1,9,14,6,3,54,2,0 };
for (const auto& i : s)
cout << i << ' ';
cout << endl;
s.erase(s.find(-1));//s.find()找不到-1会返回end(),end()不指向有效结点,debug模式下报错
for (const auto& i : s)
cout << i << ' ';
cout << endl;
return 0;
}
swap
void swap (set<value_type>& x);
交换2颗set底层的红黑树(实际上是交换了根节点)
int main()
{
set<int> s1 = { 1,4,3 };
set<int> s2 = { 9,5,0 };
for (const auto& i : s1)
cout << i << ' ';
cout << endl;
for (const auto& i : s2)
cout << i << ' ';
cout << endl;
s1.swap(s2);
for (const auto& i : s1)
cout << i << ' ';
cout << endl;
for (const auto& i : s2)
cout << i << ' ';
cout << endl;
return 0;
}
clear
销毁整棵树
find
const_iterator find (const value_type& val) const;
iterator find (const value_type& val);
查找,找到了返回指向该结点的迭代器,找不到返回end(),end()不指向有效结点
int main()
{
set<int> s = { 1,4,3 };
auto it = s.find(2);
if (it == s.end())
cout << "找不到" << endl;
else
cout << "*it=" << *it << endl;
return 0;
}
count
统计某一个值出现的次数,对于set,这个函数的返回值为0或1,基本无意义,对于multiset(允许键值对冗余的set),这个值有意义。
int main()
{
set<int> s = { 1,1,2,3 };
cout << s.count(1) << endl;
cout << s.count(4) << endl;
multiset<int> ms = { 1,1,2,3 };
cout << ms.count(1) << endl;
cout << ms.count(3) << endl;
return 0;
}
lower_bound和upper_bound
在set中有lower_bound和upper_bound,在算法库中也有通用的lower_bound和upper_bound.
算法库中的lower_bound:
template<class forwarditerator,class T>
forwarditerator lower_bound(forwarditerator first,forwarditerator last,const T& val);
在一个迭代器区间[first,last)中找到第一个大于等于val的位置,并返回该位置的迭代器,如果找不到,返回last。lower_bound要求[first,last)的元素按照统一的标准进行了排序
int main()
{
vector<int> v = { 2,4,7,9,18 };
auto it = lower_bound(v.begin(), v.end(), 6);
cout << *it << endl;
return 0;
}
可以设置Compare,让lower_bound在[first,last)中找到第一个小于等于val的位置,当Compare是greater时,lower_bound是找第一个小于等于val的位置
template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
算法库中的upper_bound:
upper_bound与lower_bound的唯一区别在于upper_bound是找第一个大于val的位置,当Compare为greater时,upper_bound是找第一个小于val的位置。
set的lower_bound和upper_bound
iterator lower_bound (const value_type& val);
const_iterator lower_bound (const value_type& val) const;
int main()
{
//基于set的Compare是less<T>的前提
set<int> s={5,8,2,1,3,4};
set<int>::iterator it=s.lower_bound(2);//第一个>=2位置的迭代器,指向2的迭代器
cout<<*it<<endl;
it=s.upper_bound(2);//第一个大于2位置的迭代器,指向3的迭代器
cout<<*it<<endl;
return 0;
}
若set的Compare是greater<T>
则lower_bound(int val)返回第一个<=val的迭代器,upper_bound返回第一个<val的迭代器。
equal_range
pair<const_iterator,const_iterator> equal_range (const value_type& val) const;
pair<iterator,iterator> equal_range (const value_type& val);
在set中找所有等于val的值,返回这段迭代器区间。equal_range一般适用于multiset
int main()
{
multiset<int> ms={1,5,3,4,3,6,3};
pair<multiset<int>::iterator,multiset<int>::iterator> ret=ms.equal_range(3);
multiset<int>::iterator it=ret.first;
while(it!=ret.last)
cout<<*it++<<endl;
return 0;
}
multiset
multiset与set的使用基本一致,multiset允许键值对冗余,multiset有一个重载的erase会删除所有等于val的值
int main()
{
multiset<int> ms={3,1,6,3,5,5,3,1,2};
for(const auto& i:ms)
cout<<i<<' ';
cout<<endl;
ms.erase(3);
ms.erase(5);
for(const auto& i:ms)
cout<<i<<' ';
cout<<endl;
return 0;
}
map的使用
map与set的不同之处是map的底层红黑树的结点存的是结构体,map的模型是key-value模型。
template <class Key,class T,class Compare = less<Key>,class Alloc = allocator<pair<const Key,T>>>
class map;
Key是key的类型,T是value的类型,map底层是按照key的大小进行排序的,默认是key大于根节点的都在右边,小于根节点的都在左边。若Compare为greater<Key>
,则相反。
int main()
{
map<string, string,greater<string>> m;
m.insert(make_pair("string", "字符串"));
m.insert(make_pair("sort", "排序"));
m.insert(make_pair("vector", "向量"));
m.insert(make_pair("list", "双链表"));
m.insert(make_pair("deque", "双端队列"));
for (const auto& e : m)
{
//e.first对应key,e.second对应value
cout << e.first << " " << e.second << endl;
}
map<string, string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
return 0;
}
operator[]
map支持operator[],可以根据key得到value.使用起来十分方便
int main()
{
map<string, string> m;
m.insert(make_pair("string", "字符串"));
m.insert(make_pair("sort", "排序"));
m.insert(make_pair("vector", "向量"));
m.insert(make_pair("list", "双链表"));
m.insert(make_pair("deque", "双端队列"));
cout<<m["string"]<<' ';
cout<<m["sort"]<<' ';
cout<<m["vector"]<<' ';
cout<<m["list"]<<' ';
cout<<m["deque"]<<' ';
return 0;
}
map的operator[]如果[]中的key不存在,会创建一个pair并插入.
int main()
{
map<string, int> countmap;
string str[] = { "苹果","梨子","菠萝", "香蕉", "香蕉", "苹果", "菠萝", "梨子", "苹果", "苹果" };
for (const string& s : str)
countmap[s]++;//第一次创建的时候相当于insert(pair<string,int>("苹果",int())),返回value的引用
for (const auto& s : countmap)
cout << s.first << " " << s.second << endl;
return 0;
}
int main()
{
map<string, int> countmap;
string str[] = { "苹果","梨子","菠萝", "香蕉", "香蕉", "苹果", "菠萝", "梨子", "苹果", "苹果" };
for (const string& s : str)
cout << countmap[s] << endl;
for (const auto& s : countmap)
cout << s.first << " " << s.second << endl;
return 0;
}
0
0
0
0
0
0
0
0
0
0
菠萝 0
梨子 0
苹果 0
香蕉 0
insert和erase
map的insert需要插入一个键值对pair(实际上就是一个结构体),erase只需要提供key就能从红黑树中删除这个key对应的结构体。
int main()
{
map<string,int> m;
m.insert(pair<string,int>("张三",18));
m.insert(make_pair("李四",20));
m.erase("张三");
return 0;
}
make_pair是一个函数模板,由于函数模板可以自动推导类型,所以在向map中插入结构体时,经常使用make_pair.
template <class T1, class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
return pair<T1,T2>(x,y);
}
multimap
map不允许键值对冗余,而multimap允许键值对冗余。multimap没有operator[]
int main()
{
multimap<string,int> mp;
mp.insert(make_pair("张三",18));
mp.insert(make_pair("张三",19));
mp.insert(make_pair("李四",20));
auto it=mp.find("张三");
it->second=22;
it++;
it->second=25;
for(const auto& p:mp)
cout<<p.first<<' '<<p.second<<endl;
return 0;
}
用STL的map解决一道c语言处理起来比较麻烦的题:138. 复制带随机指针的链表 - 力扣(LeetCode)
class Solution {
public:
Node* copyRandomList(Node* head) {
//1.复制链表并建立key-value映射
Node* tmp=new Node(0);
Node* copy=tmp;
Node* cur=head;
map<Node*,Node*> mp;
while(cur)
{
tmp->next=new Node(cur->val);
tmp=tmp->next;
mp.insert(make_pair(cur,tmp));//建立原链表结点与拷贝链表结点的映射
cur=cur->next;
}
//2.根据key-value确定复制的链表的random
tmp=copy->next;
cur=head;
while(cur)
{
tmp->random=mp[cur->random];
tmp=tmp->next;
cur=cur->next;
}
Node* ans=copy->next;
delete copy;
return ans;
}
};