容器分类
顺序容器:vector / list / deque / forward_list(C11)
关联容器: set / multiset / map / multimap
无序关联容器(C11): unordered_set / unordered_multiset / unordered_map / unordered_multimap
容器适配器: stack / queue / priority_queue
还可以根据结构来分类,线性和非线性
顺序容器
vector
特点:
1.vector的本质是数组,但是优于数组,数组的大小固定,vector的大小可以动态扩展。
2.拥有一段连续的内存空间,并且起始地址不变,支持随机存取,即 [] 操作符。
3.对头部和中间进行插入删除元素操作需要移动内存,插入位置越靠后,移动的元素越少,效率越高。
4.对尾元素操作最快,此时一般不需要移动内存
动态扩容机制:
当容器的capacity不足时,会进行动态扩容。
执行步骤:分配新空间-复制元素-释放原空间
因此适用vector的最佳场景是知道数据的规模,在初始化vector的时候,设置好capacity容量,就不会触发动态扩容,不会影响性能。
reverse(int)可以设置预留的容器的空间
优缺点及适用场景:
优点:支持随机访问,即 [] 操作和 .at(),所以查询效率高。
缺点:当向其头部或中部插入或删除元素时,为了保持原本的相对次序,插入或删除点之后的所有元素都必须移动,所以插入的效率比较低。
适用于对象简单,变化较小,并且频繁随机访问的场景
操作 | 时间复杂度 |
---|---|
push_back | O(1) |
pop_back | O(1) |
insert | O(n) |
erase | O(n) |
访问[] | O(1) |
list
特点:
1.本质是双向链表,内存空间不连续,元素存放在堆中
2.不支持随机访问,插入删除效率高
3.每分配一个元素都会从内存中分配,每删除一个元素都会释放它占用的内存
适用场景:
适用于频繁插入删除,不经常随机访问的场景
操作 | 时间复杂度 |
---|---|
push_front | O(1) |
push_back | O(1) |
pop_front | O(1) |
pop_back | O(1) |
insert | O(1) |
erase | O(1) |
访问 | O(n) |
deque
特点:
1.按照页或者块来分配存储器,每页包含固定数目的元素
2.支持随机储存
3.头尾删除插入元素效率高
适用场景:
适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景
操作 | 时间复杂度 |
---|---|
push_front | O(1) |
push_back | O(1) |
pop_front | O(1) |
pop_back | O(1) |
insert | O(n) |
erase | O(n) |
访问 | O(1) |
关联容器
关联容器的特点比较相似,因此统一进行记录。
特点:
1.都是由红黑树实现,有序
2.set的元素不能重复,multiset/multimap是可以一对多(一个key对应多个value)
3.增删改查时间复杂度都近似于O(logn)
红黑树与平衡二叉树(AVL树):
平衡树的优点:
同种元素序列情况下的深度最小的二叉排序树。这可以减少二叉树元素查找的深度,从而提升平均查找效率。
AVL树:
(1 )左子树和右子树的深度之差的绝对值不超过1;
(2)左子树和右子树也是平衡二叉树。
执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保存平衡,而因为旋转非常耗时,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况
红黑树:
通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因此,红黑树是一中弱平衡二叉树
相对于要求严格的AVL树来说,它的旋转次数少,插入最多两次旋转,删除最多三次旋转,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树
折中选择红黑树作为关联容器的底层数据结构。
无序关联容器
特点:
1.都是基于哈希表实现,无序
2.查找效率O(1),耗时在构建hash表和解决hash冲突,维护hash表上面
hash表:
根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
hash冲突的解决办法:
1.开放地址法,发生hash碰撞的时候,顺序查下一个位置是否碰撞,直到查完全表或者找到不碰撞的位置,进行使用
2.再次hash法
3.拉链法,一个数组中挂着多个链表的指针,hash碰撞,就把新结点挂在hash值的列表上
4.建立公共溢出区,将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
容器适配器
stack:
1.仅允许在一端进行插入(push)和删除(pop)运算。这一端被称为栈顶,相对地,把另一端称为栈底。
2.插入:向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素
3.删除:从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素
4.最后入栈的一组数据可以最先被取出,这种行为也被叫做LIFO:Last In First Out,即后进先出
stack的基本操作:
1.入栈:如s.push(x)
2.出栈:如 s.pop().注意:出栈操作只是删除栈顶的元素,并不返回该元素
3.访问栈顶:如s.top()
4.判断栈空:如s.empty().当栈空时返回true
5.访问栈中的元素个数,如s.size()
queue:
1.在一端插入,另一端删除
2.FIFO(先进先出)
queue的基本操作:
1.入队,如例:q.push(x); 将x 接到队列的末端。
2.出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
3.访问队首元素,如例:q.front(),即最早被压入队列的元素。
4.访问队尾元素,如例:q.back(),即最后被压入队列的元素。
5.判断队列空,如例:q.empty(),当队列空时,返回true。
6.访问队列中的元素个数,如例:q.size()
priority_queue:
1.默认是最大堆,堆顶是最大的元素
2.queue相当于是先进去的优先级最大的一个priority_queue,因此priority_queue是有序的。
函数声明:priority_queue< type, container, function >
这三个参数,后面两个可以省略,第一个不可以。
type:数据类型
container:实现优先队列的底层容器
function:元素之间的比较方式
第二个参数忽略,默认是使用vector存储,第三个比较函数忽略,默认是最大堆
基本操作同queue
截止到此,第一篇文章关于STL的总结结束了,继续努力总结~