1. 请说说STL的基本组成部分?
STL是标准模板库(Standard Template Library),简单来说就是一些常用数据结构和算法模板的集合。
广义上讲:STL分为三类:Algorithm(算法),Container(容器)和Iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。
详细的说:STL由6部分组成:容器(Container)、算法(Algorithm)、迭代器(Iterator)、仿函数(Function object)、适配器(Adaptor)、空间配制器(Allocator)。
- 容器(Container):容器是一种数据结构,如list,vector和deque(双端队列)等,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器。
- 算法(Algorithm):是用来操作容器中数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数本身与它们操作的数据的结构和类型无关,因此它们可以用于从简单数组到高度复杂的容器的任何数据结构上。
- 迭代器(Iterator):提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。除此之外,迭代器也可以是那些定义了operator*()(重载指针符号)以及其它类似于指针的操作的类对象。
- 仿函数(Function):仿函数又称之为函数对象。本质就是类重载了一个operator(),创建一个行为类似函数的对象。
- 适配器(Adaptor):简单的说就是一种接口类,专门用来修改现有类的接口,提供一种新的接口;调用现有的函数来实现所需要的功能。主要包括3种适配器Container Adaptor、Iterator Adaptor、Function Adaptor。
- 空间配制器(Allocator):为STL提供空间配置的系统。其中主要工作包括两部分:对象的创建与销毁;内存的获取与释放。
2. 请说说STL种常见的容器?
容器可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是模板类,分为顺序容器、关联式容器、容器适配器三种类型,三种类型容器特性分别如下:
-
顺序容器
容器并非排序的,元素的插入位置同元素的值无关。包含vector、deque、list等,具体实现如下:
(1)vector 头文件 < vector >
动态数组。元素在内存连续存放,随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。
(2)deque 头文件 < deque >
双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(仅此于vector)【根据数据结构的学习,个人认为这里可以实现随机存取是因为双向队列底层也是数组】。在两端增删元素具有极佳的性能。
(3)list 头文件 < list >
双向链表。元素在内存不是连续存放,在任何位置增删元素都能在常数时间完成。不支持随机存取。 -
关联式容器
元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;查找时具有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap等,具体实现如下:
(1)set/multiset 头文件 < set >
set即集合。set种不允许相同元素,multiset种允许存在相同元素。
(2)map/multimap 头文件< map >
map与set的不同在于map中存放的元素是键值对,一个名为first,另一个名为second,根据first值对元素从小到大排序,并可快速地根据first来检索元素。map同multimap的不同在于是否允许相同first值来检索元素,map不可以,multimap可以。 -
容器适配器
封装了一些基本的容器,使之具备了新的函数功能,比如把deque封装一下变为一个具有stack功能的数据结构。新得到的数据结构就叫适配器。包含stack、queue、priority_queue等,具体实现如下:
(1)stack 头文件< stack >
栈的删除、检索和修改的项只能是最后插入序列的项(栈顶)。后进先出。
(2)queue 头文件< queue >
队列的插入只能在尾部进行,删除、检索和修改只允许从头部进行。先进先出。
(3)priority_queue 头文件< queue >
优先级队列。内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列。 -
说说STL中map、hashtable、deque、list的实现原理
map、hashtable、deque、list实现机理分别是红黑树、函数映射、双向队列、双向链表、它们的特性分别如下: -
map实现原理
map内部实现了一个红黑树(红黑树是非严格平衡的二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个结点都代表着map的一个元素。因此map进行的查找、删除、添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉树(二叉查找树、二叉排序树)存储的,特点就是左子树的所有节点的键值【不是键值对的键值】都小于根节点的键值,右子树所有节点的键值都大于根节点的键值。使用中序遍历可以将键值按照从小到大遍历出来。 -
hashtable(散列表、哈希表)实现原理
hashtable采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够快速地进行查找。这决定了哈希表特殊的数据结构,它同数组、链表以及二叉排序树等相比较有明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在记录的关键字进行比较来进行查找。 -
deque实现原理
deque内部实现的是一个双向队列。元素在内存连续存放。随机存取任何元素都在常数时间完成。所有适合vector的操作都适用于deque。在两端增删元素具有极佳的性能。 -
list实现原理
list内部实现的是一个双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。给定一个下标i,访问第i个元素的内容,只能从头部挨个遍历到第i个元素。
4. 说说vector和list的区别,分别适用于什么场景?
vector和list区别在于底层实现机理不同,因而特性和适用场景也有所不同。
vector:一维数组
特点:元素在内存连续存放,动态数组,在堆中分配内存,有保留内存,如果减少大小后内存也不会释放。
- 优点:和数组一样开辟一段连续的空间,并且支持随机访问,所以它的查找效率高,时间复杂度为O(1)
- 缺点:由于开辟一段连续的空间,所以插入删除会需要对数据进行移动,时间复杂度为O(n),另外当空间不足时需要扩容。
list:双向链表
特点:元素在堆中存放,每个元素都是存放在一块内存中,它的内存空间可以是不连续的,通过指针来进行数据访问。
- 优点:底层实现是循环双链表,当对大量数据进行插入删除时,其时间复杂度为O(1)
- 缺点:底层没有连续的空间,只能通过指针来进行访问,所以查找数据需要遍历O(n)
应用场景:
vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随机访问而不在乎插入删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除而不关心随机访问,应该使用list。