zoukankan      html  css  js  c++  java
  • STL

    STL概况

    面试题 

    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存储这个集合比较好

    vector 和 list 的区别

    1) vector, 连续存储的容器,动态数组,在堆上分配空间 ;

    底层实现:数组

    如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素

    适用场景:经常随机访问,且不经常对非尾节点进行插入删除。

    2)list,动态链表,在堆上分配空间,每插入一个元素都会分配空间,每删除一个元素都会释放空间。

    底层:双向链表

    访问:随机访问性能很差,只能快速访问头尾节点

    适用场景:经常插入删除大量数据

    3) vector在中间节点进行插入删除会导致内存拷贝,list不会。

    4) vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。

    5) vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。

    list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

    vector每次insert或erase之后,以前保存的iterator会不会失效?

    理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存,但是vector一般底层是用数组实现的,我们仔细考虑数组的特性,不难得出另一个结论,insert时,假设insert位置在p,分两种情况:

    a) 容器还有空余空间,不重新分配内存,那么p之前的迭代器都有效,p之后的迭 代器都失效

    b) 容器重新分配了内存,那么p之后的迭代器都无效,erase时,假设erase位置在p,则p之前的迭代器都有效并且p指向下一个元素位置(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效

     

    STL对于小内存块请求与释放的处理

      STL考虑到小型内存区块的碎片问题,设计了双层级配置器,第一级配置直接使用malloc()和free();第二级配置器则视情况采用不同的策略,当配置区大于128bytes时,直接调用第一级配置器;当配置区块小于128bytes时,便不借助第一级配置器,而使用一个memory pool来实现。究竟是使用第一级配置器还是第二级配置器,由一个宏定义来控制。SGI STL中默认使用第二级配置器。
      二级配置器会将任何小额区块的内存需求量上调至8的倍数,(例如需求是30bytes,则自动调整为32bytes),并且在它内部会维护16个free-list, 各自管理大小分别为8, 16, 24,…,128bytes的小额区块,这样当有小额内存配置需求时,直接从对应的free list中拔出对应大小的内存(8的倍数);当客户端归还内存时,将根据归还内存块的大小,将需要归还的内存插入到对应free list的最顶端。
    小结:
    STL中的内存分配器实际上是基于空闲列表(free list)的分配策略,最主要的特点是通过组织16个空闲列表,对小对象的分配做了优化。
    1)小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。
    2)避免内存碎片的产生。零乱的内存碎片不仅会浪费内存空间,而且会给OS的内存管理造成压力。
    3)尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,
    但是,这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的,比如上述:
    stl::vector array;
    array是知道它需要分配的对象大小为sizeof(int)。一个通用的内存分配器是不需要知道待释放内存的大小的,类似于free(p)。

    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做下标。

     

    unordered_map和map

    内部实现机理

    • map: map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率。
    • unordered_map: unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的

    优缺点以及适用处

    map优点: 

    • 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
    • 红黑树,内部实现一个红黑书使得map的很多操作在的时间复杂度下就可以实现,因此效率非常的高
    • 对于那些有顺序要求的问题,用map会更高效一些

    map 缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间

    unordered_map 优点: 

    因为内部实现了哈希表,因此其查找速度非常的快

    unordered_map  缺点: 
    对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map,
    哈希表的建立比较耗费时间

     

    STL 中迭代器的作用,有指针为何还要迭代器

    1) Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示

    2) 迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->、*、++、--等,相当于一种智能指针。

    3) 迭代器产生原因:Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。

     

    STL 迭代器是怎么删除元素的呢

    1) 对于序列容器vector,deque来说,使用erase(itertor)后,后边的每个元素的迭代器都会失效,但是后边每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器;

    2) 对于关联容器map set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

    3) 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator。

     

    平衡二叉树(AVL树)和红黑树

    1)平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。

    2)红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑),红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。

    3)所以红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。

    哈希表(hash表)

    哈希表的实现主要包括构造哈希和处理哈希冲突构造哈希,主要包括直接地址法,除留余数法

    处理哈希冲突:当哈希表关键字集合很大时,关键字值不同的元素可能会映射到哈希表的同一地址上,这样的现象称为哈希冲突。常用的解决方法有:

    1) 开放定址法,冲突时,用某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。(如,线性探测,平方探测)

    2) 再哈希法:当发生冲突时,用另一个哈希函数计算地址值,直到冲突不再发生。

    3) 链地址法:将所有哈希值相同的key通过链表存储,key按顺序插入链表中。

    参考

    Lucky&C++面试之STL

    STL笔试面试题总结(干货)

     

  • 相关阅读:
    [其他]将Windows Terminal添加到右键菜单
    [VS Code]在自己的Ubuntu服务器上构建VSCode Online
    [Go]goFileView-基于Golang的在线Office全家桶预览
    [Go]基于Go语言的Web路由转发,多个网站共享一个端口(新版本,支持WebSocket)
    [WSL]在Windows10子系统里安装运行桌面(xUbuntu)
    [Go]使用Golang对鸢尾花数据集进行k-means聚类
    [Python+JavaScript]JS调用摄像头并拍照,上传至tornado后端并转换为PIL的Image
    [Python]Python基于OpenCV批量提取视频中的人脸并保存
    [WSL]Windows10 Ubuntu子系统编译安装线程安全版LAMP
    [Go]基于Go语言的Web路由转发,多个网站共享一个端口(存在问题,已经抛弃,新解决方案请看新博客)
  • 原文地址:https://www.cnblogs.com/chen-cs/p/13199052.html
Copyright © 2011-2022 走看看