什么是STL?
- C++ STL从广义来讲包括了三类:容器、算法、迭代器。
- 算法:包括遍历、查找、排序等常用算法,以及不同容器特定的算法。
- 容器:就是数据的存放形式,包括序列式容器和关联式容器,序列式容器就是list,vector等,关联式容器就是set,map等。
- 迭代器:就是在不暴露容器内部结构的情况下对容器的遍历。
迭代器:++ it、it ++哪个好,为什么
- 前置返回一个引用,后置返回一个对象
- 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
// ++i实现代码为:(前置)
int& operator++() {
*this += 1;
return *this;
}
//i++实现代码为: (后置)
int operator++(int) {
int temp = *this;
++*this;
return temp;
}
STL中hash_table的实现?
- hash_set、hash_map、hash_multiset、hash_multimap四个关联容器都是以hashtable为底层实现方法(技巧)
- Hashtable底层实现是通过开链法(开链法解决哈希冲突)来实现的,hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。
1 原理
它通过把关键码(key)值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在散列函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,映射函数f(key)为哈希函数(散列函数)。
哈希表的底层实际上是基于数组来存储的
- 当插入键值对时,并不是直接插入该数组中,而是通过对键进行Hash运算得到Hash值,然后和数组容量取模,得到在数组中的位置后再插入。
- 取值时,先对指定的键(key)求Hash值,再和容量取模得到底层数组中对应的位置,如果指定的键值与存储的键(key)相匹配,则返回该键值对,如果不匹配,则表示哈希表中没有对应的键值对。
这样做的好处是在查找、插入、删除等操作可以做到O(1),最坏的情况是O(n),当然这种是最极端的情况,极少遇到。
实现哈希表我们可以采用两种方法:
- 1、数组+链表
- 2、数组+二叉树
2 哈希冲突
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为哈希冲突。
很明显,极端情况下如果数组容量为1,哪必然发生碰撞,如果数组容量无限大,哪碰撞的概率非常之低。所以,哈希碰撞还取决于负载因子。负载因子是存储的键值对数目与数组容量的比值,比如数组容量100,当前存贮了90个键值对,负载因子为0.9。负载因子决定了哈希表什么时候扩容,如果负载因子的值太大,说明存储的键值对接近容量,增加碰撞的风险,如果值太小,则浪费空间
主要有四类处理冲突的方法:
- 开链法(常用)
- 开放定址法(常用)
- 公共溢出区(不常用)
- 再Hash法(不常用)
2.1 开链法
主要思想是基于数组和链表的组合来解决冲突,桶(Bucket)中不直接存储键值对,每个Bucket都链接一个链表,当发生冲突时,将冲突的键值对插入链表中。
- 优点在于方法简单,非同义词之间也不会产生聚集现象(相比于开放定址法),并且其空间结构是动态申请的,所以比较适合无法确定表长的情况
- 缺点是链表指针需要额外的空间,遇到碰撞拒绝服务时会退化为单链表。
2.2 开放定址法
主要思想是发生冲突时,直接去寻找下一个空的地址,只要底层的表足够大,就总能找到空的地址。这个寻找下一个地址的行为,叫做探测。
2.3 公共溢出区
主要思想是建立一个独立的公共区,把冲突的键值对都放在其中。不常用,这里不再细述。
2.4 再Hash法
主要思想是有冲突时,换另外一个Hash函数来算Hash值。不常用,这里不再细述。