序列式容器和关联式容器
C++STL包含了序列式容器和关联式容器:
序列式容器 里面存储的是元素本身,其底层为线性序列的数据结构。比如:vector,list,deque,forward_list(C++11)等。
关联式容器 里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。比如:set、map、unordered_set、unordered_map等。
注意: C++STL当中的stack、queue和priority_queue属于容器适配器,它们默认使用的基础容器分别是deque、deque和vector。
树形结构和哈希结构
根据应用场景的不同,C++STL总共实现了两种不同结构的关联式容器:树型结构和哈希结构。
其中,树型结构容器中的元素是一个有序的序列,而哈希结构容器中的元素是一个无序的序列。
C++标准库提供了四种主要的树形结构关联式容器:set
、multiset
、map
和 multimap
。这些容器都是基于树的数据结构,通常使用红黑树实现,用于存储键值对,并提供高效的查找和排序功能。以下是对这四种容器的详细解释:
set:
set 是一个集合容器,它存储唯一的键值(key),不允许重复的键。
元素按照键值自动排序(通常升序),这使得查找和插入操作都非常高效。
提供了 insert、erase、find 等操作来管理元素集合。
multiset:multiset 与 set 类似,但允许存储重复的键值。因此,它可以包含相同的键多次。
元素按照键值自动排序,查找和插入操作也非常高效。
提供了 insert、erase、find 等操作。
map:map 是一个键值对容器,每个键都唯一对应一个值。
元素按照键值自动排序,通常按照键的升序顺序排列。
提供了 insert、erase、find 等操作来管理键值对。
multimap:multimap 与map 类似,但允许存储相同的键对应不同的值,因此一个键可以对应多个值。
元素按照键值自动排序,查找和插入操作非常高效。
提供了 insert、erase、find 等操作。
这些容器的共同特点是它们的元素是按照键值有序存储的,这使得它们适用于需要快速查找和自动排序的情况。它们使用平衡二叉树(通常是红黑树)来实现这些功能,保持了插入、删除和查找的平均时间复杂度为 O(log N)
。
键值对
键值对是用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
比如我们若是要建立一个英译汉的字典,那么该字典中的英文单词与其对应的中文含义就是一一对应的关系,即通过单词可以找到与其对应的中文含义。
在SGI-STL中关于键值对的定义如下:
template <class T1, class T2>
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)
{}
};
set
set的介绍
1.set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。
2.set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。
3.与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对。
4.set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
5.在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较。
6.set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代。
7.set底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。
set的模板参数列表
T:表示存储在set容器中的元素的数据类型,set会存储类型为T的元素。
Compare:表示用于比较元素之间的顺序的比较函数。默认情况下,它采用less<T>,使用元素的小于运算符<来进行比较。你可以提供自定义的比较函数,以定义元素之间的排序规则。这个比较函数必须是一个可调用的二元谓词,它接受两个参数(const T&类型)并返回一个bool值,指示它们的顺序。
Alloc:表示用于分配和管理内存的分配器类型,也就是元素空间的管理方式。默认情况下,它采用std::allocator<T>,这是C++标准库提供的分配器。你可以提供自定义的分配器类型,以满足特定的内存管理需求。
成员类型
set的构造
1.explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());:
这是默认构造函数,创建一个空的std::set容器。
参数comp用于指定比较函数,它用于确定元素的顺序。默认情况下,使用key_compare(),即容器类型中指定的比较函数。
参数alloc用于指定分配器,用于管理内存。默认情况下,使用allocator_type(),即容器类型中指定的分配器。
2.template <class InputIterator> set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());:
这是范围构造函数,创建一个std::set容器,并初始化其内容,从范围 [first, last) 中的元素。
参数first和last是迭代器,用于指定范围。容器将包含该范围内的元素。
参数comp和alloc与默认构造函数中的含义相同,用于指定比较函数和分配器。
3.
set (const set& x);
:这是拷贝构造函数,创建一个新的
std::set
容器,并使用另一个set
容器x
的内容初始化它。这个构造函数用于创建一个副本,将
x
中的所有元素复制到新容器中。
例如:
// 使用默认构造函数创建一个空的set
std::set<int> mySet;
// 使用范围构造函数创建一个包含 vector 中元素的set
std::vector<int> vec = {3, 1, 4, 1, 5, 9};
std::set<int> mySet(vec.begin(), vec.end());
// 使用拷贝构造函数拷贝set1所有元素
std::set<int> set1 = {1, 2, 3};
// 使用拷贝构造函数创建一个新的 set,复制 set1 的内容
std::set<int> set2(set1);
赋值运算符重载
set& operator= (const set& x); :
这个赋值运算符用于将一个std::set容器的内容复制给另一个std::set容器。具体来说,它将右操作数(x,另一个std::set容器)的内容复制到左操作数(调用该运算符的std::set容器)中,并返回左操作数的引用。
例如:
std::set<int> set1 = {1, 2, 3};
std::set<int> set2;
// 使用赋值运算符将 set1 的内容复制到 set2
set2 = set1;
set的成员函数
set迭代器
set当中迭代器相关函数如下:
示例:
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s;
//插入元素(去重)
s.insert(1);
s.insert(4);
s.insert(3);
s.insert(3);
s.insert(2);
s.insert(2);
s.insert(3);
//遍历容器方式一(范围for)
for (auto e : s)
{
cout << e << " ";
}
cout << endl; //1 2 3 4
//删除元素方式一
s.erase(3);
//删除元素方式二
set<int>::iterator pos = s.find(1); //查找值为1的元素
if (pos != s.end())
{
s.erase(pos);
}
//遍历容器方式二(正向迭代器)
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl; //2 4
//容器中值为2的元素个数
cout << s.count(2) << endl; //1
//容器大小
cout << s.size() << endl; //2
//清空容器
s.clear();
//容器判空
cout << s.empty() << endl; //1
//交换两个容器的数据
set<int> tmp{ 11, 22, 33, 44 };
s.swap(tmp);
//遍历容器方式三(反向迭代器)
set<int>::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl; //44 33 22 11
return 0;
}
multiset
multiset容器与set容器的底层实现一样,都是平衡搜索树(红黑树),multiset容器和set容器所提供的成员函数的接口都是基本一致的,multiset容器和set容器的唯一区别就是,multiset允许键值冗余,即multiset容器当中存储的元素是可以重复的。
示例:
#include <iostream>
#include <set>
using namespace std;
int main()
{
multiset<int> m;
//插入元素(允许重复)
m.insert(3);
m.insert(2);
m.insert(2);
m.insert(3);
for (auto e : m)
{
cout << e << " ";
}
cout << endl; //2 2 3 3
return 0;
}
由于multiset容器允许键值冗余,因此两个容器中成员函数find和count的意义也有所不同:
map
map的介绍
1.map是关联式容器,它按照特定的次序(按照key来比较)存储键值key和值value组成的元素,使用map的迭代器遍历map中的元素,可以得到有序序列。
2.在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,并取别名为pair。
3.map容器中元素的键值key不能被修改,但是元素的值value可以被修改,因为map底层的二叉搜索树是根据每个元素的键值key进行构建的,而不是值value。
4.在内部,map中的元素总是按照键值key进行比较排序的。当不传入内部比较对象时,map中元素的键值key默认按照小于来比较。
5.map容器通过键值key访问单个元素的速度通常比unordered_map容器慢,但map容器允许根据顺序对元素进行直接迭代。
6.map容器支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
7.map在底层是用平衡搜索树(红黑树)实现的,所以在map当中查找某个元素的时间复杂度为logN。
map的模板参数列表
Key:表示map容器中的键类型,也就是键值对中的键的类型。
T:表示map容器中的值类型,也就是键值对中的值的类型。
Compare:表示比较键的方式的函数对象,默认使用 std::less<Key>,可以根据需要自定义键的比较方式。
Alloc:表示用于分配内存的分配器类型,默认使用 std::allocator<std::pair<const Key, T>>,通常情况下不需要自定义。
成员类型
map的构造
1.empty(默认构造函数):
explicit map(const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
这是std::map的默认构造函数,它创建一个空的std::map对象。
comp 参数是可选的,表示用于比较键的自定义比较函数,默认使用 key_compare(),即使用std::less<Key>。
alloc 参数是可选的,表示用于内存分配的自定义分配器,默认使用 allocator_type(),即使用std::allocator<std::pair<const Key, T>>。
2.range(范围构造函数):
template <class InputIterator> map(InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
这个构造函数允许您从一个范围(由迭代器 first 和 last 定义)初始化std::map容器。
comp 参数是可选的,表示用于比较键的自定义比较函数,默认使用 key_compare()。
alloc 参数是可选的,表示用于内存分配的自定义分配器,默认使用 allocator_type()。
3.copy(拷贝构造函数):
map(const map& x);
这个构造函数用于创建一个新的std::map容器,并以另一个std::map容器 x 的内容进行初始化,即复制x中的键值对到新的容器中。
例如:
// 使用默认构造函数创建一个空的 std::map
std::map<int, std::string> myMap1;
// 使用范围构造函数创建并初始化 std::map
std::map<int, std::string> myMap2 = {{1, "One"}, {2, "Two"}, {3, "Three"}};
// 使用复制构造函数创建一个与 myMap2 相同内容的副本
std::map<int, std::string> myMap3(myMap2);
赋值运算符重载
将一个
std::map
的内容复制到另一个已存在的std::map
中。
例如:
std::map<int, std::string> sMap = {{1, "One"}, {2, "Two"}};
std::map<int, std::string> tMap;
tMap = sMap; // 使用拷贝赋值运算符将内容复制到目标容器
map的成员函数
示例:
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//获取容器中元素的个数
cout << m.size() << endl; //3
//容器中key值为2的元素个数
cout << m.count(2) << endl; //1
//清空容器
m.clear();
//容器判空
cout << m.empty() << endl; //1
//交换两个容器中的数据
map<int, string> tmp;
m.swap(tmp);
return 0;
}
map迭代器
map当中迭代器相关函数如下:
示例:
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//用正向迭代器进行遍历
map<int, string>::iterator it = m.begin();
while (it != m.end())
{
cout << "<" << it->first << "," << it->second << ">" << " ";
it++;
}
cout << endl; //<1,one> <2,two> <3,three>
//用反向迭代器进行遍历
map<int, string>::reverse_iterator rit = m.rbegin();
while (rit != m.rend())
{
cout << "<" << rit->first << "," << rit->second << ">" << " ";
rit++;
}
cout << endl; //<3,three> <2,two> <1,one>
//用范围for进行遍历
for (auto e : m) {
cout << "<" << e.first << "," << e.second << ">" << " ";
}
cout << endl; //<1,one> <2,two> <3,three>
return 0;
}
注意:
编译器在编译时会自动将范围for替换为迭代器的形式,因此支持了迭代器实际上就支持了范围for。
multimap
multimap容器与map容器的底层实现一样,也都是平衡搜索树(红黑树),multimap容器和map容器所提供的成员函数的接口都是基本一致的,multimap容器和map容器的区别与multiset容器和set容器的区别一样,multimap允许键值冗余,即multimap容器当中存储的元素是可以重复的。
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
multimap<int, string> m;
//插入元素(允许重复)
mm.insert(make_pair(2, "two"));
mm.insert(make_pair(2, "double"));
mm.insert(make_pair(1, "one"));
mm.insert(make_pair(3, "three"));
for (auto e : m)
{
cout << "<" << e.first << "," << e.second << ">" << " ";
}
cout << endl; //<1,one> <2,two> <2,double> <3,three>
return 0;
}
由于multimap容器允许键值冗余,因此两个容器中成员函数find和count的意义也有所不同:
注意:
由于multimap容器允许键值冗余,调用[ ]运算符重载函数时,应该返回键值为key的哪一个元素的value的引用存在歧义,因此在multimap容器当中没有实现[ ]运算符重载函数。