1、STL的六大组件
容器、算法、迭代器、仿函数(函数对象)、配接器、配置器(空间配置和管理)
2、仿函数是指看起来像函数的对象,其原理是在类中重载了操作符(),直接调用即可。
3、SGI STL 的内存分配策略: 如果要分配的区块够大,超过128字节时,就直接用malloc分配;当要分配的区块小于128字节时,则以内存池管理。
内存池的管理策略: 每次配置一大块内存,并维护对应的空闲链表。下次若再有相同大小的内存需求,就直接从空闲链表中拨出。如果客户释放小额的区块,就由配置器回收到空闲链表中。 为了方便管理,SGI会主动将任何小额区块的内存需求量上调至8的倍数,并维护16个空闲链表,各自管理大小分别是8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节的小额区块。
4、迭代器的五类:
input iterator:所指的对象,不允许外界改变,只读。
Output Iterator:只写
Forward Iterator:允许写入型算法在此种迭代器所形成的区间上进行读写操作
Bidirectional Iterator:可双向移动。
Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(前三种支持operator++,第四种再加上operator--),第五种涵盖所有指针算术能力,如p+n,p-n, p[n], p1-p2, p1 < p2.
5、vector
vector采用的数据结构是:线性连续空间。以两个迭代器start和finish分贝指向所配置得来的连续空间中目前已被使用的范围,以迭代器end_of_storage指向整块连续空间的尾端。
vector一般实际配置的大小可能比客户需求的量更大一些,以备将来的扩充。当分配的空间用完之后,一般会以原来大小的两倍另外配置一块较大的空间,然后将原来内容拷贝过来,然后在原内容之后构造新元素,并释放原空间。
由于空间的扩容问题,一旦发生扩容,之前vector的所有迭代器都会失效。
6、deque
deque是双向开口的连续线性空间,可以在头尾两端分别做元素的插入和删除操作,而vector在头部插入、删除效率很差。
deque和vector的最大差异,一在于deque允许常数时间内对头端进行元素的插入和删除,二在于deque没有容量的观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
deque是由一段段分别连续的空间组成的,其整体的连续性是假象的。它由一块所谓的map空间作为主控,其中的每个元素都是指针,指向另一段较大的连续线性空间,也就是deque真正的存储空间。这有些类似于二维数组。
7、stack 配接器(适配器)
stack默认的是以deque为底部结构的,也可以链表list为底部结构。stack不能有遍历行为,也不能有迭代器。
8、queue 配接器(适配器)
queue默认的是以deque为底部结构,也可以链表list为底部结构。queue不能有遍历行为,也没有迭代器。
9、heap
heap作为优先级队列的底部结构使用,使得元素的插入和极值的取得时间复杂度都为O(logN)。
heap利用的是一个数组或vector来存储元素,利用一组heap算法实现。利用了完全二叉树的特点:第i个结点的左孩子是2*i,右孩子是2*i+1.
heap默认一般使用大顶堆。
大顶堆的插入操作:首先将新加入的元素放到最下一层的叶结点,也就是数组的最后元素的后面。然后调整整个堆:上溯,将新结点和其父结点比较,如果键值比父节点大,就交换父子位置。一直上溯,直到不需要交换或直到根结点。例如要插入的键值为50的元素
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
68
|
31
|
65
|
21
|
24
|
32
|
26
|
19
|
16
|
13
|
50
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
68
|
31
|
65
|
21
| 50 |
32
|
26
|
19
|
16
|
13
| 24 |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
68
| 50 |
65
|
21
| 31 |
32
|
26
|
19
|
16
|
13
| 24 |
大顶推的删除操作:肯定删除的是数组的第一个位置的元素,应为它的键值最大。删除策略是将数组的最后一个元素放入数组的第一个位置处,然后调整使之成为大顶推。调整方法是下溯:将第一个位置处的元素和它的左右孩子比较,如果小于左右孩子中最大的那个孩子,就和那个孩子交换位置,如果大于两个孩子,则停止。这样一直下溯下去,直到叶子结点。
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
|
68
| 50 |
65
|
21
| 31 |
32
|
26
|
19
|
16
|
13
| 24 |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
| 24 | 50 |
65
|
21
| 31 |
32
|
26
|
19
|
16
|
13
| 68 |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
| 65 | 50 | 24 |
21
| 31 |
32
|
26
|
19
|
16
|
13
| 6 |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
| 65 | 50 | 32 |
21
| 31 | 24 |
26
|
19
|
16
|
13
| 68 |
10、priority_queue 配接器
priority_queue以vector为底部容器,构建了大顶堆。没有迭代器,也不能遍历。
11、slist单向链表,区别于list双向链表‘
12、关联式容器:set、 map、 multiset、 multimap
标准规格的关联式容器以RB-tree为底层机制,但是SGI STL 仍然提供了一份不在规格之列的关联式容器:hash table为底层机制的hash_set、 hash_map、 hash_multiset、 hash_multimap
关联式容器没有所谓的头尾,只有最大元素和最小元素,所以不会有push_back、push_front、pop_back、pop_front、 begin、end这样的行为。
13、RB-tree
操作系统将文件存放在树结构中, 编译器实现了表达式树, 文件压缩用的哈弗曼算法用到树结构, 数据库使用的是B-tree
14、 set 键值是不可以随意改变的 insert_unique
set的多有元素都是根据键值自动被排序的,默认情况下递增排序。它的键值就是它的实值, set不允许有两个元素有相同的键值。
set的迭代器是const类型的,因为不能随便改变set的元素值,否则会打乱set元素的排列规则。
15、map
map的所有元素会根据元素的键值自动排序的,默认递增排序。 map的键值是不能修改的,map的实值是可以修改的。
16、multiset
multiset和set的唯一区别就是它允许键值重复。insert_equal
17、multimap
multimap和 map的唯一区别就是它允许键值重复
18、hash table
二叉搜索树具有对数平均时间O(logN),但这样的表现构造在一个假设上:输入数据有足够的随机性,否则要进行大量的调整操作。
hash table在插入、删除、搜寻等操作上也具有常数平均时间,而且这种表现是以统计为基础,不需要依赖元素的随机性。
解决碰撞问题的方法:线性探测、二次探测、开链等。
线性探测:
当计算出来的位置上的空间已经被占用时,就循环往下一一寻找,如果到达尾端,就绕道头部继续寻找,直到找到一个可用的空间为止。这样做的前提条件是表格足够大。但是这样花费的时间比较多。
二次探测:
如果hash 函数计算出来的位置为H, 而该位置上已被使用时,就一次尝试H+1^2, H+2^2, H+3^2, H+4^2, ...., H+i^2.
开链:
表格的每一个元素中维护一个list,hash函数为我们分配某一个list,然后我们在那个list身上执行元素的插入、搜寻、删除等操作。SGI STL的hash table 就是采用的开链方法。类似于桶。
表格用vector容器实现,这样方便改变表格的大小。表格的大小要大于元素的个数,这样使得链表不会太长,减少访问时间。
19、hash_set hash_map
hash_set/hash_map 与 set/map 的使用类似,只是它们的底层数据结构不一样。