STL标准库面试题(转)
一、vector的底层(存储)机制
二、vector的自增长机制
三、list的底层(存储)机制
四、什么情况下用vector,什么情况下用list
五、list自带排序函数的排序原理
六、deque的底层机制
七、deque与vector的区别
八、map底层机制,查找复杂度,能不能边遍历边插入
九、vector插入删除和list有什么区别
十、hashtable如何避免地址冲突
十一、hashtable、hash_set、hash_map的区别
十二、hash_map与map的区别、什么时候用它们两个?
十三、红黑树有什么性质?
十四、map和set的3个问题
十五、为什么vector插入操作可能导致迭代器失效
十六、hashtable和hashmap的区别
十七、STL底层数据结构实现
十八、STL提供哪六大组件?
十九、auto_ptr可以做vector的元素吗?为什么?
二十、简单说一下next_permutation和partition的实现?
二十一、不允许有遍历行为的容器有哪些?(不提供迭代器)
一、vector的底层(存储)机制
vector就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。
二、vector的自增长机制
当已经分配的空间不够装下数据时,分配双倍于当前容量的存储区,把当前的值拷贝到新分配的内存中,并释放原来的内存。
对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
三、list的底层(存储)机制
以结点为单位存储数据,结点的地址在内存中不一定连续,每次插入或者删除一个元素,就配置或者释放一个元素空间。
四、什么情况下用vector,什么情况下用list
vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但是非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。
list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁。
五、list自带排序函数的排序原理
将前2个元素合并,再将后2个元素合并,然后合并这2个子序列成为4个元素序列的子序列,重复这一过程,得到8个,16个,,,,子序列,最后得到的就是排序后的序列。
时间复杂度O(log n)
六、deque的底层机制
deque动态地以分段连续空间组合而成,随时可以增加一段新的连续空间并链接起来,不提供空间保留功能。
注意:除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。对duque的排序,为了提高效率,可先将deque复制到一个vector上排序,然后再复制到deque。
deque采用一块map(不是STL的map容器)作为主控,其为一小块连续空间,其中每个元素都是指针,指向另一段较大的连续空间(缓冲区)。
deque的迭代器包含4个内容:
1)cur:迭代器当前所指元素
2)first:此迭代器所指的缓冲区的头。
3)last:缓冲区尾。
4)node:指向管控中心。
七、deque与vector的区别
1.vector是单向开口的连续线性空间,deque是双向开口的连续线程空间。
2.deque没有提供空间保留功能,vector则要提供空间保留功能。
3.deque也提供随机访问迭代器,但是其迭代器比vector复杂很多
八、map底层机制,查找复杂度,能不能边遍历边插入
红黑树,自平衡的二叉搜索树。自动排序效果不错。
通过map的迭代器不能修改键值,只能修改其实值。
查找复杂度:O(logN)
不可以,map不像vector,它在容器进行erase操作后不会返回后一个元素的迭代器,不能遍历第往后插删。
九、vector插入删除和list有什么区别
vector插入删除数据,需要对现有数据复制移动,如果vector存储的对象很大或者构造函数很复杂,则开销很大,如果是简单的小数据,效率优于list
list插入和删除数据,需要对现有数据进行遍历,但要在首部插入数据,效率很高。
十、hashtable如何避免地址冲突
1.线性探测:hash函数计算某个元素的插入位置后,如果该位置的空间已经被占用,则继续向下寻找,直到找到一个可用空间为止。
2.二次探测:如果计算的位置被占用,就依次尝试H+1^2,H+2^2 等
3.开链:每一个表格元素中维护一个list,在那个list中执行插入、删除
十一、hashtable、hash_set、hash_map的区别
hash_set以hashtable为底层,不具有排序功能,能快速查找,其键值就是实值。(set以红黑树为底层,具有排序功能)
hash_map以hashtable为底层,没有自动排序功能,能快速查找,每一个元素同时拥有一个实值和键值。(map以红黑树为底层,有排序功能)
十二、hash_map与map的区别、什么时候用它们两个?
构造函数:hash_map需要hash function 和等于函数,而 map需要比较函数(大小或小于)
存储结构:hash_map以hashtable为底层,而map以红黑树为底层。
总的来说,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logN级别。但不一定常数就比log小,而且hash_map还有hash function耗时。
如果考虑效率,特别当元素达到一定数量级时,用hash_map
考虑内存,或者元素数量较少时,用map
选用map还是hash_map,关键是看关键字查询操作次数,以及你所需要保证的是查询总体时间还是单个查询的时间。如果是要很多次操作,要求其整体效率,那么使用hash_map,平均处理时间短。如果是少数次的操作,使用 hash_map可能造成不确定的O(N),那么使用平均处理时间相对较慢、单次处理时间恒定的map,考虑整体稳定性应该要高于整体效率,因为前提在操作次数较少。如果在一次流程中,使用hash_map的少数操作产生一个最坏情况O(N),那么hash_map的优势也因此丧尽了。
十三、红黑树有什么性质?
1.每个节点都是红色或黑色
2.根节点为黑色
3.叶节点为黑色的NULL结点。
4.如果结点为红,其子节点必须为黑
5.任一节点到NULL的任何路径,所含黑结点数必须相同
十四、map和set的3个问题
1.为何map和set的插入删除效率比其他序列高
因为不需要内存拷贝和移动
2.为何map和set每次insert之后,以前保存的iterator不会失效?
因为插入操作只是结点指针换来换去,结点内存没有改变,而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。
3.当数据元素增多时,(从10000到20000),map、set查找速度会怎样变化?
红黑树用二分查找法,时间复杂度为logN,所以查找次数从log100000=14变为log20000=15,多了1次而已。
十五、为什么vector插入操作可能导致迭代器失效
vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一个较大的新空间,然后将内容拷贝过来,并释放原来的空间,由于操作改变了空间,所以迭代器失效。
理论会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看做是失效的,原则上就是不能使用过期的内存。
但vector一般底层是用数组实现的,仔细考虑数组的特性,得出结论:
1.insert时,假设insert位置为p,分2种情况:
a)容器还有空余空间,不重新分配内存,p前的迭代器有效,p后的失效。
b)重分内存,p之后的迭代器失效
2.erase时,假设erase位置为p,则p之前的迭代器都有效,并且p指向下一个元素位置,(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效。
十六、hashtable和hashmap的区别
hashmap以hashtable为底层。有以下不同:
1.hashtable是Dictionary的子类,而hashmap是Map接口的一个实现类
2.hashtable中的方法是同步的,而hashmap的方法不同步
十七、STL底层数据结构实现
1.vector:数组,支持快速随机访问
2:list 双向链表,支持快速增删
3.deque:一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,支持随机访问
4.stack:底层用deque或者list实现,不用vector的原因是扩容耗时
5:queue 底层用deque或者list实现,不用vector的原因是扩容耗时
6:priority_queue:一般以vector为底层容器,heap为处理规则来管理底层容器实现
7:set 红黑树 有序,不重复
8:multiset 红黑树 有序,可重复
9:map 红黑树 有序,不重复
10:multimpap 红黑树 有序,可重复
11:hash_set hash表 无序,不重复
12:hash_map hash表 ,无序,不重复
13:hashtable 底层结构为vector
十八、STL提供哪六大组件?
1.容器
就是各种数据结构
序列式容器:array、vector、heap、priority_queue、list、slist、deque、stack、queue
关联式容器:set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap
2.算法
各种常见算法:sort、search、copy、erase等,值得学习的有,sort、next_permutation、partition、merge sort 等
3.迭代器
扮演容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,从实现角度看,迭代器是一种将operator*,operator->,operator++,operator–等指针相关操作予以重载的class template.所有的STL容器多附带有自己专属的迭代器。只有容器设计者才知道如何设计迭代器,源生指针也是一种迭代器。是设计模式中的一种。
4.仿函数
行为类函数,可作为算法的某种策略,从实现角度看,仿函数是一种重载了operator()的class或class template。一般函数指针可视为狭义的仿函数。
5.配接器
一种用来修饰容器或者仿函数或迭代器接口的东西。比如queue和stack,看着像容器,其实就是deque包了一层皮。
6.配置器
复制空间配置与管理,从实现角度看,配置器就是实现了动态空间配置、空间管理、空间释放的classtemplate。
十九、auto_ptr可以做vector的元素吗?为什么?
不能。因为STL的标准容器规定它所容纳的元素必须是可以拷贝构造和可被转移赋值的。而auto_ptr不能,可以用shared_ptr智能指针代替。
二十、简单说一下next_permutation和partition的实现?
1.next_permutation(下一个排列)
首先,从最尾端开始往前寻找2个相邻元素,令第一个元素为i,第二个元素为I,且满足i< I,找到这样一组相邻元素后,再从尾端开始向前检验,找出第一个大于i的元素j,将i和j对调,再将ii之后的所有元素颠倒排列,此即所求“下一个”排列组合。
2.partion:
令头端迭代器first向尾部移动,尾部迭代器last向头部移动。当first所指的值大于或等于枢轴时就停下来,当last所指的值小于或等于枢轴时也停下来,然后检验两个迭代器是否交错。如果first仍然在last左边,就将连着元素互换,然后各自调整一个位置(向中央逼近),再继续进行相同的行为。如果发现两个迭代器叫错了,表示整个序列已经调整完毕。
二十一、不允许有遍历行为的容器有哪些?(不提供迭代器)
1.queue ,除了头部外,没有其他方法存取deque的其他元素。
2.stack(底层以deque实现),除了最顶端外,没有任何方法可以存取stack的其他元素。
3.heap,所有元素都必须遵循特别的排序规则,不提供遍历功能。
转载自