目录
- 序列式容器和关联式容器
- set使用介绍
- multiset的介绍
- map的使用介绍
- multimap的说明
序列式容器和关联式容器
在STL中像vector list deque forward_list这样的容器,里面存储的数据元素是线性结构的,通过线性的数据结构来管理和操作,这样的容器就是序列式容器。
关联式容器的数据单位是一对键值对,即<key,value>结构的键值对,key和value有着对应关系,在数据检索时比序列式容器效率更高。
STL中的关联式容器有map、set、multimap、multiset,它们存储的数据类型就是键值对pair,是一种类模板。
template<class T1,class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
:first(T())
,second(T())
{}
pair(const T1&a,const T2 &b)
:first(a)
,second(b)
{}
};
上述四种容器都是存储实例化的pair类型数据,set和multiset是只对key值操作的容器,可以认为它的pair类型是<key,key>。
set使用介绍
set底层使用的数据结构是红黑树,也是二叉搜索树的一种,所以我们往set中插入数据也是二叉搜索树的插入方式。
set<int>s;
s.insert(6);
s.insert(9);
s.insert(4);
s.insert(2);
s.insert(5);
s.insert(8);
s.insert(1);
s.insert(1);
s.insert(1);
cout << s.size() << endl;
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
set的插入就是往二叉搜索树插入数据,重复的数据不会插入,同时用迭代器依顺序取值打印就是树的中序遍历。
set的构造函数常用的就是默认构造,还有迭代器构造,用输入型迭代器即可。
vector<int>v = { 9,5,7,8,2 };
set<int>s(v.begin(),v.end());
cout << s.size() << endl;
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
}
迭代器类型用普通迭代器、反向迭代器、const修饰的迭代器
同样有存储数据数量成员函数size,是否为空成员函数empty,其他一些查找find 删除erase 清空clear基本同其他容器用法相同。
multiset的介绍
multiset和set的区别就是能够存储相同的数据。
multiset<int>s;
s.insert(6);
s.insert(9);
s.insert(4);
s.insert(2);
s.insert(2);
s.insert(5);
s.insert(8);
s.insert(1);
s.insert(1);
s.insert(1);
for (auto e : s)
cout << e << " ";
cout << endl;
auto it = s.find(2);
while (it != s.end() && *it == 2)
{
auto next = it;
++next;
s.erase(it);
it = next;
}
for (auto e : s)
cout << e << " ";
上面对multiset简单的使用了一下,可以看成multiset可以存储相同的数据,find()查找数据时没找到就返回末尾迭代器,找到了如果有多个就返回中序遍历的最前面一个数的迭代器
set和multiset的erase()删除指定数据会返回成功删除了的数据个数。
map的使用介绍
map是存储具体的pair数据类型的,测试map的插入数据
map<string, string>dict;
dict.insert(pair<string, string>("sort", "排序"));
pair<string, string>kv("algorithm", "算法");
dict.insert(kv);
dict.insert(make_pair("test", "测试"));
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << (*it).first << " " << it->second << endl;
++it;
}
这里来插入英文和中文对应的两个字符串这种类型是pair<string,string>,代码也演示了插入pair的三种方式
- 用临时变量的方式有参构造pair类型来插入。
- 创建一个pair变量再插入。
- 用pair类模板中的成员函数make_pair,此函数就是返回一个pair变量,刚好插入到map中。
用迭代器方式遍历也是中序遍历,是按照pair的第一个值first也是就key来构建树的。
就是按字符串比较大小的方式排序。
可以用map来做存储语言互译程序的底层结构。平时还可以用map来计数,比如统计水果受喜爱的投票计数
map<string, int>countmap;
string arr[] = { "苹果","苹果","香蕉","西瓜","苹果","香蕉","香蕉", "香蕉","樱桃" };
for (auto& e : arr)
{
auto kv = countmap.find(e);
if (kv != countmap.end())
++kv->second;
else
countmap.insert(make_pair(e, 1));
}
上面代码就是统计水果的一种方案,起初是没找到,然后插入,计数为1,后面找到了这种水果就计数++。这种方案弊端是在没有找到时,会插入,这种情况就查找了两次,find一次insert一次,find和insert都会对树从根开始往下查找。
方案二
for (auto& e : arr)
{
auto kv = countmap.insert(make_pair(e, 1));
if (kv.second == false)
kv.first->second++;
}
这里就利用了map的插入函数的特性
insert的返回值是pair<iterator,bool>,插入时,如果树中已经有相同key值的pair,返回值的second值就是false,否则是true,第一个值是个迭代器,是要插入的pair的迭代器,插入成功指向新插入的pair,失败就说明原先就有了相同key的pair,就指向原先的pair,不管插入成功与否都指向我们指定的key的pair,上面代码每次都是插入一种水果计数为1,如果返回的pair的second=false说明原先就有,计数++达到计数的效果。
这里不管成功与否都会指向要统计的水果,不管那种情况只查找了一次,比方案一更优。
map还重载了[]
以key值为索引返回value值的引用
实现这样功能的方法就是以下对insert的使用
可以把说明复杂的代码分解一下
auto pair=insert(make_pair(k,mapped_type()));
auto kv=*(pair.first);
kv.second;
我们要操作指定key对应的value值 直接利用上面就讲过的insert的特性,插入一个pair,第一个值first是我们指定的key,second直接用类型的默认构造就好了,返回的pair第一个值是迭代器指向我们指定key的pair,我们不用到返回pair的second,就对指向的pair解引用拿到我们要找的有指定key的pair,来操作里面的second,从而达到了用key索引返回value的目的。
利用[]我们可以设计出统计水果的方案三
for (auto& e : arr)
countmap[e]++;
pair的second是int类型,默认构造就是初始化为0,插入一种水果,对它的计数++,代码简洁效率又高。
multimap的说明
multimap就是可以存重复key值的特殊map,还有一点区别就是multimap中没有重载[],因为可能有多个相同key值的pair,这时就不知道要返回那个的value了。
上面对水果计数三种方案的效果都如下图
既然已经计数了,再想想如果根据计数来排序,显示员工最喜欢的前k中水果。
int main()
{
vector<string>v = { "苹果","香蕉","草莓","香蕉","苹果","苹果","西瓜","橘子","西瓜","西瓜","西瓜" };
GetFavoriteFruit(v, 3);
return 0;
}
用一个GetFavoriteFruit函数来计数和显示排名前k的水果。
void GetFavoriteFruit(const vector<string>& fruits, size_t k) //显示员工最喜欢吃的前k中水果
{
map<string, int>m; //这里就用到了前面计数的方案三
for (auto& e : fruits)
m[e]++;
vector<pair<string, int>>v(m.begin(), m.end());
sort(v.begin(), v.end(), congreat());
for (int i = 0; i < k; ++i)
cout << v[i].first << " " << v[i].second << endl;
用排序方式, 方式1 先用map统计各种水果的投票,在把map中的元素拷贝到vector中进行排序,用仿函数控制排序方式,algorithm中的sort必须传入随机迭代器,而map中的迭代器是双向迭代器,所以可以把map中一个个的pair数据赋值到vector中。
struct congreat
{
bool operator()(const pair<string, int>& L, const pair<string, int>& R)
{
return L.second > R.second;
}
};
把map中的数据pair一个个挪到vector中起始开销挺大的,vector中也可以存map的迭代器,用仿函数控制给迭代器排序,然后遍历迭代器也可以达到排序效果
struct itgreat
{
bool operator()(const map<string, int>::iterator& L, const map<string, int>::iterator& R)
{
return L->second > R->second;
}
};
vector<map<string, int>::iterator>v;
auto it = m.begin();
while (it != m.end())
{
v.push_back(it);
++it;
}
sort(v.begin(), v.end(), itgreat());
for (int i = 0; i < k; ++i)
{
cout << v[i]->first << " " << v[i]->second << endl;
}
也可以使用堆的方式对map中的pair或者迭代器排序,达到效果。
struct conless
{
bool operator()(const pair<string, int>& L, const pair<string, int>& R)
{
return L.second < R.second;
}
};
priority_queue<pair<string, int>, vector<pair<string, int>>, conless>q;
// 用堆来排序,1 同样堆可以存map的元素,或者map的迭代器,用仿函数分别控制排序方式
for (auto& e : m)
{
q.push(e);
}
while (k--)
{
auto& top = q.top();
cout << top.first << " " << top.second << endl;
q.pop();
}
struct itless
{
bool operator()(const map<string, int>::iterator& L, const map<string, int>::iterator& R)
{
return L->second < R->second;
}
};
priority_queue<map<string, int>::iterator, vector<map<string, int>::iterator>, itless>q; //2 堆中存map迭代器
auto it = m.begin();
while (it != m.end())
{
q.push(it);
++it;
}
while (k--)
{
auto& top = q.top();
cout << top->first << " " << top->second << endl;
q.pop();
}
最后还可以用multimap来操作,先用map统计好数据后,让后让map中的pair的first去作为要插入到multimap中的pair的second,second就作为first。
multimap<int, string, greater<int>>sortm;
//把统计好数据的map m的数据又放到multimap中用投票数作key,改变仿函数,中序遍历打印出降序来,达到同样的效果,用multimap处理有票数相等的情况
for (auto& e : m)
{
sortm.insert(make_pair(e.second, e.first));
}
auto it = sortm.begin();
while (k--)
{
cout << it->second << " " << it->first << endl;
++it;
}
最终效果都是