容器 & 算法

版权声明: https://blog.csdn.net/qq_41880190/article/details/88848337

map & set

map、set 区别、分别怎样实现、数据存放形式、如何扩容

map

map和set一样是关联式容器,它们的底层容器都是红黑树,区别就在于map的值不作为键,键和值是分开的,以键值对进行存储

为了方便查找,键起到索引的作用,值则表示与索引相关联的数据,以红黑树的结构实现插入、删除等操作可以在 O(logn) 时间内完成

  • map 以红黑树作为底层
  • 其所有元素都是键 + 值存在
  • 不允许键重复
  • 所有元素都是通过键自动排序的
  • map 的键是不能修改的,但是其键对应的值是可以修改的

在 map 中,一个键对应一个值,其中键不允许重复,不允许修改,但是键对应的值是可以修改的

map 常用的操作

map<int, string> a;map<string, int> a;	//支持多种类型
map1.insert(pair<int, string>(102, "asdfgh"));	//插入数据
map1.insert(map<int, string>::value_type(102, "asdfgh"));
map1[102] = "asdfgh";
map1.find(key);	//元素查找,返回一个迭代器指向键值为 key 的元素,如果没有找到则返回 map 尾部的迭代器
//元素删除:先查找元素,找到之后在删除
map<int, string>::iterator it = map1.find(key);
map1.erase(it);
//map 中的 swap 交换的是两个容器,而不是一个容器中的元素
sort 函数,因为 map 中 key 是按照升序排列的,所以不能使用 sort 函数

从 STL 源码分析 map

与 set 相同,map 同样是以红黑树 RB_Tree 为底层机制的关联式容器。map 的每一个元素都拥有两个值,一个键值(key)和一个实值(value)。它的内部实现是用一个 pair 来保存这个两个值。所以,map 的每一个元素又是一个 pair。下面是 STL 源码中 stl_pair.h 对 pair 的定义

template<class T1, class T2>
struct pair{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair()
		:first(T1())
		,second(T2())
	{}
	pair(const T1& a, const T2& b)
		:first(a)
		,second(b)
	{}
#ifdef __STL_MEMBER_TEMPLATES
	template<class U1, class U2>
	pair(const pair<U1, U2>& p)
		:first(p.first)
		,second(p.second)
	{}
#endif
};

因为 map 的底层是一棵红黑树,红黑树又是一棵平衡二叉搜索树,自动排序效果很好,所有的元素都会根据元素的键值自动排序,这一点也限制了我们不能修改元素的键值,键值关系到 map 元素的排列规则,任意改变 map 的键值都会影响到 map 的组织结构,其中 STL 中是这样限制我们不能修改键值的

typedef pair<const key, T> value_type

他将 pair 重新命名为 value_type,同时将 pair 的键值声明为 const 类型,限制我们不可以修改它

map 的底层是红黑树,map 的各种操作接口红黑树也都提供了,几乎所有的操作行为,都是红黑树的操作行为,从 STL 源码中,map 又是如何调用红黑树进行操作的

其实在 STL 中,大多数仅仅是将 RBTree 重新换个名字而已,源码分析如下

private:
	//先对红黑树重命名
	typedef rb)tree<key_type, value_type, selectlst<value_type>, key_compare, Alloc> rep_type;
	rep_type t;
public:
	//在对类型重命名
	typedef typename rep_type::pointer pointer;
	typedef typename rep_type::const_pointer const_pointer;
	typedef typename rep_type::reference reference;
  	typedef typename rep_type::const_reference const_reference;
  	typedef typename rep_type::iterator iterator;
  	typedef typename rep_type::const_iterator const_iterator;
  	typedef typename rep_type::reverse_iterator reverse_iterator;
  	typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  	typedef typename rep_type::size_type size_type;
  	typedef typename rep_type::difference_type difference_type;

当然它也有自己的东西

	typedef key key_type;
	typedef T data_type;
	typedef T mapped_type;
	typedef pair<const key, T> value_type;
	typedef compare key_compare;

对象成员

map 源码中对私有成员的定义如下

private:
	typedef rb_tree<key_type, value_type, selectlst<value_type>, key_compare, Alloc> rep_type;
	rep_type t;

我们可以看到,它直接将一个红黑树的 rb_tree 重命名成 rep_type,并且利用 rep_type 构建了一个红黑树对象 t,

方法

构造

explicit map(const compare& comp = compare(), const Allocator& = Allocator());

关键字 explicit 的作用是防止隐式转换或者说防止构造函数被隐式调用,参数 compare 的作用是传入一个比较器,当然 compare 的作用就是决定排的是升序还是降序,它内部有实现了一个 operator(),功能是大小比较

总的来说,它在内部实现就直接调用了红黑树的构造函数,从 STL 源码看出,它的缺省构造函数和非缺省构造函数,都是直接调用红黑树的

源码

map() : t(comapre()){}
explicit map(const comapre& comp) : t(comp){}

另外一个构造函数

template<class InputIterator>
map(InputIterator first, InputIterator last, const comapre& comp = comapre(), const Allocator& = Allocator());

这个构造函数有一个模板参数,这个参数的类型一般是迭代器,通过传入 first 和 last 两个迭代器,将座闭右开空间[first,last) 的内容插入新构造的 map 中,Allocator 是一个空间配置器

使用模板参数,必定意味着它有多种实现,在 STL 源码中,他通过改变参数,实现函数重载,进而来实现多种参数类型传参

源码:

#ifdef __STL_MEMBER_TEMPLATES
  	//原版
	template <class InputIterator>
  	map(InputIterator first, InputIterator last)
    	: t(Compare()) { t.insert_unique(first, last); }
  	template <class InputIterator>
  	map(InputIterator first, InputIterator last, const Compare& comp)
    	: t(comp) { t.insert_unique(first, last); }
#else
  	//value_type(也就是pair)的指针类型
  	map(const value_type* first, const value_type* last)
    	: t(Compare()) { t.insert_unique(first, last); }
  	map(const value_type* first, const value_type* last, const Compare& comp)
    	: t(comp) { t.insert_unique(first, last); }
  	//const类型迭代器
  	map(const_iterator first, const_iterator last)
    	: t(Compare()) { t.insert_unique(first, last); }
  	map(const_iterator first, const_iterator last, const Compare& comp)
    	: t(comp) { t.insert_unique(first, last); }
#endif /* __STL_MEMBER_TEMPLATES */

拷贝构造

map(const map<key, T, comapre, Allocator>& x);

直接调用红黑树拷贝构造

同样,它直接调用红黑树的赋值运算符重载函数,实现自己的功能

map<key, T, compare, Alloc>& operator=(const map<key, T, compare, Alloc>& x)
{
    t = x.t;
    return *this;
}

功能方法

erase、swap、clear

void erase(iterator position) {
    t.erase(position);
}
size_type erase(const key_type& x){
    return t.erase(x);
}
void erase(iterator first, iterator last){
    t.erase(first, last);
}
void clear(){
    t.clear(); 
}
template <class Key, class T, class Compare, class Alloc>
inline void swap(map<Key, T, Compare, Alloc>& x,map<Key, T, Compare, Alloc>& y) {
    x.swap(y);
}

<!Documents>

set

set 是一种关联式容器,它用于存储数据,并且能从一个数据集合中取出数据,它的每个元素的值必须唯一,而且 set 会根据该值对数据进行排序,每个元素的值不能直接被改变,内部采用红黑树,multiset 跟 set 类似,唯一的区别就是 multiset 允许键值重复

  • set 以红黑树作为底层
  • 所存储元素只有键没有值
  • 不允许出现键值重复
  • 所有元素都会自动排序
  • 不能通过迭代器来改变 set 的值,因为 set 的值就是键

针对这五点来说,前四点都不用再多作说明,第五点需要做一下说明。如果 set 中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了 set 的结构,导致iterator 失效,不知道应该指向之前的位置,还是指向改变后的位置。所以 STL 中将 set 的迭代器设置成 const,不允许修改迭代器的值。

set 与 map 的区别比较

unordered_map & unordered_set

STL

resize、reserve 区别、容器如何扩容、各种操作时间复杂度、迭代器如何删除元素 & 失效、

猜你喜欢

转载自blog.csdn.net/qq_41880190/article/details/88848337