map和 set 相关的问题
1. 为什么map和set的插入删除比其它容器的效率高?
2. insert的时候迭代器会不会失效?
3. 为什么直接使用函数会比STL中的map,set快?
4. 为什么map的底层要用红黑树实现(不用val树)?
5. 红黑树与Val树的区别
6. 关联式容器的插入,删除效率 为什么比序列式的容器高?
7. 红黑树和哈希表的迭代器类型
8. map和set的区别
9. set中的元素为什么不允许修改?
10.为什么multimap不支持[]
11.红黑树如何实现出map和set两种数据结构
-
为什么map和set的插入删除比其它容器的效率高?
对于关联式容器来说,不需要做内存的拷贝和内存的移动,他们的存储方式是以节点的方式存储,只需要保留对父子节点的指针,所以实现插入和删除的时候只需要移动节点的指针。 -
insert的时候迭代器会不会失效?
不会失效,map和set的节点是malloc申请出来的,尽管insert了新的节点,内存并没有变,变得只是指针的指向。所以insert的时候迭代器并不会失效。但是对于序列式容器来说来说,当插入一个新的元素,有可能需要扩容,因为需要保证内存的连续存放,所以会引起迭代器的失效。 -
为什么直接使用函数会比STL中的map,set快? – 其实问的是空间配置器
如果直接使用这些函数的话,你会自己申请很多的节点,而且频繁的插入和删除的时候,内存碎片就会存在,STL采用的是空间配置器Allocator分配内存,采用内存池+l链表的方式管理内存,大大减少了内存碎片的使用 -
为什么map的底层要用红黑树实现(不用val树)?
通过红黑树的五条性质可以得出红黑树上最长的路径不会超过最短的路径的两倍。因此是一种近似的二叉搜索树,它并没有二叉搜索树的趋于线性化的弊端,对于严格的Val树来说它的旋转次数少,所以对于查找,删除,修改较多的情况下,通常用红黑树 -
红黑树与Val树的比较
- 红黑树是近似平衡的二叉搜索树,而Val树是严格平衡的二叉搜索树
- AVL是严格平衡的,频繁的插入和删除,导致大量的旋转(左旋,右旋,左右双旋,右左双旋),导致效率降低
- 红黑树是近似平衡的,插入最多旋转2次,删除最多旋转3次。
- 红黑树在查找、插入删除的复杂度都是O(logn), 且性能稳定
- 另外红黑树保证节点的键值K是严格有序的
-
关联式容器的插入,删除效率 为什么比序列式的容器高?
他的插入删除效率比其他序列容器高是因为不需要做内存拷贝和内存移动,而直接替换指向节点的指针即可 -
红黑树和哈希表的迭代器类型
红黑树有正向迭代器,反向迭代器 红黑树的可以正向遍历,也可以反向遍历 rbegin(),rend()红黑树的迭代器++:
- 右孩子结点不为空的时候,找右子树的最左节点
- 右孩子为空的时候, 向上找父节点,知道 cur != parent->right
红黑树的迭代器–:
- 左孩子结点不为空,找左子树的最右节点
- 左孩子节点为空的时候,向上找父节点,直到 cur != parent->left为止
begin(): 红黑树中最小节点的位置(即最左侧的位置 Leftmost() )
end(): 最大节点的下一个位置,但是要对迭代器进行–操作,找不到最后一个节点,所以end()节点放在根节点的位置
-
哈希表只有正向迭代器
为什么只有正向的迭代器?
因为哈希表的底层数据结构是链表数组,需要遍历单链表,所以只需要有正向的迭代器Hash的迭代器的使用 :
首先从数组的起始位置开始向后遍历,直到遇到一个不为空的位置的 链表头部就是 begin的位置,++的含义就是cur->next 或者当这个链表已经走到头的时候,访问下一个不为空的链表数组的位置
-
map和set的区别
相同点:
- 都是关联式容器,底层都是红黑树
- 都是严格有序,且键值不允许重复
- map和set的key值都不允许修改
不同点:
- map的V是kv的键值对,而set的V是v关键词对应的值,且与key一致 (set的底层放的是 value-value的键值对) – 一种映射关系
- map支持operator[]修改键值key对应的value,而set不允许修改,因为set的value值就是key值,所以不允许修改关键词
-
set中的元素为什么不允许修改?
- set的底层的迭代器是const_iterator
- set存放的value实际上就是key的值,key的值是我们用来排序的,所以不允许修改
-
为什么multimap不支持[]
因为multimap中允许重复的键值,如果修改的话,要修改多份,降低查找效率,和修改效率。STL是追求效率的 -
红黑树如何实现出map和set两种数据结构
依靠的是模板参数。大致是这样的template<K,V,keyofValue>
第1个参数是键值key,第2个参数是根据map和set是不同的,map是key-value的键值对,而set就是value值,值等于key值,第三个参数是根据不同的Value类型获取key值(map就是 pair<K,V>, set 就是 K )
为什么要提供第三个参数,因为我们每次插入删除,查找的时候都是根据T的类型来插入的,需要提取出key值。 因为我们的底层时候操作的时候就是 RBTreeNode 类型的节点,这个T有可能是pair<>,也有可能是int, 所以我们要提取T类型的key来进行比较,插入删除
比如红黑树中map的实现片段
//K
template<class K, class V>
class map
{
public:
struct MapKeyOfValue //实现这个仿函数类
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private: // K , V , 提取K值的仿函数
RBTree<K, pair<K, V>, MapKeyOfValue> _t; // _t就是红黑树
红黑树节点操作的是 RBTreeNode<T> 类型的,这里的T就是pair<k,v> 所以要提取k值进行比较
};