0 前言
STL有六大组件,但主要包含容器、迭代器和算法三个部分。
- 容器(Containers):用来管理某类对象的集合。每一种容器都有其优点和缺点,所以,为了应付程序中的不同需求,STL 准备了3类共7+种基本容器类型。
- 迭代器(Iterators):用来在一个对象集合的元素上进行遍历动作。这个对象集合或许是个容器,或许是容器的一部分。每一种容器都提供了自己的迭代器,而这些迭代器了解该种容器的内部结构。
- 算法(Algorithms):用来处理对象集合中的元素,比如 Sort,Search,Copy,Erase 那些元素。通过迭代器的协助,我们只需撰写一次算法,就可以将它应用于任意容器之上,这是因为所有容器的迭代器都提供一致的接口。
容器用来管理某类对象。为了应付程序中的不同需求,STL 准备了两类共七种基本容器类型,还有一类适配器容器:
- 序列式/顺序容器(Sequence containers):此为可序群集,其中每个元素均有固定位置—取决于插入时机和地点,和元素值无关。如果你以追加方式对一个群集插入六个元素,它们的排列次序将和插入次序一致。STL提供了三个序列式容器:向量(vector)、双端队列(deque)、列表(list),此外也可以把 string 和 array 当做一种序列式容器。
- 关联式容器(Associative containers):此为已序群集,元素位置取决于特定的排序准则以及元素值,和插入次序无关。如果你将六个元素置入这样的群集中,它们的位置取决于元素值,和插入次序无关。STL提供了四个关联式容器:集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)
- 适配器容器:stack(栈容器)、queue(队列容器)、priority_queue(优先队列容器))
示意图如下:
1 序列式容器
1.1 vector 向量
vector(向量): 是一种序列式容器,事实上和数组差不多,但它比数组更优越。一般来说数组不能动态拓展,因此在程序运行的时候不是浪费内存,就是造成越界。而 vector 正好弥补了这个缺陷,它的特征是相当于可拓展的数组(动态数组),它的随机访问快,在中间插入和删除慢,但在末端插入和删除快。
特点
- 拥有一段连续的内存空间,每个元素在内存上是连续的,并且起始地址不变,因此它能非常好的支持随机存取,即 [] 操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了 vector 的效率。
- 对头部和中间进行插入删除元素操作需要移动内存,如果你的元素是结构或类,那么移动的同时还会进行构造和析构操作,所以性能不高。
- 对最后元素操作最快(在后面插入删除元素最快),此时一般不需要移动内存,只有保留内存不够时才需要;支持高效的随机访问和在尾端插入/删除操作,但其他位置的插入/删除操作效率低下。
用vector存储自定义类对象时,自定义类对象须满足:
a、有可供调用的无参构造函数(默认的或自定义的);
b、有可用的拷贝赋值函数(默认的或自定义的)
优缺点和适用场景
-
优点:支持随机访问,即 [] 操作和 .at(),所以查询效率高。
-
缺点:当向其头部或中部插入或删除元素时,为了保持原本的相对次序,插入或删除点之后的所有元素都必须移动,所以插入的效率比较低。
-
适用场景:适用于对象简单,变化较小,并且频繁随机访问的场景。
函数/例子
//vector主要成员函数
vector<int> v1{10,2};
empty()://判断当前向量容器是否为空
size()://返回当前向量容器中的实际元素个数
[]://返回指定下标的元素
reserve(n)://为当前向量容器预分配n个元素的存储空间
capacity()://返回当前向量容器在重新进行内存分配以前所能容纳的元素个数
resize(n)://调整当前向量容器的大小,使其能容纳n个元素
push_back(item)://在当前向量容器尾部添加一个元素
insert(pos,elem)://在pos位置插入元素elem,即将元素elem插入到迭代器pos指定的元素之前
front()://获取当前向量容器的第一个元素
back()://获取当前向量容器的最后一个元素
erase()://删除当前向量容器中某个迭代器或者迭代器区间指定的元素
clear()://删除当前向量容器中的所有元素
begin()://该函数的两个版本分别返回iterator或者const_iterator,引用容器的第一个元素
end()://该函数的两个版本分别返回iterator或者const_iterator,引用容器的最后一个元素后面的一个位置
rbegin()://该函数的两个版本分别返回reverse_iterator或者const_reverse_iterator,引用容器的最后一个元素
rend()://该函数的两个版本分别返回reverse_iterator或者const_reverse_iterator,引用容器的第一个元素前面的第一个位置
如:
int main() {
myv.push_back(1);
myv.push_back(2);
myv.push_back(3);
// 正向输出所有元素
vector<int>::iterator it;
myv.insert(myv.begin(), 0);
for (it = myv.begin(); it != myv.end(); ++it)
{
cout << *it << endl;
}
// 反向输出所有元素
vector<int>::reverse_iterator it1;
for (it1 = myv.rbegin(); it1 != myv.rend(); it1++)
{
cout << *it1 << endl;
}
return 0;
}
1.2 deque 双端队列
deque(double-ended queue)是由一段一段的定量连续空间构成。一旦要在 deque 的前端和尾端增加新空间,便配置一段定量连续空间,串在整个 deque 的头端或尾端。因此不论在尾部或头部安插元素都十分迅速。 在中间部分安插元素则比较费时,因为必须移动其它元素。deque 的最大任务就是在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。
双端队列容器由若干个块构成,每个块中的元素的地址是连续的,但是块的地址是不连续的
deque容器不像vector那样把所有的元素保存在一个连续的内存块,而是采用多个连续的存储块存放数据元素,所以空间的重新分配要比vector快,因为重新分配空间后原有的元素不需要复制
连续存储结构,即其每个元素在内存上也是连续的,类似于vector,不同之处在于,deque提供了两级数组结构,第一级完全类似于vector,代表实际容器;另一级维护容器的首位地址。 这样,deque除了具有vector的所有功能外,还支持高效的首端插入/删除操作。
特点
按页或块来分配存储器的,每页包含固定数目的元素。deque 是 list 和 vector 的折中方案。兼有 list 的优点,也有vector 随机线性访问效率高的优点。
优缺点和适用场景
-
优点:支持随机访问,即 [] 操作和 .at(),所以查询效率高;可在双端进行 pop,push。
-
缺点:不适合中间插入删除操作;占用内存多。
-
适用场景:适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景。
函数/例子
// 定义deque双端队列容器的几种方式
deque<int> dp1; // 定义元素为int的双端队列dp1
deque<int> dp2(10); // 指定dp2的初始大小为10个int元素
deque<double> dq3(10,1.23); // 指定dq3的10个初始元素的初始值为1.23
deque<int> dp4(dp2.begin(),dp2.end()) // 用dp2的所有元素初始化dp4
// deque常用函数
empty()://判断双端队列容器是否为空队
size()://返回双端队列容器中的元素个数
front()://取队头元素
back()://取队尾元素
push_front(elem)://在队头插入元素elem
push_back(elem)://在队尾插入元素elem
pop_front()://删除队头一个元素
pop_back()://删除队尾一个元素
erase()://从双端队列容器中删除一个或几个元素
clear()://删除双端队列容器中的所有元素
begin()://该函数的两个版本返回iterator或const_iterator,引用容器的第一个元素
end()://该函数的两个版本返回iterator或const_iterator,引用容器的最后一个元素后面的一个位置
rbegin()://该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的最后一个元素
rend()://该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的第一个元素前面的一个位置
void disp(deque<int> &dp){
deque<int>::iterator iter;
for (iter = myv.begin(); it != myv.end(); ++it)
{
cout << *it << endl;
}
}
deque<int> dp;
dp.push_front(1); // 插入队头
dp.push_back(2);// 插入队尾
1.3 list 链表
List 由双向链表(doubly linked list)实现而成,元素也存放在堆中,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供 [] 操作符的重载。非连续存储结构,具有双链表结构,每个元素维护一对前向和后向指针,因此支持前向/后向遍历。支持高效的随机插入/删除操作,但随机访问效率低下,且由于需要额外维护指针,开销也比较大。
特点
没有空间预留习惯,所以每分配一个元素都会从内存中分配,每删除一个元素都会释放它占用的内存。在哪里添加删除元素性能都很高,不需要移动内存,当然也不需要对每个元素都进行构造与析构了,所以常用来做随机插入和删除操作容器。访问开始和最后两个元素最快,其他元素的访问时间一样。
优缺点和适用场景
-
优点:内存不连续,动态操作,可在任意位置插入或删除且效率高。
-
缺点:不支持随机访问。
-
适用场景:适用于经常进行插入和删除操作并且不经常随机访问的场景。
函数/例子
// 定义list容器的几种方式
list<int> l1; // 定义元素为int的链表l1
list<int> l2(10); // 指定链表l2的初始大小为10个int元素
list<double> l3(10,1.23); // 指定l3的10个初始元素的初值为1.23
list<int> l4(a,a+5); // 用数组a[0...4]共5个元素初始化l4
empty()://判断链表容器是否为空
size()://返回链表容器中的实际元素个数
push_back(elem)://在链尾尾部插入元素elem
pop_back()://删除链表容器最后一个元素
remove()://删除链表容器中所有指定值的元素
remove_if(cmp)://删除链表容器中满足条件的元素
erase()://从链表容器中删除一个或几个元素
unique()://删除链表容器中相邻的重复元素
clear()://删除链表容器中的所有元素
insert(pos,elem)://在pos位置插入元素elem,即将元素elem插入到迭代器pos指定的元素之前
insert(pos,n,elem)://在pos位置插入n个元素elem
insert(pos,pos1,pos2)://在迭代器pos处插入[pos1,pos2)的元素 lst.inset(it,start,end)
reverse()://反转链表
sort()://对链表容器中的元素排序
begin()://该函数的两个版本返回iterator或const_iterator,引用容器的第一个元素
end()://该函数的两个版本返回iterator或const_iterator,引用容器的最后一个元素后面的一个位置
rbegin()://该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的最后一个元素
rend()://该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的第一个元素前面的一个位置
void disp(list<int> &dp){
list<int>::iterator iter;
for (iter = lst.begin(); it != lst.end(); ++it)
{
cout << *it << endl;
}
}
vector V.S. deque V.S. list
a、若需要随机访问操作,则选择vector;
b、若已经知道需要存储元素的数目, 则选择vector;
c、若需要随机插入/删除(不仅仅在两端),则选择list;
d、只有需要在首端进行插入/删除操作的时候,才选择deque,否则都选择vector;
e、若既需要随机访问,又需要随机插入/删除,则需要在vector与list间做个折中;
f、当要存储的是大型类对象时,list要优于vector,当然这时候也可以用vector来存储。
capacity V.S size
a、capacity是容器需要增长之前,能够盛的元素总数;只有连续存储的容器才有capacity的概念(例如vector,deque,string),list不需要capacity;
b、size是容器当前存储的元素的数目;
c、vector默认的容量初始值,以及增长规则依赖于编译器;
d、vector与deque的迭代器支持算术运算,list的迭代器只能进行++/–操作,不支持普通的算术运算。
1.4 string 字符串容器
string是一个保存字符序列的容器,它的所有元素为字符类型,类似vector,因此除了有字符串的一些常用操作外,还包含了所有序列容器的操作,字符串的常用操作包括增加,删除,修改,查找,比较,连接,输入,输出等,string重载了许多运算符,包括+,+=,<,=,[],<<和>>等,
函数/例子
// 创建string容器的几种方式
string()://创建一个空的字符串
string(const string& str)://用字符串str建立当前字符串
string(const string& str,size_type str_idx)://用字符串str起始于str_idx的字符建立当前字符串
string(const string& str,size_type str_idx,size_type str_num)://用字符串str起始于str_idx的str_num个字符建立当前字符串
string(const char* cstr)://用C-字符串cstr建立当前字符串
string(const char* chars,size_type chars_len)://用C-字符串cstr开头的chars_len个字符建立当前字符串
string(size_type num,char c)://用num个字符c建立当前字符串
例如:
char cstr[] = "China! Greate Wall";
string s1(cstr); // China! Greate Wall
string s2(s1); // China! Greate Wall
string s3(cstr,7,11); // Grate Wall
string s4(cstr,6); // China!l
string s5(5,'A'); // AAAAA
//string常用函数(size_type在不同的机器上长度是可以不同的,并非固定的长度,一般通常size_type为ungisned int类型)
empty()://判断当前字符串是否为空串
size()://返回当前字符串的实际字符个数(返回结果为size_type类型)
length()://返回当前字符串的实际字符个数
[index]://返回当前字符串位于index位置的字符,index从0开始
s(index)://返回当前字符串位于index位置的字符
compare(const string& str)://返回当前字符串与字符串str的比较结果,在比较时,若两者相等,返回0,若前者小于后者返回-1,否则返回1
append(cstr)://在当前字符串的末尾添加一个字符串str
insert(size_type index,const string & str)://在当前字符串的index处插入一个字符串str
find(string& s,size_type pos)://从当前字符串中的pos位置开始查找字符串s的第一个位置,找到后返回其位置,没有找到返回-1
replace(size_type index,size_type len,const string& str)://将当前字符串中起始于index的len个字符用一个字符串str替换
replace(iterator begin,iterator end,const string& str)://将当前字符串中由迭代器begin和end所指区间的所有字符用一个字符串替换
substr(size_type index);//返回当前字符串起始于index的子串
substr(size_type index,size_type len)://返回当前字符串起始于index的长度为len的子串
clear()://删除当前字符串中的所有字符
erase()://删除当前字符串中所有字符
erase(size_type index)://删除当前字符串从index开始的所有字符
erase(size_type index,size_type len)://删除当前字符串从index开始的len个字符
2 关联式容器
2.1 set 集合
set(集合)由红黑树实现,其内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复。
特点
set 中的元素都是排好序的,集合中没有重复的元素;map 和 set 的插入删除效率比用其他序列容器高,因为对于关联容器来说,不需要做内存拷贝和内存移动。
另外 Multiset 和 set 相同,只不过它允许重复元素,也就是说 multiset 可包括多个数值相同的元素。
优缺点和适用场景
-
优点:使用平衡二叉树实现,便于元素查找,且保持了元素的唯一性,以及能自动排序。
-
缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
-
适用场景:适用于经常查找一个元素是否在某群集中且需要排序的场景。当 set 集合中的元素为结构体时,该结构体必须实现运算符 ‘<’ 的重载
函数/例子
// set/multiset常用成员函数
empty()://判断容器是否为空
size()://返回容器中的实际元素个数
insert()://插入元素
erase()://从容器中删除一个或几个元素
clear()://删除所有元素
count(k)://返回容器中关键字k出现的次数
find(k)://如果容器中存在关键字为k的元素,返回该元素的迭代器,否则返回end()值
upper_bound()://返回一个迭代器,指向关键字大于k的第一个元素
lower_bound()://返回一个迭代器,指向关键字不小于k的第一个元素
begin()://用于正向迭代,返回容器的第一个元素
end()://用于正向迭代,返回容器的最后一个元素后面的一个位置
rbegin()://用于反向迭代,返回容器的最后一个元素的位置
rend()://用于反向迭代,返回容器的第一个元素前面的一个位置
#include <iostream>
#include <set>
#include <string>
using namespace std;
struct People
{
string name;
int age;
bool operator <(const People p) const
{
return age < p.age;
}
};
int main(int argc, char* argv[])
{
set<People> setTemp;
setTemp.insert({"张三",14});
setTemp.insert({ "李四", 16 });
setTemp.insert({ "隔壁老王", 10 });
set<People>::iterator it;
for (it = setTemp.begin(); it != setTemp.end(); it++)
{
printf("姓名:%s 年龄:%d\n", (*it).name.c_str(), (*it).age);
}
return 0;
}
/*
输出结果
姓名:王二麻子 年龄:10
姓名:张三 年龄:14
姓名:李四 年龄:16
*/
可以看到结果是按照年龄由小到大的顺序排列。另外 string 要使用c_str()转换一下,否则打印出的是乱码。
2.1 map 映射
map 由红黑树实现,其元素都是 “键值/实值” 所形成的一个对组(key/value pairs)。每个元素有一个键,是排序准则的基础。每一个键只能出现一次,不允许重复。
map 主要用于资料一对一映射的情况,map 内部自建一颗红黑树,这颗树具有对数据自动排序的功能,所以在 map 内部所有的数据都是有序的。比如一个班级中,每个学生的学号跟他的姓名就存在着一对一映射的关系。
特点
自动建立 Key - value 的对应。key 和 value 可以是任意你需要的类型。根据 key 值快速查找记录,查找的复杂度基本是 O(logN),如果有 1000 个记录,二分查找最多查找 10次(1024)。增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改 key。
multimap 和 map 相同,但允许重复元素,也就是说 multimap 可包含多个键值(key)相同的元素。
优缺点和适用场景
-
优点:使用平衡二叉树实现,便于元素查找,且能把一个值映射成另一个值,可以创建字典。
-
缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
-
适用场景:适用于需要存储一个数据字典,并要求方便地根据key找value的场景。
map和multimap都是映射类模板,映射是实现关键字与值关系的存储结构,可以使用一个关键字key来访问相应的数据值value,set/multimap中的key和value都是key类型,而map/multimap中的key和value是一个pair类结构,
函数/例子
//pair类结构的声明形式如下:
struct pair{
T first;
T second;
}
// pair中的first为第一个分量(在map中对应key),second为第二个分量(在map中对应value)
pair <double,double> p1; // 定义pair对象p1
cin >> p1.first >> p1.second;
// 同时pair对==,!=,<,>,<=,>=共六个运算符进行重载,提供了按照字典序对元素进行大小比较的比较运算符模板函数
// map/multimap的主要成员函数如下
empty()://判断容器是否为空
size()://返回容器中的实际元素个数
map[key]://返回关键字为key的元素的引用,如果不存在这样的关键字,则以key作为关键字插入一个元素(不适合multimap)
insert(elem)://插入一个元素elem并返回该元素的位置
clear()://删除所有元素
find()://在容器中查找元素
count()://容器中指定关键字的元素个数(map中只有1或者0)
begin()://用于正向迭代,返回容器中的第一个元素位置
end()://用于正向迭代,返回容器中最后一个元素的位置
rbegin()://用于反向迭代,返回容器中最后一个元素的位置
rend()://用于反向迭代,返回容器中第一个元素前面的一个位置
// 在map中修改元素
map<char,int> mymap;
mymap['a'] = 1;
// 获取map中的值
int ans = mymap['a'];
#include<map>
map<char, int> mymap;
mymap['a'] = 3;
map<char, int>::iterator it;
for (it = mymap.begin(); it != mymap.end(), ++it) {
cout << it->first, it->second;
}
#include "stdafx.h"
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
map<int, string> mapTemp;
mapTemp.insert({ 5,"张三" });
mapTemp.insert({ 3, "李四"});
mapTemp.insert({ 4, "隔壁老王" });
map<int, string>::iterator it;
for (it = mapTemp.begin(); it != mapTemp.end(); it++)
{
printf("学号:%d 姓名:%s\n", (*it).first, (*it).second.c_str());
}
return 0;
}
/*
输出结果:
学号:3 姓名:李四
学号:4 姓名:隔壁老王
学号:5 姓名:张三
*/
3 适配器容器
除了以上七个基本容器类别,为满足特殊需求,STL还提供了一些特别的(并且预先定义好的)容器配接器,根据基本容器类别实现而成。
适配器容器是指基于其他容器实现的容器,也就是说适配器容器包含另一个容器作为其底层容器,在底层容器的基础上实现适配器容器的功能,实际上在算法设计中可以将适配器容器作为一般容器来使用
3.1 stack
名字说明了一切,stack 容器对元素采取 LIFO(后进先出)的管理策略。
C++栈和数据结构中的栈一样,具有后进先出的特点,栈容器默认的底层容器是deque,用户也可以指定其他底层容器
stack<string,vector<string>> myst; //第二个参数指定底层容器为vector
stack容器只有一个出口,即栈顶,可以在栈顶插入(进栈)和删除(出栈)元素,而不允许顺序遍历,所以stack容器没begin()/end()/rbegin()/rend()迭代器成员函数,stack容器的主要成员为:
empty()://判断栈容器是否为空
size()://返回栈容器中的实际元素个数
push(elem)://元素elem进栈
top()://返回栈顶元素
pop()://元素出栈
#include<stack>
using namespace std;
void main(){
stack<int> st;
st.push(1);st.push(2);st.push(3);
cout << st.top() << endl;
while(!st.empty()){
cout << st.pop() <<endl;
st.pop();
}
}
3.2 queue
queue 容器对元素采取 FIFO(先进先出)的管理策略。也就是说,它是个普通的缓冲区(buffer)。
队列类模板,和数据结构中的队列一样,具有先进先出的特点,queue容器不允许顺序遍历,没有begin()/end()和rbegin()/rend()这样的用于迭代器的成员函数,
// 主要成员函数如下:
empty()://判断队列容器是否为空
size()://返回队列容器中的实际元素个数
front()://返回队头元素
back()://返回队尾元素
push(elem)://元素elem进队
pop()://元素出队
#include<queue>
using namespace std;
void main(){
queue<int> qu;
qu.push(1);qu.push(2);qu.push(3);
cout << "队头元素" << qu.front();
cout << "队尾元素" << qu.back();
while(!qu.empty()){
cout << qu.front() << endl;
qu.pop();
}
}
3.3 priority_queue
priority_queue 容器中的元素可以拥有不同的优先权。所谓优先权,乃是基于程序员提供的排序准则(缺省使用 operators)而定义。Priority queue 的效果相当于这样一个 buffer:“下一元素永远是queue中优先级最高的元素”。如果同时有多个元素具备最髙优先权,则其次序无明确定义。
优先队列是一种具有受限访问操作的存储结构,元素可以以任意顺序进入优先队列,一旦元素在优先队列容器中,出队操作将出队列中优先级最高的元素,
// 主要成员函数
empty()://判断优先队列容器是否为空
size()://返回优先队列容器中的实际元素个数
push(elem)://元素elem进队
top()://获取队头元素
pop()://元素出队
// 优先队列中优先级的高低由队列中数据元素的关系函数(比较运算符)确定,用户可以使用默认的关系函数(对于内置数据类型,默认关系函数是值越大优先级越高),也可以重载自己编写的关系函数
#include<queue>
using namespace std;
void main(){
priority_queue<int> qu;
qu.push(3);qu.push(1);qu.push(2);
cout << qu.top() << "队头元素" << endl;
qu.pop();
}
4 总结
4.1 各容器的特点总结
- vector 头部与中间插入和删除效率较低,在尾部插入和删除效率高,支持随机访问。
- deque 是在头部和尾部插入和删除效率较高,支持随机访问,但效率没有 vector 高。
- list 在任意位置的插入和删除效率都较高,但不支持随机访问。
- set 由红黑树实现,其内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复,且插入和删除效率比用其他序列容器高。
- map 可以自动建立 Key - value 的对应,key 和 value 可以是任意你需要的类型,根据 key 快速查找记录。
在实际使用过程中,到底选择这几种容器中的哪一个,应该根据遵循以下原则:
-
如果需要高效的随机存取,不在乎插入和删除的效率,使用 vector。
-
如果需要大量的插入和删除元素,不关心随机存取的效率,使用 list。
-
如果需要随机存取,并且关心两端数据的插入和删除效率,使用 deque。
-
如果打算存储数据字典,并且要求方便地根据 key 找到 value,一对一的情况使用 map,一对多的情况使用 multimap。
-
如果打算查找一个元素是否存在于某集合中,唯一存在的情况使用 set,不唯一存在的情况使用 multiset。
4.2 各容器的时间复杂度分析
- vector 在头部和中间位置插入和删除的时间复杂度为 O(N),在尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1);
- deque 在中间位置插入和删除的时间复杂度为 O(N),在头部和尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1);
- list 在任意位置插入和删除的时间复杂度都为 O(1),查找的时间复杂度为 O(N);
- set 和 map 都是通过红黑树实现,因此插入、删除和查找操作的时间复杂度都是 O(log N)。
4.3 各容器的共性
各容器一般来说都有下列函数:
默认构造函数、复制构造函数、析构函数、empty()、max_size()、size()、operator=、operator<、operator<=、operator>、operator>=、operator==、operator!=、swap()。
顺序容器和关联容器都共有下列函数:
- begin() :返回容器第一个元素的迭代器指针;
- end():返回容器最后一个元素后面一位的迭代器指针;
- rbegin():返回一个逆向迭代器指针,指向容器最后一个元素;
- rend():返回一个逆向迭代器指针,指向容器首个元素前面一位;
- clear():删除容器中的所有的元素;
- erase(it):删除迭代器指针it处元素。