树形结构关联式容器(map和set,multiset和multimap)
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树形结构关联式容器(map和set,multiset和multimap)相关的知识,希望对你有一定的参考价值。
目录
一.容器介绍
STL容器主要分为序列式容器和关联式的容器。
- 序列式容器:底层为线性序列的数据结构,里面保存的是数据本身。比如vector,list,deque和forward_list。
- 关联式容器:底层右两种不同的结构,树形结构和哈希结构。里面存储的是<key,value>结构的键值对,再数据检索式比序列式容器效率更高。例如:map和set,multiset和multimap等
二.键值对
键值对是用来表示意义对应关系的一种结构,该结构一般只包含两个成员变量key和value。key代表键值,value代表与key对应的信息。一般就是知道key的值,来找value的值。
SGI-STL种关于键值对的定义:
template<class T1,class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
//成员变量
first_type first;
second_type second;
//构造函数
pair()
:first(T1())
, second(T2())
{}
pair(const T1& a, const T2& b)
:first(a)
, second(b)
{}
};
其中结构体pair就代表键值对,pair种有两个成员变量first和second。first就对应key,second对应value。
三.树形结构关联式容器
根据应用场景的不同,STL一共实现了两种关联式容器:树形结构和哈希结构。
树形结构的关联式容器主要有四种:map,set,multiset和multimap。这四种容器的共同点为:底层结构都是使用平衡二叉树(红黑树),容器中的元素是一个有序序列。
3.1 set
T:set中存放元素的类型,实际底层存储<value,value>键值对。
Compare:set中元素的比较方式的类型,默认less<T>
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器(内存池)管理。
3.1.1 set介绍
- set是按照一定次序存储元素的容器。默认是左子树结点值小于根节点值,右子树结点值大于根节点值。如果将第二个参数less<T>,改为greater<T>,可以使得左子树结点值大于根节点值,右子树结点值小于根节点值。
- 在set中,键值对的value就是key,类型为T,并且每一个的value必须是唯一的,不能有数据重复。
- set元素不能再容器中进行修改(元素总数const),但是可以从容器中删除和插入它们。
注意:
- 与map和multimap不同,map和multimap中存储的是正在的键值对<key,value>,set中只放value,但是底层实现实际存放的是由<value,value>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- set中的元素不可以重复(因此可以使用set进行去重)。
- 使用set迭代器遍历set中的元素,可以得到有序序列。可以使用迭代器遍历就可以使用范围for遍历。由于set底层是平衡二叉树,得到有序序列,说明是中序遍历。
- set中的元素默认按照小于来比较(less<T>)。
- set查找某个元素时间复杂度为logN
- set中元素不允许修改,因为修改了可能就不构成平衡二叉树了。
- set底层用平衡二叉树(红黑树)实现。
- set是二叉搜索树K模型的应用。
3.1.2 set的使用
使用set时必须加头文件#include<set>
- set的构造函数
函数声明 | 功能介绍 |
explicit set(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type()) | 构造一个空set。比较方式和空间配置器都是缺省值。explicit关键字修饰不能发生类型转化 |
set(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type()) | 用迭代器[first,last)区间中的元素构造set,比较方式和空间配置器为缺省值。 |
set(const set& x) | set的拷贝构造函数,注意是深拷贝 |
set<int> s1;
int array[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
//迭代器
set<int> s2(array, array + sizeof(array) / sizeof(int));
//修改比较方式
set<int,greater<int>> s3(array, array + sizeof(array) / sizeof(int));
//拷贝构造
set<int> s4(s2);
- set的迭代器
函数声明 | 功能介绍 |
iterator begin() | 返回set中起始位置元素的迭代器 |
iterator end() | 返回set中最后一个元素后面的迭代器 |
const_iterator cbegin()const | 返回set中起始位置元素的const迭代器,迭代器位置值不可以被修改 |
const_iterator cend()const | 返回set中最后一个元素后面的const迭代器,迭代器位置值不可以被修改 |
reverse_iterator rbegin() | 返回set中第一个元素的反向迭代器,即end()的前一个元素位置 |
reverse_iterator rend() | 返回set最后一个元素的下一个位置的反向迭代器,即begin()前一个元素位置。 |
const_reverse_iterator crbegin()const | 返回set中第一个元素的反向const迭代器,即cend()的前一个元素位置 |
const_reverse_iterator crend()const | 返回set最后一个元素的下一个位置的反向迭代器,即cbegin()前一个元素位置。 |
使用迭代器进行遍历
int main(){
int array[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
//迭代器
set<int> s(array, array + sizeof(array) / sizeof(int));
//正向迭代
set<int>::iterator it = s.begin();
while (it != s.end()){
cout << *it << " ";
it++;
}
cout << endl;
//反向迭代
set<int>::reverse_iterator rit = s.rbegin();
while (rit != s.rend()){
cout << *rit << " ";
rit++;
}
cout << endl;
//范围for迭代
for (auto e : s){
cout << e << " ";
}
cout << endl;
getchar();
return 0;
}
输出:
支持迭代器遍历就支持范围for遍历,范围for底层用迭代器实现
- set容量函数
函数声明 | 功能介绍 |
bool empty()const | 判断set是否为空,为空返回true,反之返回false |
size_type size()const | 返回set中有效元素个数 |
- set的修改操作
函数声明 | 功能介绍 |
pair<iterator,bool> insert(const value_type& x) | 再set中插入元素x,实际插的是<x,x>的键值对,如果插入成功,返回<该元素在set中位置迭代器,true>,插入失败,说明已存在,返回<x在set中位置迭代器,false>。 |
void erase(iterator position) | 删除set中position位置的值 |
size_type erase(const value_type& val) | 删除set中值为val的元素,返回删除元素的个数 |
void erase(iterator first, iterator last) | 删除set中[first,last)区间中的元素 |
void swap(set& x) | 交换set和x中的元素 |
void clear() | 将set中的元素清空 |
iterator find(const value_type& val) const | 在set中找打值为val元素的位置,没找到返回end()位置 |
size_type count(const value_type& val) const | 返回set中值为val的元素个数 |
注意:序列式容器中没有find函数,使用的是算法中的find,因为效率低。
int main(){
set<int> s;
int array[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
int len = sizeof(array) / sizeof(array[0]);
//插入
for (int i = 0; i < len; i++){
s.insert(array[i]);
}
set<int>::iterator it = s.find(3);
if (it != s.end()){//没找到返回end()位置,越界了
s.erase(it);//删除某一位置
}
s.erase(4);//删除某一值
s.erase(s.begin(), s.end());//删除区间值
int arr[] = { 15, 11, 12, 10 };
set<int> s1(arr, arr + sizeof(arr) / sizeof(arr[0]));
s.swap(s1);//交换
s.clear();//清空
getchar();
return 0;
}
3.2 map
Key:键值对中key的类型
T:键值对中value的类型
Compare:比较器的类型,map中的元素是按照键值对中的key来比较的,缺省情况下按照小于(less)进行比较。一般情况下,内置类型该参数不需要进行传递,如果无法进行比较时(自定义类型),需要用户编写比较规则后,传递该参数,一般使用函数指针或者仿函数来传递。
Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库的空间配置器。
3.2.1 map介绍
- map是关联式容器,按照特定的次序,比较key来存储键值key和值value组合成的元素。
- 在map中,键值key通常用于排序和唯一的标识元素,而value中存储和键值key关联的内容,键值key和value类型可能不同,在map 内部,key和value通过键值对pair绑定在一起。
- 在map内部是按照比较键值key来进行排序的。
- map支持下标访问,即在[]加入key可以找到value,或者map中没有key会插入key和value。
注意:
- map的元素是键值对,插入时,需要构造键值对插入。键值对中由两个成员,一个first对应key,一个second对应value。
- map中的key是不可以修改的,但是value是可以修改的。因为是按照key来建立树的。
- 默认按照小于(less)方式进行比较
- map用迭代器遍历可以得到一个有序序列。底层平衡二叉树,中序遍历得到一有序序列。
- 查找效率高O(logN)。
- 支持下标[]访问,operator[]实际进行插入和查找。
- map中的元素key值是不重复的,但是不同的key值对应的value可能是重复的。
- 实际map是二叉搜索树的KV模型的应用。
3.2.2 map使用
map使用时需要包含头文件#include<map>
- map的构造函数
函数声明 | 功能介绍 |
explicit map(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type()) | 构造一个空map |
template <class InputIterator> map(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type()); | 将区间[first,last)之间的数构造map |
map(const map& x) | 拷贝构造map,深拷贝 |
#include<iostream>
#include<map>
#include<utility>
#include<functional>
using namespace std;
int main(){
map<int, int> m1;
//迭代器区间拷贝
pair<int, int> array[] = { pair<int, int>(5, 1), pair<int, int>(3, 1), pair<int, int>(4, 1), pair<int, int>(1, 1), pair<int, int>(7, 1), pair<int, int>(8, 1), };
map<int, int> m2(array, array + sizeof(array) / sizeof(array[0]));
//改变比较方式
map<int, int, greater<int>> m3(array, array + sizeof(array) / sizeof(array[0]));
//拷贝构造
map<int, int> m4(m2);
map<int, int, greater<int>> m5(m3);
getchar();
return 0;
}
- map迭代器
和set迭代器相同。
函数声明 | 功能介绍 |
iterator begin() | 返回起始位置元素的迭代器 |
iterator end() | 返回最后一个元素后面的迭代器 |
const_iterator cbegin()const | 返回起始位置元素的const迭代器,迭代器位置值不可以被修改 |
const_iterator cend()const | 返回最后一个元素后面的const迭代器,迭代器位置值不可以被修改 |
reverse_iterator rbegin() | 返回第一个元素的反向迭代器,即end()的前一个元素位置 |
reverse_iterator rend() | 返回最后一个元素的下一个位置的反向迭代器,即begin()前一个元素位置。 |
const_reverse_iterator crbegin()const | 返回第一个元素的反向const迭代器,即cend()的前一个元素位置 |
const_reverse_iterator crend()const | 返回最后一个元素的下一个位置的反向迭代器,即cbegin()前一个元素位置。 |
map遍历:
注意:map保存的是键值对,访问时,访问的时键值对结构体,需要用'.'和"->"操作符访问。
int main(){
//键值对数组,元素都是匿名对象
pair<int, int> array[] = { pair<int, int>(5, 1), pair<int, int>(3, 1), pair<int, int>(4, 1), pair<int, int>(1, 1), pair<int, int>(7, 1), pair<int, int>(8, 1), };
map<int, int> m(array, array + sizeof(array) / sizeof(array[0]));
map<int, int>::iterator it = m.begin();
while (it != m.end()){
//因为保存的是键值对结构体pair,用'.'和'->'操作符访问
cout << it->first << ":" << it->second << endl;
//cout << (*it).first << ":" << (*it).second << endl;
it++;
}
//反向遍历
map<int, int>::reverse_iterator rit = m.rbegin();
while (rit != m.rend()){
cout << rit->first << ":" << rit->second << endl;
rit++;
}
//范围for遍历
for (auto e : m){
cout << e.first << ":" << e.second << endl;
}
getchar();
return 0;
}
- map的容量
函数声明 | 功能介绍 |
bool empty()const | 检测map中元素是否为空,为空返回true,否则返回false |
size_type size()const | 返回map中有效元素的个数 |
- map修改
再次说明,map是比较key来建立平衡二叉树的。
函数声明 | 功能介绍 |
pair<iterator,bool> insert(const value_type& x) | 再map中插入键值对x,注意x是一个键值对,返回值也是键值对。如果插入成功,返回<该元素在set中位置迭代器,true>,插入失败,说明已存在,返回<x在set中位置迭代器,false>。 |
void erase(iterator position) | 删除map中position位置的值 |
size_type erase(const value_type& k) | 删除map中key值为k的元素,返回删除元素的个数 |
void erase(iterator first, iterator last) | 删除map中[first,last)区间中的元素 |
void swap(set& x) | 交换map和x中的元素 |
void clear() | 将map中的元素清空 |
iterator find(const value_type& k) const | 在map中找到key值为k元素的位置,没找到返回end()位置 |
size_type count(const value_type& k) const | 返回map键值key为k的元素个数 |
注意:map保存的时键值对,插入时需要构造键值对插入。
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<utility>
#include<functional>
using namespace std;
int main(){
map<string, string> dic;
//插入
//map元素为键值对,所以要插入键值对,这些都是pair匿名对象
dic.insert(pair<string,string>("sort", "排序"));
dic.insert(pair<string, string>("up", "上"));
dic.insert(pair<string, string>("down", "下"));
dic.insert(pair<string, string>("left", "左"));
dic.insert(pair<string, string>("right", "右"));
dic.insert(pair<string, string>("insert", "插入"));
//使用make_pair插入,不需要键值对的模板参数类型,编译器自动推演
dic.insert(make_pair("north", "北"));
//删除
//位置删除
map<string, string>::iterator it = dic.find("right");
防止没找到越界
if (it != dic.end()){
dic.erase(it);
}
直接删除
dic.erase("insert");
区间删除
dic.erase(dic.begin(), dic.end());
map<string, string> fruit;
fruit.insert(make_pair("苹果","apple"));
fruit.insert(make_pair("桃子", "peach"));
fruit.insert(make_pair("香蕉", "banana"));
//交换
dic.swap(fruit);
dic.clear();
getchar();
return 0;
}
3.2.2 map中operator[]原理
- operator[]的介绍
当键值key的k值存在map中时,返回的是键值对中value的引用。当键值key的k值不存在时,将该键值对<key,value>插入,键值key的值是k,value值是插入value类型的缺省值,返回值是插入键值对的value的引用。
- operator[]使用
- 原理:
map的operator[]的实现是通过insert来实现的。
首先来了解map的nsert。
insert返回值也是一个键值对,
当插入的键值对val不存在时,插入成功,insert返回的键值对中,iterator代表插入成功的键值对在map中的位置,bool为true
当插入的键值对存在时,插入失败,insert返回的键值对中,iterator代表已存在键值对在map中的位置,bool为false。
总结operator[]原理:
由于operator[]是通过insert实现的如上
为什么不用find,find如果没有找到,返回值是end()迭代器,也不会实现插入。
总结operator[]作用:
- 插入,不存在时会插入
- 查找,已存在,返回键值对键值value
- 修改,已存在,修改键值value
一般operator[]用来插入和修改,不用来查找,因为不存在时会插入。
3.2.3 用map记录次数
int main(){
string arr[] = { "苹果", "梨子", "西瓜", "苹果", "香蕉", "梨子", "苹果", "香蕉" };
map<string, int> countMap;
//方法1
//利用find
for (auto str : arr){
map<string, int>::iterator it = countMap.find(str);
if (it != countMap.end()){
it->second++;
}
else{
countMap.insert(make_pair(str, 1));
}
}
//方法2,利用insert
for (auto e : arr){
pair<map<string, int>::iterator, bool> res = countMap.insert(make_pair(e, 1));
if (res.second == false)//插入失败,已存在
{
res.first->second++;//键值对value++
}
}
//方法3,利用operator[]
for (auto e : arr){
countMap[e]++;
}
for (auto e : countMap){
cout << e.first << ":" << e.second << endl;
}
getchar();
return 0;
}
3.3multiset
3.3.1 multiset介绍
- multiset是按照特定顺序存储元素的容器,其中的元素是可以重复的。
- 在multiset中,时间保存的是<value,value>键值对,multiset元素不可以修改,修改了可能就不是二叉搜索树了,所以元素总是const的,但是可以删除和插入multiset容器。
- 在内部,multiset中的元素总是按照内部的比较规则所指定的特定弱排序准则进行排序。
- multiset底层结构为平衡二叉树(红黑树)。
注意:
1. multiset中再底层中存储的是<value, value>的键值对
2. mtltiset的插入接口中只需要插入即可
3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5. multiset中的元素不能修改
6. 在multiset中找某个元素,时间复杂度为O(logN)
7. multiset的作用:可以对元素进行排序
8.使用时与set包含头文件相同
3.3.2 multiset使用
此处只简单演示set与multiset的不同,其他接口接口与set相同
#include<iostream>
#include<set>
using namespace std;
int main(){
int arr[] = { 2, 1, 3, 9, 6, 0, 5, 8, 4, 7, 3, 4, 3 };
multiset<int> ms;
set<int> m;
for (auto e : arr){
ms.insert(e);
m.insert(e);
}
//遍历set
for (auto e : m){
cout << e << " ";
}
cout << endl;
//遍历multiset
for (auto e : ms){
cout << e << " ";
}
cout << endl;
getchar();
return 0;
}
输出:
3.4 multiamp
3.4.1 multimap介绍
multimap和map基本相同,主要的不同是map键值对中的key是不可以重复的,multimap中键值对中的key是可以重复的。
注意:
- 只是说key,没有说value。value可以重复,也可以不重复。
- multimap保存的键值对中的key值不可以修改,但是对应的value可以修改。
- multimap中没有重载operator[],因为如果想按照key去找value,由于multimap中key可以重复,就不知道返回哪个value的引用了。
- 注意保存的是键值对<key,value>
- 使用时与map包含的头文件相同
3.4.2使用
#include<iostream>
#include<map>
using namespace std;
int main(){
pair<int, int> arr[] = { pair<int, int>(1, 0), pair<int, int>(2, 0), pair<int, int>(1, 0), pair<int, int>(2, 0), pair<int, int>(3, 0) };
map<int, int> m(arr, arr + sizeof(arr) / sizeof(arr[0]));
multimap<int, int> mm(arr, arr + sizeof(arr) / sizeof(arr[0]));
//map
for (auto e : m){
cout << e.first << ":" << e.second;
cout << " ";
}
cout << endl;
//multimap
for (auto e : mm){
cout << e.first << ":" << e.second;
cout << " ";
}
cout << endl;
getchar();
return 0;
}
输出:
以上是关于树形结构关联式容器(map和set,multiset和multimap)的主要内容,如果未能解决你的问题,请参考以下文章