校招准备系列4-STL容器、算法

STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它是ANSI/ISO C++标准中的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。堆、栈、队列、链表,算法包括查找、排序、排列、集合操作。
STL不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等

STL相关问题

https://www.cnblogs.com/raichen/p/5817158.html

vector内存如何管理,空间是否会自动减小,是否可以显式减小?

不会自动减小,可以用swap来减小空间。

智能指针,区别,实现,可放入容器的类型约束,如果放入了会怎么样

STL的容器由于要支持添加,排序等,所以元素之间 经常需要复制,所以想要放入STL标准容器的对象,都必须能拷贝及赋值。如果不能存入对象,可以考虑存储对象的指针。

auto_ptr可以做vector的元素呢?为什么?

不能。因为STL的标准容器规定它所容纳的元素必须是可以拷贝构造和可被转移赋值的。而auto_ptr不能,可以用shared_ptr智能指针代替。

顺序容器

概述
vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
deque:双端队列。支持快速随机访问。在头、尾部插入/删除速度很快
list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
forward_list:单向链表。
array(C++11新增):固定大小数组。支持快速随机访问。不能添加或删除元素
string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。
特性

vector和string

底层实现:底层数据结构为数组
将元素保存在连续的内存空间中。因为是连续存储,能非常好的支持随机存取,下标运算是非常快速的,O(1)。
在中间位置或头部插入/删除元素会非常耗时,需要移动插入/删除位置之后的所有元素。添加元素甚至可能会导致要分配额外的存储空间,这时,所有元素都要移动到新的存储空间中。
在尾部插入/删除元素比较快。
vector在堆中分配内存,元素连续存放(即vector中保存的元素是在堆上,而vector变量本身是在栈上)。会预先分配多于初始vector大小的内存。当size超过容量capacity时,会扩容成2倍。一旦扩容,即使进行clear,erase操作,内存也不会释放,除非用swap。
对中间和开始处进行添加删除元素操作需要移动内存,如果你的元素是结构或是类,那么移动的同时还会进行构造和析构操作,所以性能不高 (最好将结构或类的指针放入vector中,而不是结构或类对象本身,这样可以避免移动时的构造与析构)。

capacity()返回vector所能容纳的元素数量(在不重新分配内存的情况下)
size()是当前元素个数(string还有个length(),和size()是一样的)

list和forwoar_list

底层实现:底层数据结构为双向链表
在任何位置的插入/删除操作都很快,O(1)。
但是随机访问元素的成本很高,需要遍历整个容器,O(n)。(除了头和尾元素的访问比较快)
list是双向链表,元素也是在堆中存放。
list没有空间预留习惯,所以每插入一个元素都会从内存中分配空间,每删除一个元素都会释放它占用的内存。
list在哪里添加删除元素性能都很高,不需要移动内存,当然也不需要对每个元素都进行构造与析构了,所以常用来做随机操作容器。

deque

底层实现:底层数据结构为一个中央控制器和多个缓冲区
支持快速的随机访问,但是没有vector快
在中间位置插入/删除元素会非常耗时
在两端插入/删除元素很快,与list或forward_list速度相当。
deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:

[堆1] --> [堆2] -->[堆3] --> …
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品。

在标准库中vector和deque提供几乎相同的接口,在结构上它们的区别主要在于这两种容器在组织内存上不一样,deque是按页或块来分配存储器的,每页包含固定数目的元素.相反vector分配一段连续的内存,vector只是在序列的尾段插入元素时才有效率,而deque的分页组织方式即使在容器的前端也可以提供常数时间的insert和erase操作,而且在体积增长方面也比vector更具有效率

array

底层实现:数组
是一种更安全、更容易使用的数组类型
大小固定
不支持插入/删除元素及改变容器大小的操作。

stack

底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

queue

底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)

讨论
vector每次insert或erase之后,以前保存的iterator会不会失效?

理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存
但是vector一般底层是用数组实现的,我们仔细考虑数组的特性,不难得出另一个结论
insert时,假设insert位置在p,分两种情况:
a) 容器还有空余空间,不重新分配内存,那么p之前的迭代器都有效,p之后的迭代器都失效
b) 容器重新分配了内存,那么p之后的迭代器都无效咯
erase时,假设erase位置在p,则p之前的迭代器都有效并且p指向下一个元素位置(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效

vector中的reserve函数和resize函数的区别?

reserve并不改变容器内实际元素数量,它仅影响vector预先分配多大的内存空间,而且只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量,此时一般将容量扩充1倍;
resize只改变容器中元素的数目,而不是容器的容量。

vector扩容

固定长度扩容 VS 成倍扩容
https://blog.csdn.net/dengheCSDN/article/details/78985684

vector扩容的最佳倍数为什么在1~2之间?
https://blog.csdn.net/dengheCSDN/article/details/78985684

如果以 大于2 倍的方式扩容,下一次申请的内存会大于之前分配内存的总和,导致之前分配的内存不能再被使用。
当 k =1.5 时,在几次扩展以后,可以重用之前的内存空间了。

关联容器

概述

按关键字有序保存元素
map:关联数组;保存key-value对
set:key即value,即只保存key的容器
multimap:key可重复出现的map
multiset:key可重复出现的set
无序集合
unordered_map:用哈希函数组织的map
unordered_set:用哈希函数组织的set
unordered_multimap:用哈希函数组织的map,key可重复
unordered_multiset:用哈希函数组织的set,key可重复

特性

set、map、multiset、multimap内部实现是用红黑树(一种非常高效的平衡二叉查找树,也称为RB树)。RB树的统计性能要好于一般的 平衡二叉树。最坏情况下的插入、删除、查找时间是O(logn)

unordered_set、unordered_map、unordered_multiset、unordered_multimap底层数据结构为hash表。查找元素的时间是O(1),但是如果hash函数选择不好的话(碰撞过多)可能会退化成O(n)。插入删除也是O(1)的

unordered_map等hash_XXX是非STL标准的,在C++11加入?存疑

set, multiset

set和multiset会根据特定的排序准则(operator< 决定)自动将元素排序,set中元素不允许重复,multiset可以重复。
因为是排序的,所以set中的元素不能被修改,只能删除后再添加。(set::iterator is const)
set中判断元素是否相等:
if(!(A

map, multimap

map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序,map中元素的key不允许重复,multimap可以重复。
因为是排序的,所以map中元素的key不能被修改,只能删除后再添加。key对应的value可以修改。
向map中添加的元素的key类型必须重载<操作符用来排序。排序与set规则一致。
讨论

hash_map和map的区别在哪里?

构造函数:hash_map需要hash函数,operator==;map只需要比较函数(operator<).
存储结构:hash_map采用hash表存储,map一般采用 红黑树(RB Tree) 实现。因此其memory数据结构是不一样的。
什么时候需要用hash_map,什么时候需要用map?

总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。
现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。

为何map和set不能像vector一样有个reserve函数来预分配数据?

map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map声明的时候从参数中传入的Alloc。例如:
map, Alloc > intmap;
这时候在intmap中使用的allocator并不是Alloc, 而是通过了转换的Alloc,具体转换的方法时在内部通过Alloc::rebind重新定义了新的节点分配器,详细的实现参看彻底学习STL中的Allocator。
其实你就记住一点,在map和set里面的分配器已经发生了变化,reserve方法你就不要奢望了。
map的底层是一棵红黑树,在对节点的插入或是删除操作中,通过旋转来保持平衡性,最坏情况下的插入、删除、查找时间是O(logn)

迭代器失效

容器的插入insert和erase操作可能导致迭代器失效,对于erase操作不要使用操作之前的迭代器,因为erase的那个迭代器一定失效了,正确的做法是返回删除操作时候的那个迭代器。

STL容器中,在迭代器删除元素时会发生什么?

可能发生迭代器失效

容器的插入insert和erase操作可能导致迭代器失效,对于erase操作不要使用操作之前的迭代器,因为erase的那个迭代器一定失效了,正确的做法是返回删除操作时候的那个迭代器。

容器类 插入 删除
list 所有迭代器不失效 有且仅有被删除节点的迭代器失效
vector vector的容量(capacity)未改变时,仅插入位置之后的迭代器失效 被删除节点之后的迭代器失效
set/map 所有迭代器不失效 有且仅有被删除节点的迭代器失效
deque 在头尾位置插入元素,可能指向其他元素的迭代器失效。 在除头尾位置外删除元素,所有迭代器失效。在除头尾位置外插入元素,其他迭代器失效。

STL算法

快排算法的枢轴位置是怎么选择的?

三点中值法,取整个序列的头、尾、中央三个位置的元素,以其中值作为枢轴。

简单说一下next_permutation和partition的实现?
  • next_permutation(下一个排列)
    首先,从最尾端开始往前寻找两个相邻元素,另第一个元素为i,第二个元素为ii,且满足i
  • partition
    令头端迭代器first向尾部移动,尾部迭代器last向头部移动。当first所指的值大于或等于枢轴时就停下来,当last所指的值小于或等于枢轴时也停下来,然后检验两个迭代器是否交错。如果first仍然在last左边,就将连着元素互换,然后各自调整一个位置(向中央逼近),再继续进行相同的行为。如果发现两个迭代器叫错了,表示整个序列已经调整完毕。
partial_sort算法

接受一个middle的index,该middle位于[first, last)的元素序列范围内,然后重新安排[first, last),使得序列中的middle-first个最小元素以指定顺序排序最终放置在[first, middle)中, 其余的元素安置在[middle, last)内,不保证有任何指定的顺序。

你可能感兴趣的:(C++,校招,C++,校招)