目录
1.红黑树
1.1 为何先介绍红黑树?
答:因为set和map的底层都是以红黑树构建,所以set和map几乎所有的操作行为,都只是调用RB-Tree的操作函数;红黑树是平衡二叉搜索树的其中一种,AVL是另一种平衡二叉搜索树。
红黑树的本质其实是对概念模型2-3-4树的一种实现,因此我们先来关注2-3-4树。2-3-4树是阶数为4的B树,B树,全名BalanceTree,平衡树。这种结构主要用来做查找。
知道set和map的底层都是红黑树,下面所示的set和map的特点都会很容易理解;
关于红黑树参考这篇博客:硬核图解面试最怕的红黑树【建议反复摩擦】_敖 丙的博客-CSDN博客_面试写红黑树
2.set
2.1 set的特性
<1> set元素的键值就是实值,实值就是键值;
<2> set不允许两个元素有同样的键值,因为set使用的是红黑树的insert_unique(),而非insert_equal()
<3> set的所有元素都会根据元素的键值自动排序,默认升序排列
<4> 无法通过set的迭代器改变set的元素值,因为set的元素值就是键值,键值关系到set元素的排列规则,set的iterator是constant iterator;
<5> set进行元素的删除或插入操作时,操作之前所有的迭代器在操作完成后都依然有效(仍然可以通过该迭代器找到对应的元素),当然被删除的那个元素的迭代器失效(无法对该迭代器进行任何操作),因为set和map的底层是红黑树,各节点之间通过指针链接;
注意:STL的begin()和end()不是迭代器,而是成员函数,bengin()的返回值是指向当前容器中第一个元素的迭代器;begin()和end()从容器创建到销毁期间一直都会指向容器中第一个元素的位置和最后一个元素的下一个位置,删除begin()所指向的元素后,begin()的返回值会自动找到下一个元素;
2.2 set的操作函数
1.insert
set容器的insert操作有三种重载方式,分别为
//函数原型
1. pair<iterator, bool> insert (const value_type& x)
2. iterator insert (iterator positon, const value_type& x)
3. void insert (inputIterator first, InputItreator last)
//举例
#include <set>
using namespace std;
int main(){
set<int> s;
s.insert(set<int>::value_type(1)); //等价于s.insert(1);set<int>::value_type用于强制数据类型转化
s.insert(s.begin(), set<int>::value_type(2));
s.insert(s.begin(), s.end());
}
三种插入方法的参数不同,返回值也不同。
方法1,单个参数,返回值为pair<iterator,bool>对组类型,bool表示插入是否成功,若bool值为true,iterator为插入成功后元素的迭代器,否则是指向已存在的等值元素的迭代器;
方法2,两个参数,(iterator positon, const value_type& x),参数1代表要插入的位置,参数2为要插入元素的键值(也即实值)。返回值为插入成功后元素的迭代器
方法3,两个参数,作用暂时没搞明白;
2.erase
set的erase于insert一致,也有三种重载方法
//函数原型
//删除迭代器position指向的元素
1. void erase (iterator position)
//删除指定键值的元素
2. size_type erase (const key_type& x)
//删除fist至last范围内的元素
3. void erase (itreator first, iterator last)
//举例
set<string> s;
s.insert(s.begin(), set<string>::value_type("a"));
s.insert("c");
for (auto it=s.cbegin(); it != s.end(); it++) {
cout << *it;
}
s.erase("a");
s.erase(s.begin());
s.erase(s.begin(), s.end());
方法2中的返回值,表示删除指定元素是否成功,1:成功,0:失败
set<string> s;
s.insert(s.begin(), set<string>::value_type("a"));
s.insert("c");
s.insert("b");
cout << s.erase("d");
cout << s.erase("b");
//运行结果
0
1
3.find
除了可以使用STL的find()库函数外,set和map都有自己的find函数,根据红黑树的树形结构进行查找,更加快速(比较方法为:小就往左走,大就往右走),STL的find()库函数会遍历搜索
//函数原型
iterator find (sonst key_type& x) {
return t.find(x);
} //返回值为指向待查元素的迭代器
//举例
set<string> s;
s.insert(s.begin(), set<string>::value_type("a"));
s.insert("c");
s.insert("b");
//set自己的find函数
if (s.find("c") != s.end()) {
cout << "方法1,找到了" << endl;
}
STL的find()库函数
if (find(s.begin(), s.end(), "c") != s.end()) {
cout << "方法2找到了" << endl;
}
//运行结果
方法1找到了
方法2找到了
4.empty
判断当前set容器是否为空
bool empty () const
{
return t.empty;
}
5.clear
清楚当前set容器的所有元素
void clear();
6.cout
cout函数用来统计某特定元素的个数,set中因为不能出现重复的键值,所以cout的返回值只能为0或1;
set<string> s;
s.insert(s.begin(), set<string>::value_type("a"));
s.insert("c");
s.insert("b");
cout << s.count("1");
cout << s.cout("a");
运行结果//
0
1
7.lower_bound
8.upper_bound
9.equal_range
STL的map、multimap、set、multiset都有三个比较特殊的函数,lower_bound、upper_bound、equal_range。
原型如下:
iterator lower_bound (const value_type& val) const; iterator upper_bound (const value_type& val) const; pair<iterator,iterator> equal_range (const value_type& val) const;
上面三个函数是相关联的,equal_range返回两个迭代器,第一个迭代器是lower_bound的返回值,第二个迭代器是upper_bound的返回值。(注意是使用相同val值调用的情况下。)
从msdn及c++标准来看,lower_bound、upper_bound两个函数用于记录允许元素重复出现的数据集中给定关键字val在当前集合中区间范围。或是在不允许元素重复出现的容器中≥val的最小元素的迭代器
对诸如set、map这种关键字唯一的集合而言,lower_bound、upper_bound返回迭代器不相同
1.关键字val在集合中不存在,二者返回结果一样,函数返回的是≥val的最小元素的迭代器。
2.如果关键在val在集合中存在,lower_bound返回val关键字本身的迭代器,upper_bound返回关键字val下一个元素迭代器。
对multiset、multimap这类关键字不唯一的集合而言。按照关键字后面一个关键字在集合中出现的次数可分为:
1.关键字val不在集合中,这种情况下与set、map一致,函数返回的是≥val的最小元素的迭代器
2.关键字val出现在集合中,但是是唯一的,这种情况和set、map情况类似;
3.关键字val出现在集合中,出现多次,这种情况下lower_bound返回第一个出现关键字val对应的迭代器,upper_bound返回位于关键字val对应位置后第一个不是val的位置的迭代器;也就是lower_bound、upper_bound构成的上下限的区间(equal_range返回值),该迭代区间的长度表示关键字val在集合中出现的次数。
如果二者返回值相等,表示关键字val在集合中未出现。(例外情况,集合中的所有元素均≥关键字val,返回集合的iterator::end)
如果迭代器区间长度是1,表示关键字val在集合中仅出现1次。
迭代器区间长度大于1,则表示关键字val出现多次,并且一定不是set和map这种关键字唯一的集合
3.map
3.1 map的特点
<1> map的所有元素都是pair<key,value>,同时拥有键值(key)和实值(value),
<2> map的所有元素都会根据元素的键值自动排序,所以map中键值才是关键,实值不重要
<3> map不允许两个元素拥有相同的键值,但是键值不同的前提下可以拥有相同的实值
<4>无法通过map的迭代器修改map元素的键值,但可以修改map元素的实值,因为map的键值影响map中元素的排列规则,随意修改会破坏整个map,而修改实值则不影响;
<5> map进行元素的删除或插入操作时,操作之前所有的迭代器在操作完成后都依然有效(仍然可以通过该迭代器找到对应的元素),当然被删除的那个元素的迭代器失效
3.2 map的操作函数
1.insert
map的insert与set一致,也有三种重载方式,如下所示
//函数原型
<1> pair<iterator, bool> insert (const value_type& x)
<2> iterator insert (iterator position, const value_type& x)
<3> void insert (InputIterator first, InputIterator last)
//举例
#include <string>
#include <map>
int main(){
map<string, int> m;
m.insert(pair<string , int>("g", 15));
pair<string, int> temp("n",30);
m.insert(temp);
m.insert(m.begin(), pair<string, int>("d", 17));
m[string("a")] = 20; //以数组下标形式插入元素,键值为数组下标,实值为数组元素值
m.insert(m.end()--, map<string, int>::value_type("z", 12));
auto it = m.begin();
for (; it != m.end(); it++) {
cout << "<" << it->first << "," << it->second << ">" << endl;
}
return 0;
}
//运行结果
<a,20>
<d,17>
<g,15>
<n,30>
<z,12>
由上述运行结果可知:
1.map插入元素的数据型别必须是pair<_ , _>对组。
2.map插入元素后,各元素在map中的顺序根据各元素的键值来自动排序,与插入的先后顺序无关,哪怕认为指定插入位置也无作用
2.erase
map的erase与set一致,也有三种重载方法
<1>iterator erase(iterator position)
<2>size_type erase(const key_type& x)
<3>void erase(iterator first, iterator last)
3.find
map的find和set基本一致
<1>iterator find(const key_type& x)
auto it = m.find("g");
cout << it->first << endl;
cout << m.find("g")->second;
运行结果:
g
15
由上述代码可知:map的元素访问使用 iterator->first来访问元素的键值,使用iterator->second来访问元素的实值;
4.cout
5.clear
6.lower_bound
7.upper_bound
8.equal_range
4-8中操作在set中已经详细描述,用法一致,不再赘述
9.map与set不同的地方
map翻译为映射,映射指键值到实值的映射,实际上map像是一个数组,map元素的键值可看作数组的下标,唯一不可重复,不可修改;map元素的实值则可看作数组中某下标的值,可重复,可修改
演示用数组下标的方式访问和赋值map元素
m["g"] = 20; //插入,左值
int a = m["g"]; //访问,右值运用
cout << a << endl;
auto it = m.find("g");
//it->first = "z"; //错误,无法修改键值
it->second = 22; //通过迭代器,修改实值
int b = m["g"]; //访问,右值运用
cout << b << endl;
//运行结果
20
22
4. multiset和multimap
multiset的特性以及用法与set完全一致,唯一的区别是:multiset允许键值重复,因为它的插入操作采用的是RB-Tree的insert_equal();
multimap的特性以及用法与map完全一致,唯一的区别是:multimap允许键值重复,因为它的插入操作采用的是RB-Tree的insert_equal();