C++面试之STL
STL相关的面试题
了解STL吗?
0:STL常用的容器有哪些以及各自的特点是什么?
1.vector:底层数据结构为数组 ,支持快速随机访问。 2.list:底层数据结构为双向链表,支持快速增删。 3.deque:底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问。 4.stack:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时 5.queue:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装) 6.priority_queue:的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现 7.set:底层数据结构为红黑树,有序,不重复。 8.multiset:底层数据结构为红黑树,有序,可重复。 9.map:底层数据结构为红黑树,有序,不重复。 10.multimap:底层数据结构为红黑树,有序,可重复。 11.hash_set:底层数据结构为hash表,无序,不重复。 12.hash_multiset:底层数据结构为hash表,无序,可重复 。 13.hash_map :底层数据结构为hash表,无序,不重复。 14.hash_multimap:底层数据结构为hash表,无序,可重复。
使用场景
1、如果你需要高效的随机存取,而不在乎插入和删除的效率,使用vector
2、如果你需要大量的插入和删除,而不关心随机存取,则应使用list
3、如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque。
4、如果你要存储一个数据字典,并要求方便地根据key找value,那么map是较好的选择
5、如果你要查找一个元素是否在某集合内存中,则使用set存储这个集合比较好
1:说说 vector 和 list 的区别
1) vector, 连续存储的容器,动态数组,在堆上分配空间 ;
底层实现:数组。
如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素。
适用场景:经常随机访问,且不经常对非尾节点进行插入删除。
2)list,动态链表,在堆上分配空间,每插入一个元素都会分配空间,每删除一个元素都会释放空间。
底层:双向链表
访问:随机访问性能很差,只能快速访问头尾节点。
适用场景:经常插入删除大量数据
2) vector在中间节点进行插入删除会导致内存拷贝,list不会。
3) vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
4) vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。
2 map 和 set 有什么区别
1) map和set都是C++的关联容器,其底层实现都是红黑树(RB-Tree)。
2) map中的元素是key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。
3) set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。
4) map支持下标操作,set不支持下标操作。map可以用key做下标,
2.1unordered_map和map 说说区别
内部实现机理
- map: map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率。
- unordered_map: unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的
优缺点以及适用处
- map
- 优点:
- 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
- 红黑树,内部实现一个红黑书使得map的很多操作在的时间复杂度下就可以实现,因此效率非常的高
- 缺点:
适用处,对于那些有顺序要求的问题,用map会更高效一些- 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间
- 优点:
- unordered_map
- 优点:
- 因为内部实现了哈希表,因此其查找速度非常的快
- 缺点:
适用处,对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map- 哈希表的建立比较耗费时间
- 优点:
3 STL 中迭代器的作用,有指针为何还要迭代器
1) Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
2) 迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->、*、++、--等,相当于一种智能指针。
3) 迭代器产生原因:
Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。
4 STL 迭代器是怎么删除元素的呢
1) 对于序列容器vector,deque来说,使用erase(itertor)后,后边的每个元素的迭代器都会失效,但是后边每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器;
2) 对于关联容器map set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
3) 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,
5平衡二叉树(AVL树)和红黑树
1)平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。
2)红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑),红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。
3)所以红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。
请你回答一下map底层为什么用红黑树实现 1、红黑树: 红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。 性质: 1. 每个节点非红即黑 2. 根节点是黑的; 3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的; 4. 如果一个节点是红色的,则它的子节点必须是黑色的。 5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点; 2、平衡二叉树(AVL树): 红黑树是在AVL树的基础上提出来的。 平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。 AVL树中所有结点为根的树的左右子树高度之差的绝对值不超过1。 将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。 3、红黑树较AVL树的优点: AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。 所以红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。
6 栈溢出的原因
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数。
1) 局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出,局部变量是存储在栈中的。
2) 递归调用层次太多。
3) 指针或数组越界。例如进行字符串拷贝,或处理用户输入等等。
7 堆和栈的区别
C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。
1) 栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放。栈由系统自动分配,速度快,但是程序员无法控制。
2) 堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作。一般是由程序员分配释放,未被释放可能引起内存泄漏。堆是有程序员自己分配,速度较慢,容易产生碎片,不过用起来方便。
3) 全局变量和静态变量的存储是放在一块的。
4) 常量区:常量存储在这里,不允许修改。
5) 代码区:存放函数体的二进制代码。
8 哈希表(hash表)
哈希表的实现主要包括构造哈希和处理哈希冲突:构造哈希,主要包括直接地址法,除留余数法。
处理哈希冲突:当哈希表关键字集合很大时,关键字值不同的元素可能会映射到哈希表的同一地址上,这样的现象称为哈希冲突。常用的解决方法有:
1) 开放定址法,冲突时,用某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。(如,线性探测,平方探测)
2) 再哈希法:当发生冲突时,用另一个哈希函数计算地址值,直到冲突不再发生。
3) 链地址法:将所有哈希值相同的key通过链表存储,key按顺序插入链表中。