修正时间:2021-7-31
修正二次:2021-09-14

文章目录

  • 1 什么是STL
  • 2 迭代器中i++,++i,哪一个好,为什么
  • 3 左值引用和右值引用
  • 4 STL 简单 hashtable 的实现
  • 5 简单说一下traits技法(待完善)
  • 6 一级和二级空间适配器
  • 7 vector和list的区别和应用
  • 8 STL 中size()和capacity()和reserve()
  • 9 vector如何缩小空间
  • 10 理解顺序性容器、关联性容器和容器适配器
  • 11 顺序容器和关联容器删除元素的区别
  • 12 STL迭代器的实现(重)
  • 13 map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器? 为什么使用红黑树?(重)
  • 14 map的四种插入方式
  • 15 map和unordermap的区别
  • 16 vector和map下标越界访问
  • 17 STL中的allocator、deallocator
  • 18 STL中hash_map扩容发生什么?
  • 19 常见容器性质总结
  • 20 说一下STL每种容器对应迭代器
  • 21 不同容器的迭代器(注意功能的区别)
  • 22 C++ STL迭代器失效问题(重要)
    • 迭代器失效的类型
    • vector失效问题
    • deque失效问题
    • list和slist失效问题
    • 其他失效问题
  • 23 set和map的区别,multimap和multiset的区别
  • 24 红黑树概念
  • 25 hashtable中解决冲突有哪些方法?
  • 容器的时间复杂度分析

1 什么是STL

C++ STL从广义来讲包括了三类:算法,容器和迭代器。

  • 算法包括排序,复制等常用算法,以及不同容器特定的算法。
  • 容器就是数据的存放形式,包括序列式容器和关联式容器以及容器配置器,序列式容器就是list,vector等,关联式容器就是set,map等,容器器配置stack,queue
  • 迭代器就是在不暴露容器内部结构的情况下对容器的遍历。

具体如下

容器(Container),是一种数据结构,如list,vector,和deques,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器;
迭代器(Iterator),提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符地方法的类对象;
算法(Algorithm),是用来操作容器中的数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数本身与他们操作的数据的结构和类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用;
仿函数(Function object,仿函数(functor)又称之为函数对象(function object),其实就是重载了()操作符的struct,没有什么特别的地方
迭代适配器(Adaptor)一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配制器(allocator)其中主要工作包括两部分1.对象的创建与销毁 2.内存的获取与释放

2 迭代器中i++,++i,哪一个好,为什么

  • 简单说前置就是对本身进行自增然后返回引用,后置就是拷贝一份,然后自增,返回的是拷贝没增长的
// ++i实现代码为:
int& operator++()
{*this += 1;return *this;} 

前置不会产生临时对象,后置必须产生临时对象(返回临时变量),临时对象会导致效率降低

//i++实现代码为:
int operator++(int)
{int temp = *this;                   ++*this;                       return temp;
} 

3 左值引用和右值引用

这个将的不错

#include <bits/stdc++.h>
using namespace std;template<typename T>
void fun(T&& t)
{cout << t << endl;
}int getInt()
{return 5;
}int main() {int a = 10;int& b = a;  //b是左值引用int& c = 10;  //错误,c是左值不能使用右值初始化int&& d = 10;  //正确,右值引用用右值初始化int&& e = a;  //错误,e是右值引用不能使用左值初始化const int& f = a; //正确,左值常引用相当于是万能型,可以用左值或者右值初始化const int& g = 10;//正确,左值常引用相当于是万能型,可以用左值或者右值初始化const int&& h = 10; //正确,右值常引用const int& aa = h;//正确int& i = getInt();  //错误,i是左值引用不能使用临时变量(右值)初始化int&& j = getInt();  //正确,函数返回值是右值fun(10); //此时fun函数的参数t是右值fun(a); //此时fun函数的参数t是左值return 0;
}

4 STL 简单 hashtable 的实现

2021-09-14
hash_table 提供有名项的存储 删除 查询,可以看作是字典结构,它实现主要通过hash faction 散列函数定义有名项和存储地址之间的映射关系,但是存在碰撞问题,解决方案三个 我们主要说说开链,在每一个表格元素维护一个链表,如果链表够短 那就可以足够快的查询速度。
 
细致可以这么说:我们用vector作为hash_table的底层实现,并称hash_table内的每个元素为桶子bucket,每个桶子里装着一个list头指针,通过list头指针,可以串联出一串节点(node)。这就是开链法的精髓:hash_table(底层是vector)的每个元素装着list头指针,每个list头指针可以开出一条链表。
 
之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。

  • Hash_table可提供对任何有名项(named item)的存取、删除和查询操作。由于操作对象是有名项,所以hash_table可被视为是一种字典结构(dictionary)
  • Hash_table使用名为hash faction的散列函数来定义有名项与存储地址之间的映射关系。使用hash faction会带来一个问题:不同的有名项可能被映射到相同的地址,这便是所谓的碰撞(collision)问题,解决碰撞问题的方法主要有三种:线性探测(linear probing)、二次探测(quadratic probing)、开链(separate chaining)。
  • 主要说说常用的开链:在每个表格元素中维护一个 list(链表),list 由 hash faction 为我们,我们在list上执行元素的插入、查找、删除等操作,虽然对list进行的查询是线性操作,但是 list 足够短的话,我们也能获得较好的效率。注意,使用开链法,表格的负载系数可能大于1。
  • 我们用vector作为hash_table的底层实现,并称hash_table内的每个元素为桶子bucket,每个桶子里装着一个list头指针,通过list头指针,可以串联出一串节点(node)。这就是开链法的精髓:hash_table(底层是vector)的每个元素装着list头指针,每个list头指针可以开出一条链表。

5 简单说一下traits技法(待完善)

  • 当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案。
 template <typename T> class Test { ...... }; 

假设有这样的需求:类Test中的某部分处理会随着类型T的不同而会有所不同,比如希望判断T是否为指针类型,当T为指针类型时的处理有别于非指针类型,怎么做?模板里再加个参数,如下

 template <typename T, bool isPointer> class Test { ......// can use isPointer to judge whether T is a pointer }; 

由于我们很难去限制用户在使用模板类时是使用指针还是基本数据类型还是自定义类型,而用常规方法也没有很好的方法去判断当前的T的类型。traits怎么做呢?

 template <typename T> struct TraitsHelper { static const bool isPointer = false; }; template <typename T> struct TraitsHelper<T*> { static const bool isPointer = true; };
if (TraitsHelper<T>::isPointer)...... // 可以得出当前类型int*为指针类型
else...... // 可以得出当前类型int非指针类型

也可以参考这边的

6 一级和二级空间适配器

参考链接

7 vector和list的区别和应用

2021-09-14总结:1随机访问 2 插入 3 删除 4 自己特点空间连续 拷贝啥的 5 双向 单向
vector是一个动态数组,区别于传统的数组可是实现空间的动态增长,但是是通过开辟新空间拷贝的方式,所以可以预先开辟一定空间,两者的区别呢 主要是vector支持高效的随机访问,和高效的尾插和尾删。但是如果中间的删除和插入就涉及到大量数据的移动 ----list双向链表,毫无疑问地址空间不连续,只能通过指针访问数据,所以比如访问第几个节点的效率就不高 但是插入和删除的效率很高

  • vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。
  • list数据结构 list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,能高效地进行插入和删除。非连续存储结构:list是一个双链表结构,支持对链表的双向遍历。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。
  • vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作。list的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除操作等都比较方便,改变指针的指向即可。vector是单向的,list是双向的。vector中的迭代器在使用后就失效了,而list的迭代器在使用之后还可以继续使用。

8 STL 中size()和capacity()和reserve()

特别注意第6点

  • size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩余的可用空间大小。当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。
  • 由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当n>capacity()时,调用reserve(n)才会改变vector容量。
  • resize()成员函数只改变元素的数目,不改变vector的容量。
    1. 空的vector对象,size()和capacity()都为0
    2. 当空间大小不足时,新分配的空间大小为原空间大小的2倍。
    3. 使用reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。
    4. 当reserve()分配的空间比原空间小时,是不会引起重新分配的。
    5. resize()函数只改变容器的元素数目,未改变容器大小。
    6. 用reserve(size_type)只是扩大capacity值,这些内存空间可能还是“野”的,如果此时使用“[ ]”来访问,则可能会越界。而resize(size_type new_size)会真正使容器具有new_size个对象。

9 vector如何缩小空间

2021-09-14补充 可以看一下STL vector的博客

  • 由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
  • 如果需要空间动态缩小,可以考虑使用deque。如果vector,可以用swap()来帮助你释放内存。vector<int>(vec).swap(vec).

10 理解顺序性容器、关联性容器和容器适配器

2021-09-14
顺序容器:线性结构,位置由操作的时间和地点有关,与元素本身无关。(vector)
关联容器:二叉树结构,插入位置与元素本身有关
容器适配器:简单的说,就是容器的容器,对于容器的封装,更换了借口,最常见的就是deque和stack底层实现都可以是deque。
具体可以参考

  • 顺序性容器 是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
  • 关联式容器 和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。这在下面具体的容器类中可以说明这一点。
  • 容器适配器 是一个比较抽象的概念, C++的解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。




补充参考链接

11 顺序容器和关联容器删除元素的区别

2021-09-14 主要知道是否移动元素就可以判断,注意erase的用法

    1. 顺序容器(序列式容器,比如vector、deque)erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;It = c.erase(it);
  • 关联容器(关联式容器,比如map、set、multimap、multiset等)

erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;c.erase(it++)

12 STL迭代器的实现(重)

2021-09-14补充

  • 迭代器的出现就是使容器的访问变得可以像指针访问数组那样简便,所以模拟实现迭代器的时候就要重载++,–,->,解引用等运算符,操作符
  • 例子参考链接->链接1
  1. 迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器,除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘合剂。

  2. 迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算符与->运算符,以及++、–等可能需要重载的运算符重载。这和C++中的智能指针很像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放内存的功能。

  3. 最常用的迭代器的相应型别有五种:value type、difference type、pointer、reference、iterator catagoly;

13 map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器? 为什么使用红黑树?(重)

2021-09-14:

  • 高效删除和插入 log(n)的时间复杂度
  • 要求是自动排序,红黑树可以实现,并且红黑树是平衡二叉树的升级,并不严格要求高度差为1
  1. 他们的底层都是以红黑树的结构实现,因此插入删除等操作都在O(logn)时间内完成,因此可以完成高效的插入删除;

  2. 在这里我们定义了一个模版参数,如果它是key那么它就是set,如果它是map,那么它就是map;底层是红黑树,实现map的红黑树的节点数据类型是key+value,而实现set的节点数据类型是value

  3. 因为map和set要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比较低。

14 map的四种插入方式

  1. 用insert函数插入pair数据,
mapStudent.insert(pair<int, string>(1, "student_one")); //匿名函数 也可以创建一个实例放入
  1. 用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type (1, "student_one"));
  1. 在insert函数中使用make_pair()函数
mapStudent.insert(make_pair(1, "student_one"));//比较常用
  1. 用数组方式插入数据
mapStudent[1] = "student_one";//最简单的方式

15 map和unordermap的区别

2021-09-14补充

  • 很容易想到,首先map底层实现是红黑树,unordermap底层实现是hash_table(可以看做vector+链表)。所以unordermap 有散列函数 插入和删除都是0(1)而map是log(n)。但是一个有序一个无序也好理解
  1. unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序

  2. 存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。

  3. 所以使用时map的key需要定义operator<。而unordered_map需要定义hash_value函数并且重载operator==。但是很多系统内置的数据类型都自带这些,

  4. 那么如果是自定义类型,那么就需要自己重载operator<或者hash_value()了。

  5. 如果需要内部元素自动排序,使用map,不需要排序使用unordered_map

  6. unordered_map的底层实现是hash_table;

  7. hash_map底层使用的是hash_table,而hash_table使用的开链法进行冲突避免,所有hash_map采用开链法进行冲突解决。

16 vector和map下标越界访问

2021-09-14

特别注意map

  • vector直接使用【】越界会直接运行报错不会抛出异常,使用.at()会抛出异常
  • map[key],可能会出现意想不到的行为。如果map包含key,没有问题,如果map不包含key,使用下标有一个危险的副作用,会在map中插入一个key的元素,value取默认值,返回value。也就是说,map[key]不可能返回null。
  • 所以map常常需要用find 和count查看是否包含key。m.count(key)取值为0,或者1(存在)。m.find(key):返回迭代器,判断是否存在,不存在返回end()。

17 STL中的allocator、deallocator

  1. 第一级配置器直接使用malloc()、free()和relloc(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;

  2. 第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;

  3. 空间配置函数allocate(),首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill(),为free-list重新分配空间;

  4. 空间释放函数deallocate(),该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。

18 STL中hash_map扩容发生什么?

补充:注意几个概念 桶 节点 vector容器 字典结构 散射函数 向前操作

  1. hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。

  2. 向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。

19 常见容器性质总结

  1. vector 底层数据结构为数组 ,支持快速随机访问

  2. list 底层数据结构为双向链表,支持快速增删

  3. deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问

deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:

[堆1] --> [堆2] -->[堆3] --> …

每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.

  1. stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

  2. queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)

  3. priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现

  4. set 底层数据结构为红黑树,有序,不重复

  5. multiset 底层数据结构为红黑树,有序,可重复

  6. map 底层数据结构为红黑树,有序,不重复

  7. multimap 底层数据结构为红黑树,有序,可重复

  8. unordered_set 底层数据结构为hash表,无序,不重复

  9. unordered_multiset 底层数据结构为hash表,无序,可重复

  10. unordered_map 底层数据结构为hash表,无序,不重复

  11. unordered_multimap 底层数据结构为hash表,无序,可重复

20 说一下STL每种容器对应迭代器

21 不同容器的迭代器(注意功能的区别)

参考链接

  1. 前向迭代器(forward iterator)
    假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。

  2. 双向迭代器(bidirectional iterator)
    双向迭代器具有正向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。

  3. 随机访问迭代器(random access iterator)
    随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

  • 此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。另外,表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。

//遍历 vector 容器。
#include <iostream>
//需要引入 vector 头文件
#include <vector>
using namespace std;
int main()
{vector<int> v{1,2,3,4,5,6,7,8,9,10}; //v被初始化成有10个元素cout << "第一种遍历方法:" << endl;//size返回元素个数for (int i = 0; i < v.size(); ++i)cout << v[i] <<" "; //像普通数组一样使用vector容器//创建一个正向迭代器,当然,vector也支持其他 3 种定义迭代器的方式cout << endl << "第二种遍历方法:" << endl;vector<int>::iterator i;//用 != 比较两个迭代器for (i = v.begin(); i != v.end(); ++i)cout << *i << " ";cout << endl << "第三种遍历方法:" << endl;for (i = v.begin(); i < v.end(); ++i) //用 < 比较两个迭代器cout << *i << " ";cout << endl << "第四种遍历方法:" << endl;i = v.begin();while (i < v.end()) { //间隔一个输出cout << *i << " ";i += 2; // 随机访问迭代器支持 "+= 整数"  的操作}
}
//创建一个 v list容器
list<int> v;
//创建一个常量正向迭代器,同样,list也支持其他三种定义迭代器的方式。
list<int>::const_iterator i;
//以下合法
for(i = v.begin(); i != v.end(); ++i)cout << *i;//以下代码则不合法,因为双向迭代器不支持用“<”进行比较
for(i = v.begin(); i < v.end(); ++i)cout << *i;
//以下不合法
for(int i=0; i<v.size(); ++i)cout << v[i];

22 C++ STL迭代器失效问题(重要)

2021-09-14

迭代器失效的类型

a.由于插入元素,使得容器元素整体“迁移”导致存放原容器元素的空间不再有效,从而使得指向原空间的迭代器失效。
b.由于删除元素使得某些元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。

vector失效问题

2021-09-14

  • 理解本质为什么会失效,不用记,deque是双向移动,全部失效,list链表就单独一个实现,map和set就是红黑树,本质还是链表所以和list一样
  1. 尾后插入:size < capacity时,首迭代器不失效尾迭代失效(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。

  2. 中间插入:中间插入:size < capacity时,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity时,所有迭代器均失效。

deque失效问题

  1. 在deque容器首部或者尾部插入元素不会使得任何迭代器失效。
  2. 在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。
  3. 在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效。

list和slist失效问题

  1. 插入操作(insert)和接合操作(splice)不会造成原有的list迭代器失效,这在vector中是不成立的,

  2. 因为vector的插入操作可能造成记忆体重新配置,导致所有的迭代器全部失效。

  3. list的删除操作(erase)也只有指向被删除元素的那个迭代器失效,其他迭代器不受影响。(list目前只发现这一种失效的情况)

其他失效问题

  • 而list双向链表每一个节点内存不连续, 删除节点仅当前迭代器失效,erase返回下一个有效迭代器;

  • map/set等关联容器底层是红黑树删除节点不会影响其他节点的迭代器, 使用递增方法获取下一个迭代器 mmp.erase(iter++);

  • unordered_(hash) 迭代器意义不大, rehash之后, 迭代器应该也是全部失效.

23 set和map的区别,multimap和multiset的区别

  • set只提供一种数据类型的接口,但是会将这一个元素分配到key和value上,而且它的compare_function用的是 identity()函数,这个函数是输入什么输出什么,这样就实现了set机制,set的key和value其实是一样的了。其实他保存的是两份元素,而不是只保存一份元素

  • map则提供两种数据类型的接口,分别放在key和value的位置上,他的比较function采用的是红黑树的comparefunction(),保存的确实是两份元素。

  • 他们两个的insert都是采用红黑树的insert_unique() 独一无二的插入 。

  • multimap和map的唯一区别就是:multimap调用的是红黑树的insert_equal(),可以重复插入而map调用的则是独一无二的插入- insert_unique(),multiset和set也一样,底层实现都是一样的,只是在插入的时候调用的方法不一样。

24 红黑树概念

  1. 它是二叉排序树(继承二叉排序树特显):

    • 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值。

    • 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值。

    • 左、右子树也分别为二叉排序树。

  2. 它满足如下几点要求:

    • 树中所有节点非红即黑。

    • 根节点必为黑节点。

    • 红节点的子节点必为黑(可以连续黑节点)(或者说不能出现连续红节点)。

    • 从根到NULL的任何路径上黑结点数相同。
      3、查找时间一定可以控制在O(logn)。

37、STL中unordered_map和map的区别和应用场景

  • map支持键值的自动排序,底层机制是红黑树,红黑树的查询和维护时间复杂度均为O(logn)O(logn)O(logn),但是空间占用比较大,因为每个节点要保持父节点、孩子节点及颜色的信息

  • unordered_map是C++ 11新添加的容器,底层机制是哈希表,通过hash函数计算元素位置,其查询时间复杂度为O(1),维护时间与bucket桶所维护的list长度有关,但是建立hash表耗时较大

  • 从两者的底层机制和特点可以看出:map适用于有序数据的应用场景,unordered_map适用于高效查询的应用场景

25 hashtable中解决冲突有哪些方法?

记住前面三种

  • 线性探测:使用hash函数计算出的位置如果已经有元素占用了,则向后依次寻找,找到表尾则回到表头,直到找到一个空位

  • 开链:每个表格维护一个list,如果hash函数计算出的格子相同,则按顺序存在这个list中

  • 再散列:发生冲突时使用另一种hash函数再计算一个地址,直到不冲突

  • 二次探测:使用hash函数计算出的位置如果已经有元素占用了,按照121^212、222^222、323^232…的步长依次寻找,如果步长是随机数序列,则称之为伪随机探测

  • 公共溢出区:一旦hash函数计算的结果相同,就放入公共溢出区

容器的时间复杂度分析

特别注意map 和 unorder_map ,插入 不要再考虑 查询了

  • vector

    • 查找 0(n)
    • 删除 0(n)
    • 插入 0(n)
    • 尾插和尾删0(1)
    • 随机访问时间复杂度 0(1)
  • list (插入和删除都是指定位置 不要想成先查找再删除 变成0(n))
    • 查找 0(n)
    • 删除 0(1)
    • 插入0(1)
  • deque
    • 查找 0(n)
    • 删除 0(n)
    • 插入 0(n)
    • 头尾插入和删除0(1)
  • set map multiset multimap
    • 查找 0(logn)
    • 删除 0(logn)
    • 插入0(logn)
  • unordered_set, unordered_map最坏就是转成链表了(我的理解0(1)找到对应桶 但是链表0(1)查询了一次)
    • 查找find O(1) 最坏情况O(n)
    • 删除erase O(1) 最坏情况O(n)
    • 插入insert O(1) 最坏情况O(n)

[STL]面试题小结(一)相关推荐

  1. 前端笔试题小结(一)

    前端笔试题小结(一) 2020-03-13 题目一: 将一个js数组去重. 样例: 输入:[ 1, "apple", 3, "a", 3, 1, 5, 6, & ...

  2. java 字符串 面试_JAVA中String介绍及常见面试题小结

    字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串. 深刻认识String 1)String为字符串常量:即String对象一旦 ...

  3. java在gc正常工作的情况下_最新JVM面试题小结,程序猿直呼内行

    这篇文章主要介绍了JVM面试题小结(2020最新版),觉得挺不错的,现在分享给大家,也给大家做个参考. Java内存区域 说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件,两个 ...

  4. linux c语言常见面试题及答案,Linux下C语言的几道经典面试题小结(分享)

    Linux下C语言的几道经典面试题小结(分享) 本篇文章整理了几道Linux下C语言的经典面试题,相信对大家更好的理解Linux下的C语言会有很大的帮助,欢迎大家探讨指正. 1.如果在Linux下使用 ...

  5. STL 中priority_queue小结

    (1)为了运用priority_queue,你必须包含头文件<queue>:#include<queue>    (2)在头文件中priority_queue定义如下: nam ...

  6. java 银行项目对于金额的面试题_2019年面试题小结

    最近大大小小面试了一些公司包括某软.某宝和其他小公司,结果都还令人满意,因此打算做一个小的总结,帮助一些同样面临跳槽或者找工作的同学抓住一些重点.就像期末考试,如果时间多,你确实需要尽可能吸收整本书的 ...

  7. 软件测试面试题小结(一)

    一.判断题 1.软件测试的目的是尽可能多的找出软件的缺陷.(Y) 2.Beta 测试是验收测试的一种.(Y) 3.验收测试是由最终用户来实施的.(N) 4.项目立项前测试人员不需要提交任何工件.(Y) ...

  8. 面试题小结 (数据分析)

    一.SQL 大部分考点围绕join连接,聚合函数,窗口函数,列转换进行命题 1.join连接 重点掌握left join和inner join 这是数据分析师使用率最高的两个语法,一般笔试题,掌握这两 ...

  9. STL bitset用法小结(详细)附蓝桥杯题:明码

    bitset用法小结 使用bitset类型需引入头文件 #include< bitset > 它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间. bitset类 ...

最新文章

  1. 2021年春季学期-信号与系统-第三次作业参考答案-第八道题
  2. cgicc thttpd经常用的调试命令
  3. 802.11w协议介绍
  4. java bigdecimal赋值_Java中BigDecimal类介绍及用法(亲测)
  5. 软件测试文档在哪里,软件测试报告技术文档
  6. PID算法搞不懂?看这篇文章。
  7. Linux常用开发环境软件-redis安装
  8. Android Studio出现Failed to open zip file. Gradle's dependency cache may be corrupt问题的解决
  9. 0.3:Before We Start
  10. 芝加哥大学计算机专业硕士,芝加哥大学计算机硕士录取条件有哪些?_托普仕留学...
  11. 液晶显示器尺寸对照表_安徽CHARACTER液晶显示屏
  12. Cherno OpenGL 教程
  13. 合成器基础(三) - 减法合成器的工作原理
  14. 光伏窗性能研究(2)——光伏窗性能研究方法和过程
  15. SQL常用脚本大全,建议收藏!
  16. 360极速浏览器插件不见了
  17. Android APP一段时间无操作显示屏保
  18. 读书笔记:《图说区块链》
  19. 【环境配置】ceres solver安装
  20. C++17值类型 (Value Categories)

热门文章

  1. win10 访问文件服务器,如何在Win10上使用SMBv1访问网络设备上的文件
  2. Vivado报错:[Runs 36-527] DCP does not exist,generate Output Products MIG ddr3 IP核后报错DCP问题解决
  3. 欢迎你、某某某同学python_新同学欢迎词
  4. beetl 页面标签_beetl模板页面使用shiro标签
  5. microsoft windows 系统更新包下载与离线安装
  6. Android聊天气泡如何使用网络.png图片实现拉伸
  7. Linux安装NVIDIA显卡驱动并配置pytorch和tensorflow环境
  8. 微信 企业付款到余额 开发 教程
  9. 利用Wireshark抓取QQ的数据流
  10. 懂得利用万有引力的猎鹰