STL笔试
STL的六大部件和联系?
STL的6大部件包括:容器(Containers)、分配器(Allocators)、算法(Algorithm)、迭代器(Iterators)、适配器(Adapters)、仿函数(Functors)。
容器用于存储数据,是与内存打交道的,其背后有分配器支撑其分配内存,基于容器写好的仿函数封装成算法,迭代器是算法对容器中数据访问的桥梁,是一种泛化的指针。从实现角度看,迭代器是一种将operator*,operator->,operator++,operator--等指针相关操作予以重载的class template。适配器,英文中adapter就是变压器的意思,用于帮助部件进行转换,包括容器适配器,仿函数适配器,迭代器适配器。
STL容器知道哪些,都解释一下,原理是什么?
序列式容器,其中每个元素均有固定位置——取决于插入时机和地点,和元素值无关。(vector、deque、list)
向量vector:在序列尾部进行插入和删除,相似的 array 是静态空间,一旦配置了就不能改变,但 vector 是动态数组,空间不足时以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对 vector 的任何操作,一旦引起空间重新配置,同时指向原vector 的所有迭代器就都失效了。
vector 的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块的 array。
优点:支持快速随机访问
缺点:对中间和开始处进行添加删除元素操作需要移动大量元素。
∴ vector 常用来保存需要经常进行随机访问的内容,并且不需要经常对中间元素进行添加删除操作。
双向队列deque:在序列头尾部都可进行插入和删除,对外声称连续空间,实际是分段连续,因为分配中央控制器map(并非map容器),map是一个连续的空间, 其每个元素都是一个指向缓冲区的指针,真正的数据在缓冲区中存放着,deque先从map中央的位置(因为双向队列,前后都可以插入元素)找到一个缓冲区地址,向该缓冲区中放入数据,空间不够时继续在map中找空闲的缓冲区来存数据。当map也不够时按照vector规则重新分配内存当作新的map,把原来map中的内容copy到新map中,并释放原空间。
优点:支持快速随机访问
缺点:需要复杂的迭代器,重载运算符
他有两个容器适配器,分别是队列queue(先进先出),堆栈stack(先进后出)
双向链表list:迭代器必须具备前移、后退的能力,节点形式来存放数据,非连续的内存空间来存放数据
优点:快速增删,不用频繁的拷贝转移
缺点:不支持随机访问和存取,不支持下标
关联式容器,元素位置取决于特定的排序准则以及元素值,和插入次序无关。(set、multiset、map、multimap)
set:元素的键值就是实值,实值就是键值。set 底层是通过红黑树(RB-tree)来实现的,由于红黑树是一种自平衡二叉搜索树,键值会自动被排序,如果set中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了set的结构,导致iterator失效,不知道应该指向之前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值。由于 set 所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 set 操作行为,都只有转调用 RB-tree 的操作行为而已。
multiset的特性以及用法和 set 完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制是 RB-tree 的 insert_equal() 而非 insert_unique()。
map 的所有元素都是 pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。同样是通过红黑树(RB-tree)来实现的,虽然不能改变key,但可以通过迭代器改变value。
其他与set一样,不赘述
容器hashtable:一段连续的空间vector,每个篮子(桶)是一个list,//buckets vector
符合Separate Chaining,编号对长度取余打散分开,碰撞就串在一起
为了避免list过长导致查询效率变慢,如果元素个数大于篮子就要rehashing
篮子个数要扩充为原来的倍数附近的素数
HashCode(编号)是通过 class HashFcn 得到的,有泛化和偏特化,
制造够乱的数字,尽量打散不要碰撞
元素最终要落到哪个篮子上,其实底层是 return hash(key) % n
常用算法及其原理
std,标准库,命名空间里
一定带两迭代器和容器沟通
1、accumulate 累计,四个参数(头、尾迭代器、一个初值,一个累计规则)
如果不设累计规则,默认把元素加至初值上
写一个一般的函数,就必须对binary_op(init,*first)可被调用
如果是函数对象,需要重载小括号
通常算法至少有两个版本,其中允许加上个自定义原则,以满足某种需求
2、for_each :在一段范围内,对每一个元素做一件你指定的事情
原理:first一直++,如果!=last,就执行 f(*first)
C++11,for( decl(变量声明) : coll(容器) ) { statement }
3、replace,replace_if,replace_copy
范围内所有等同于old_value的都以new_value取代
原理:first一直++,如果!=last且*first==old_value,
执行*first==new_value
如果是_if,就是把old_value参数替换成你所指定的比较规则(条件)
Predicate pred //判断式,返回真假
如果是_copy,把原先的元素copy到新区间,等于old_value的以new_value为值
原理:*result = *first == old_value ? new_value : *first;
4、count、count_if //遍历,
如果值和value相等 或者符合 pred(*first),计算器加1
关联式容器,可以用一个key找到data,他们拥有自己特化的算法(快速)
同样find、find_if也是如此
5、sort,原理:return(i<j)
链表是不允许随机访问,他们是有特化版本的,属于自身成员函数
6、binary_search //二分搜寻,前提排好序
原理:转调用std :: lower_bound(first,last,val)
low upper_bound
| |
10 10 10 20 20 20 30 30