文章目录
一、序列容器和关联式容器
C++STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
标准的关联式容器分为set(集合)和map(映射表)两大类,以及这两大类的衍生提multiset(多键集合)和multimap(多键映射表)。
关联式容器中每个元素都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(红黑树或哈希表)会依照其键值(key)的大小以某种特定的规则将这个元素放到适当的位置。这也就意味着,关联式容器没有所谓的头尾,自然也就不会有push_back()、push_front()、pop_back()、pop_front()、begin()、end()操作。
由于关联式容器并不像序列容器那样按线性无序存储,而是按照特定的规则存储(红黑树或哈希表的规则),且可以通过key找到value,因此在数据检索时比序列式容器效率更高,但是相应的,其遍历效率就要比序列容器低。具体的效率可以查看这篇文章C++拾趣——STL容器的插入、删除、遍历和查找操作性能对比(ubuntu g++)——遍历和查找。
注意: C++STL当中的stack、queue和priority_queue属于容器适配器,它们默认使用的基础容器分别是deque、deque和vector。
二、树形结构与哈希结构
根据应用场景的不同,C++STL总共实现了两种不同结构的关联式容器:树型结构和哈希结构。
关联式容器 | 容器结构 | 底层实现 |
---|---|---|
set、map、multiset、multimap | 树型结构 | 平衡搜索树(即红黑树) |
unordered_set、unordered_map、unordered_multiset、unordered_multimap | 哈希结构 | 哈希表 |
set、map、multiset、multimap 这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列,其查找效率为O(logN),适合需要将元素进行排序的场景或者需要元素去重的场景。
而哈希结构的容器中的元素并不有序,但是其查找效率为O(N),适合频繁查找的场景。
三、set
set具有以下特性:
- set中所有元素都会根据元素的键值自动被排序,set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。(但在底层实际存放的是由<value, value>构成的键值对)
- set中不允许两个元素有相同的键值,可以运用此特性来去重。
- set中的元素值就是其键值,因此不能被修改。 因为set在底层是用红黑树来实现的,若是对当中某个结点的值进行了修改,那么就会破坏红黑树的结构。
- set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较。
- 由于set中的元素默认按照小于来比较,因此使用set的迭代器遍历set中的元素,可以得到有序序列
- set查找的效率为红黑树查找的效率,也就是O(logN)
3.1. set的成员函数
构造方法
和其他容器一样,set支持默认构造、拷贝构造、迭代器构造,同时还可以在构造时指定其比较方式(默认为less,遍历以后为升序):
set<int> s1; //构造int类型的空容器
set<int> s2(s1); //拷贝构造
string str("abcd");
set<char> s3(str.begin(), str.end()); //使用迭代器区间构造
set < int, greater<int>> s4; //比较方式指定为大于,遍历后为降序
set当中常用的成员函数
成员函数 | 功能 |
---|---|
insert | 插入指定元素 |
erase | 删除指定元素 |
find | 查找指定元素 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定元素值的元素个数 |
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
rbegin | 获取容器中最后一个元素的反向迭代器 |
rend | 获取容器中第一个元素前一个位置的反向迭代器 |
除此之外,算法库(头文件为<algorithm>
)中还设计了求集合有关的算法,分别是求并集(set_union),交集(set_intersection),差集(set_difference),对称差集(set_symmetric_difference)。
这四个函数的参数相同,都需要 5 个参数:两个迭代器用来指定左操作数的集合范围,另两个迭代器用来作为右操作数的集合范围,还有一个迭代器用来指向结果集合的存放位置。
如果不保存运算结果;可以用一个流迭代器输出这些元素:
另外,这四个函数不光set可以使用,其他容器包括数组本身也可以使用,只需要传入迭代器即可。
三、multiset
multiset容器与set容器的底层实现一样,都是平衡搜索树(红黑树),其次,multiset容器和set容器所提供的成员函数的接口都是基本一致的。multiset容器和set容器的唯一区别就是,multiset允许键值冗余,即multiset容器当中存储的元素是可以重复的。
由于multiset容器允许键值冗余,因此两个容器中成员函数find和count的意义也有所不同:
成员函数find | 功能 |
---|---|
set对象 | 返回值为val的元素的迭代器 |
multiset对象 | 返回底层搜索树中序的第一个值为val的元素的迭代器 |
成员函数count | 功能 |
---|---|
set对象 | 值为val的元素存在则返回1,不存在则返回0 |
multiset对象 | 返回值为val的元素个数 |
四、map
map具有以下特性:
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair。
- 在内部,map中的元素总是按照键值key进行比较排序的,同样的,key值不可以修改,但是key值对应的value值可以修改。
- map不允许两个元素拥有同样的键值。
- map容器支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- map查找的效率为红黑树查找的效率,也就是O(logN)
- map与set一样,在删除元素时都会造成迭代器失效的问题。
4.1. map的成员函数
构造方法
map<int, double> m1; //默认构造,构造一个key为int类型,value为double类型的空容器
map<int, double> m2(m1); //拷贝构造
map<int, double> m3(m2.begin(), m2.end()); //迭代器构造
map<int, double, greater<int>> m4; //比较方式指定为大于
由于map中存放的是key和value,所以拷贝构造和迭代器构造不支持传入数组、vector、set等其他容器,只能传入map或map的迭代器。
map的插入insert
//插入一个键值对val,返回值为一个键值对,
//其first为该节点的迭代器(插入成功后的节点或原来已经存在的节点),second为插入成功true或失败false
pair<iterator,bool> insert (const value_type& val);
//在迭代器position的位置插入一个键值对val
iterator insert (iterator position, const value_type& val);
//插入一个指定的迭代器区间
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
其中,value_type是对键值对的重命名:
//该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
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)
{}
};
typedef pair<const Key, T> value_type;//重命名
因此,在插入时需要插入键值对对象,而不是单纯的插入两个数:
map<int, double>m;
//m.insert(1,1.1); 插入失败
m.insert(pair<int, double>(1, 1.1));//匿名对象
m.insert(make_pair(2, 2.2));//调用make_pair函数模板插入
至于如何获取键值对对象,可以使用匿名对象的方式,也可以使用make_pair函数,这个函数会根据传入的key和value的类型进行自动隐式推导,最终构造并返回一个对应的pair对象。
insert的返回值pair<iterator,bool>
insert函数的返回值也是一个pair对象,该pair对象中第一个成员的类型是map的迭代器类型,第二个成员的类型的一个bool类型,具体含义如下:
- 若待插入元素的键值key在map当中不存在,则insert函数插入成功,并返回插入后元素的迭代器和true。
- 若待插入元素的键值key在map当中已经存在,则insert函数插入失败,并返回map当中键值为key的元素的迭代器和false。
map的查找find
map的查找函数是根据所给key值在map当中进行查找,若找到了,则返回对应元素的迭代器,若未找到,则返回容器中最后一个元素下一个位置的正向迭代器,也就是end()。
map的删除erase
该函数可以根据key值进行删除,也可以根据迭代器或迭代器区间进行删除。
map的[ ]运算符重载
[ ]运算符重载函数的参数就是一个key值,而这个函数的函数体和返回值如下:
mapped_type& operator[] (const key_type& k);
( *( (this->insert(make_pair(k, mapped_type()))) .first ) ).second
其中,mapped_type相当于value:
实际上[ ]运算符重载实现的逻辑实际上就是以下三个步骤:
- 调用insert函数插入键值对,第一个参数为k,第二个参数为value的默认构造函数生成的匿名对象。
(this->insert(make_pair(k, mapped_type())))
- insert的返回值为pair<iterator,bool>,从返回值中获取迭代器。
*( (this->insert(make_pair(k, mapped_type()))) .first
- 返回该迭代器位置元素的值value的引用。
( *( (this->insert(make_pair(k, mapped_type()))) .first ) ).second
总体可以拆分为下面这种写法:
mapped_type& operator[] (const key_type& k)
{
//1、调用insert函数插入键值对
pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));
//2、拿出从insert函数获取到的迭代器
iterator it = ret.first;
//3、返回该迭代器位置元素的值value
return it->second;
}
也就是说,[ ]可以先对不存在的key进行插入(key对应的value为默认构造函数生成的匿名对象),并拿到key值对应的value值的引用,由于是引用,所以此时就可以对value进行修改。
map的其他成员函数
除了上述成员函数外,map当中还有如下几个常用的成员函数:
成员函数 | 功能 |
---|---|
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定key值的元素个数 |
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
rbegin | 获取容器中最后一个元素的反向迭代器 |
rend | 获取容器中第一个元素前一个位置的反向迭代器 |
4.2. 统计出现次数的三种方式
给定一个string数组,用来统计每个元素出现的次数,使用[ ]较为方便。
void test_map2()
{
string arr[] = { "香蕉","苹果","香蕉","苹果","草莓" };
//统计方式1
//map<string, int> countMap;
//for (const auto& str : arr)
//{
// map<string, int>::iterator ret = countMap.find(str);
// //第一次出现,插入<str,1>,后续出现就对其次数++
// if (ret != countMap.end())
// {
// ret->second++;
// }
// else
// {
// countMap.insert(make_pair(str, 1));
// }
//}
//for (auto& e : countMap)
//{
// cout << e.first << "--" << e.second << endl;
//}
//统计方式2
//map<string, int> countMap;
//for (const auto& str : arr)
//{
// pair <map<string,int>::iterator,bool> ret= countMap.insert(make_pair(str, 1));
// if (ret.second == false)
// {
// //插入失败,说明str已经在map中
// //ret.first为已经存在节点的迭代器,->second找到该节点第二个元素,也就是出现次数
// ret.first->second++;
// }
//}
//for (auto& e : countMap)
//{
// cout << e.first << "--" << e.second << endl;
//}
//统计方式3
map<string, int> countMap;
for (const auto& str : arr)
{
countMap[str]++;
}
for (auto& e : countMap)
{
cout << e.first << "--" << e.second << endl;
}
}
4.3. 对统计的次数进行排序
//排序1
//利用vector传入map的迭代器进行排序
struct MapItCompare
{
bool operator()(map<string, int>::iterator x, map<string, int>::iterator y)const
{
return x->second < y->second;//排升序
}
};
vector<map<string, int>::iterator>v;
map<string, int>::iterator countMapIt = countMap.begin();
while (countMapIt != countMap.end())
{
v.push_back(countMapIt);
++countMapIt;
}
sort(v.begin(), v.end(), MapItCompare());
for (auto& e : v)
{
cout << e->first << "--" << e->second << endl;
}
//排序2
//利用map排序,需要进行拷贝
map<int, string> sortMap;
for (auto e : countMap)
{
sortMap.insert(make_pair(e.second, e.first));
}
for (auto& e : sortMap)
{
cout << e.first << "--" << e.second << endl;
}
//排序3
//利用set排序
set<map<string, int>::iterator, MapItCompare>sortSet;
countMapIt = countMap.begin();
while (countMapIt != countMap.end())
{
sortSet.insert(countMapIt);
++countMapIt;
}
for (auto& e : sortSet)
{
cout << e->first << "--" << e->second << endl;
}
//排序4
//运用优先级队列,降序
priority_queue< map<string, int>::iterator, vector< map<string, int>::iterator>, MapItCompare> pq;
countMapIt = countMap.begin();
while (countMapIt != countMap.end())
{
pq.push(countMapIt);
++countMapIt;
}
while (!pq.empty())
{
auto e = pq.top();
cout << e->first << "--" << e->second << endl;
pq.pop();
}
五、multimap
与set和multiset类似,multimap允许键值冗余,即multimap容器当中存储的元素是可以重复的。
并且由于multimap容器允许键值冗余,调用[ ]运算符重载函数时,应该返回键值为key的哪一个元素的value的引用存在歧义,因此在multimap容器当中没有实现[ ]运算符重载函数。
成员函数find | 功能 |
---|---|
map对象 | 返回值为key的元素的迭代器 |
multimap对象 | 返回底层搜索树中序的第一个值为key的元素的迭代器 |
成员函数count | 功能 |
---|---|
map对象 | 值为key的元素存在则返回1,不存在则返回0 |
multimap对象 | 返回值为key的元素个数 |
六、用红黑树模拟实现map和set
6.1. 参数的控制
set是K模型的容器,而map是KV模型的容器,而红黑树可以使K结构也可以是KV结构,这样只需要用两种结构分别实现就可以了。
但是这样就造成了代码冗余,为了解决这个问题。就需要控制map和set传入底层红黑树的模板参数,在STL库中,第一个参数为key,但是第二个并不是value,而是参数T,这个参数可以是键值key,也可以是键值对key-value。
template<class K, class T>
class RBTree
这样,如果是set容器,T只需要传和键值一样的参数K即可,而map传入键值对即可:
template<class K>
class set
{
public:
//...
private:
RBTree<K, K> _t;
};
template<class K, class V>
class map
{
public:
//...
private:
RBTree<K, pair<K, V>> _t;
};
这样做的好处就是红黑树节点只需要一个模板参数就可以同时实现set和map。
//红黑树结点的定义
template<class T>
struct RBTreeNode
{
//三叉链
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
//存储的数据
T _data;
//结点的颜色
int _col; //红/黑
//构造函数
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
6.2. 使用仿函数比较参数大小
在插入、删除和查找时需要比较传入的键值和当前节点的键值,但是在map中,当前节点的键值T为键值对,无法直接进行比较,此时就需要传入仿函数,从<Key, Value>键值对当中取出键值Key后再进行比较。
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
具体的仿函数介绍可以查看这篇文章C++ 仿函数。
在这里,直接将仿函数定义为内部类即可:
template<class K, class V>
class map
{
//仿函数
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key
{
return kv.first;
}
};
public:
//...
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
template<class K>
class set
{
//仿函数
struct SetKeyOfT
{
const K& operator()(const K& key) //返回键值Key
{
return key;
}
};
public:
//...
private:
RBTree<K, K, SetKeyOfT> _t;
};
当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较。
6.3. 迭代器的实现
和链表的迭代器一样,红黑树的正向迭代器实际上就是对结点指针进行了封装。
正向迭代器的++操作和–操作比较复杂,具体规则如下:
前置++:
- 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点。
- 如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。
前置–:
- 如果当前结点的左子树不为空,则–操作后应该找到其左子树当中的最右结点。
- 如果当前结点的左子树为空,则–操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。
反向迭代器只需要复用正向迭代器即可。
//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
typedef Ref reference; //结点指针的引用
typedef Ptr pointer; //结点指针
typedef RBTreeNode<T> Node; //结点的类型
typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型
Node* _node; //正向迭代器所封装结点的指针
//构造函数
__TreeIterator(Node* node)
:_node(node) //根据所给结点指针构造一个正向迭代器
{}
Ref operator*()
{
return _node->_data; //返回结点数据的引用
}
Ptr operator->()
{
return &_node->_data; //返回结点数据的指针
}
//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{
return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{
return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//前置++
Self operator++()
{
if (_node->_right) //结点的右子树不为空
{
//寻找该结点右子树当中的最左结点
Node* left = _node->_right;
while (left->_left)
{
left = left->_left;
}
_node = left; //++后变为该结点
}
else //结点的右子树为空
{
//寻找孩子不在父亲右的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent; //++后变为该结点
}
return *this;
}
//前置--
Self operator--()
{
if (_node->_left) //结点的左子树不为空
{
//寻找该结点左子树当中的最右结点
Node* right = _node->_left;
while (right->_right)
{
right = right->_right;
}
_node = right; //--后变为该结点
}
else //结点的左子树为空
{
//寻找孩子不在父亲左的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent; //--后变为该结点
}
return *this;
}
};
//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator
{
typedef ReverseIterator<Iterator> Self; //反向迭代器的类型
typedef typename Iterator::reference Ref; //结点指针的引用
typedef typename Iterator::pointer Ptr; //结点指针
Iterator _it; //反向迭代器所封装的正向迭代器
//构造函数
ReverseIterator(Iterator it)
:_it(it) //根据所给正向迭代器构造一个反向迭代器
{}
Ref operator*()
{
return *_it; //通过调用正向迭代器的operator*返回结点数据的引用
}
Ptr operator->()
{
return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针
}
//前置++
Self& operator++()
{
--_it; //调用正向迭代器的前置--
return *this;
}
//前置--
Self& operator--()
{
++_it; //调用正向迭代器的前置++
return *this;
}
bool operator!=(const Self& s) const
{
return _it != s._it; //调用正向迭代器的operator!=
}
bool operator==(const Self& s) const
{
return _it == s._it; //调用正向迭代器的operator==
}
};
附录
红黑树代码
//使用枚举来定义颜色
enum Color
{
RED,
BLACK,
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
//红黑树的颜色,通过枚举来实现
Color _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
typedef Ref reference; //结点指针的引用
typedef Ptr pointer; //结点指针
typedef RBTreeNode<T> Node; //结点的类型
typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型
Node* _node; //正向迭代器所封装结点的指针
//构造函数
__TreeIterator(Node* node)
:_node(node) //根据所给结点指针构造一个正向迭代器
{}
Ref operator*()
{
return _node->_data; //返回结点数据的引用
}
Ptr operator->()
{
return &_node->_data; //返回结点数据的指针
}
//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{
return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{
return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//前置++
Self operator++()
{
if (_node->_right) //结点的右子树不为空
{
//寻找该结点右子树当中的最左结点
Node* left = _node->_right;
while (left->_left)
{
left = left->_left;
}
_node = left; //++后变为该结点
}
else //结点的右子树为空
{
//寻找孩子不在父亲右的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent; //++后变为该结点
}
return *this;
}
//前置--
Self operator--()
{
if (_node->_left) //结点的左子树不为空
{
//寻找该结点左子树当中的最右结点
Node* right = _node->_left;
while (right->_right)
{
right = right->_right;
}
_node = right; //--后变为该结点
}
else //结点的左子树为空
{
//寻找孩子不在父亲左的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent; //--后变为该结点
}
return *this;
}
};
//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator
{
typedef ReverseIterator<Iterator> Self; //反向迭代器的类型
typedef typename Iterator::reference Ref; //结点指针的引用
typedef typename Iterator::pointer Ptr; //结点指针
Iterator _it; //反向迭代器所封装的正向迭代器
//构造函数
ReverseIterator(Iterator it)
:_it(it) //根据所给正向迭代器构造一个反向迭代器
{}
Ref operator*()
{
return *_it; //通过调用正向迭代器的operator*返回结点数据的引用
}
Ptr operator->()
{
return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针
}
//前置++
Self& operator++()
{
--_it; //调用正向迭代器的前置--
return *this;
}
//前置--
Self& operator--()
{
++_it; //调用正向迭代器的前置++
return *this;
}
bool operator!=(const Self& s) const
{
return _it != s._it; //调用正向迭代器的operator!=
}
bool operator==(const Self& s) const
{
return _it == s._it; //调用正向迭代器的operator==
}
};
template<class K, class T,class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器
typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器
reverse_iterator rbegin()
{
//寻找最右结点
Node* right = _root;
while (right && right->_right)
{
right = right->_right;
}
//返回最右结点的反向迭代器
return reverse_iterator(iterator(right));
}
reverse_iterator rend()
{
//返回由nullptr构造得到的反向迭代器(不严谨)
return reverse_iterator(iterator(nullptr));
}
iterator begin()
{
//寻找最左结点
Node* left = _root;
while (left && left->_left)
{
left = left->_left;
}
//返回最左结点的正向迭代器
return iterator(left);
}
iterator end()
{
//返回由nullptr构造得到的正向迭代器(不严谨)
return iterator(nullptr);
}
RBTree()
:_root(nullptr)
{}
void RotateL(Node* parent)//左旋
{
Node* cur = parent->_right;//右变高,不可能为空
Node* curL = cur->_left;
Node* pparent = parent->_parent;
//cur左子树作为parent右子树
parent->_right = curL;
if (curL)
curL->_parent = parent;
//parent作为cur左子树
cur->_left = parent;
parent->_parent = cur;
//cur链接到pprent上
if (pparent == nullptr)//根
{
_root = cur;
cur->_parent = nullptr;
}
else//不为根
{
cur->_parent = pparent;
//判断链接在哪一侧
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
}
}
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;//cur的右子树
Node* pparent = parent->_parent;//保存parent的父亲节点
//将cur右子树链接到parent的左侧
parent->_left = curR;
if (curR)
curR->_parent = parent;
//将parent连接到cur的右侧
cur->_right = parent;
parent->_parent = cur;
//将cur与pparent链接起来
if (pparent == nullptr)//cur变成新的根
{
_root = cur;
cur->_parent = nullptr;
}
else//pparent不为根
{
cur->_parent = pparent;
if (parent == pparent->_left)//在上一级节点的左侧
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
}
}
//左右双旋
void RotateLR(Node* parent)
{
RotateL(parent->_left);
RotateR(parent);
}
//右左双旋
void RotateRL(Node* parent)
{
RotateR(parent->_right);
RotateL(parent);
}
pair<iterator,bool> Insert(const T& data)
{
//红黑树的插入与平衡二叉树相同,不过在插入完成以后需要对颜色进行调整
if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(_root, true);
}
KeyOfT kot;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(iterator(cur), false); //插入失败
}
}
//此时已经找到插入的位置了,判断插入在parent的左边还是右边
cur = new Node(data); //根据所给值构造一个结点
//因为对颜色进行调整会改变cur的指向,记录cur的指向,用于最后的返回
Node* newnode = cur;
if (kot(parent->_data)< kot(data))//插在右边
{
parent->_right = cur;
cur->_parent = parent;
}
else//插在左边
{
parent->_left = cur;
cur->_parent = parent;
}
//对红黑树的颜色进行调整,来满足红黑树的规则
//1.parent颜色是黑色,不需要调整,插入完成
//2.parent颜色是红色,违反了规则4,需要进行调整
while (parent && parent->_col == RED)
{
//parent是红色,则cur的祖父结点一定存在
Node* grandfather = parent->_parent;
//根据parent和grandfather的左右关系,分别进行调整
if (parent == grandfather->_left)
{
//找到叔叔节点,判断三种情况
Node* uncle = grandfather->_right;
//情况1,uncle存在且为红
if (uncle && uncle->_col == RED)
{
//直接进行颜色调整即可
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上调整
cur = grandfather;
parent = cur->_parent;
}
//情况2和3,uncle不存在或者存在且为黑,进行旋转
else
{
//情况2,右单旋
if (cur == parent->_left)
{
RotateR(grandfather);
//颜色调整
grandfather->_col = RED;
parent->_col = BLACK;
}
//情况3,进行左右双旋
else//cur==parent->_right
{
RotateLR(grandfather);
//颜色调整
grandfather->_col = RED;
cur->_col = BLACK;
}
break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
}
}
else//parent==grandfather->_right
{
//找到叔叔节点,判断三种情况
Node* uncle = grandfather->_left;
//情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
//直接进行颜色调整即可
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
//情况2和3,uncle不存在或者存在且为黑,进行旋转
else
{
//情况2,左单旋
if (cur == parent->_right)
{
RotateL(grandfather); //左单旋
//颜色调整
grandfather->_col = RED;
parent->_col = BLACK;
}
//情况3,进行右左双旋
else //cur == parent->_left
{
RotateRL(grandfather); //右左双旋
//颜色调整
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
}
}
}
//把根的颜色变黑
_root->_col = BLACK;
return make_pair(iterator(newnode), true); //插入成功
}
//查找函数
iterator Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (key < kot(cur->_data)) //key值小于该结点的值
{
cur = cur->_left; //在该结点的左子树当中查找
}
else if (key > kot(cur->_data)) //key值大于该结点的值
{
cur = cur->_right; //在该结点的右子树当中查找
}
else //找到了目标结点
{
return iterator(cur); //返回该结点
}
}
return end(); //查找失败
}
//进行对节点的调整
void deleteFixUp(Node* replace, Node* parent) {
Node* brother = nullptr;
// 如果顶替结点是黑色结点,并且不是根结点。
while (replace == nullptr || replace->_col == BLACK) {
//左孩子位置的所有情况
if (parent->_left == replace)
{
brother = parent->_right;
// case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况
if (brother->_col == RED)
{
brother->_col = BLACK;
parent->_col = RED;
RotateL(parent);
brother = parent->_right;
}
// 经过上面,不管进没进if,兄弟都成了黑色
// case2 黑兄,且兄弟的两个孩子都为黑
if ((brother->_left == nullptr || brother->_left->_col == BLACK) && (brother->_right == nullptr || brother->_right->_col== BLACK))
{
// 如果parent此时为红,则把brother的黑色转移到parent上,调整结束
if (parent->_col == RED)
{
parent->_col = BLACK;
brother->_col = RED;
break;
}
// 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整
else
{
brother->_col = RED;
replace = parent;
parent = replace->_parent;
}
}
else
{
// case3 黑兄,兄弟的左孩子为红色
if (brother->_left != nullptr && brother->_left->_col == RED) {
brother->_left->_col = parent->_col;
parent->_col = BLACK;
RotateR(brother);
RotateL(parent);
// case4 黑兄,兄弟的右孩子为红色
}
else if (brother->_right != nullptr && brother->_right->_col == RED) {
brother->_col = parent->_col;
parent->_col = BLACK;
brother->_right->_col = BLACK;
RotateL(parent);
}
break;
}
}
//对称位置的情况,把旋转方向反回来
else
{
brother = parent->_left;
// case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况
if (brother->_col == RED) {
brother->_col = BLACK;
parent->_col = RED;
RotateR(parent);
brother = parent->_left;
}
// 经过上面,不管进没进if,兄弟都成了黑色
// case2 黑兄,且兄弟的两个孩子都为黑
if ((brother->_left == nullptr || brother->_left->_col == BLACK)
&& (brother->_right == nullptr || brother->_right->_col == BLACK)) {
// 如果parent此时为红,则把brother的黑色转移到parent上
if (parent->_col == RED)
{
parent->_col = BLACK;
brother->_col = RED;
break;
}
// 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整
else
{
brother->_col = RED;
replace = parent;
parent = replace->_parent;
}
}
else
{
// case3 黑兄,兄弟的左孩子为红色,右孩子随意
if (brother->_right != nullptr && brother->_right->_col == RED)
{
brother->_right->_col = parent->_col;
parent->_col = BLACK;
RotateL(brother);
RotateR(parent);
}
// case4 黑兄,兄弟的右孩子为红色,左孩子随意
else if (brother->_left != nullptr && brother->_left->_col == RED) {
brother->_col = parent->_col;
parent->_col = BLACK;
brother->_left->_col = BLACK;
RotateR(parent);
}
//调整结束
break;
}
}
}
}
//删除
bool Erase(const K& key)
{
KeyOfT kot;
//用于遍历二叉树
Node* parent = nullptr;
Node* cur = _root;
//用于标记实际的待删除结点及其父结点
Node* delParentPos = nullptr;
Node* delPos = nullptr;
while (cur)
{
if (key < kot(cur->_data)) //所给key值小于当前结点的key值
{
//往该结点的左子树走
parent = cur;
cur = cur->_left;
}
else if (key > kot(cur->_data)) //所给key值大于当前结点的key值
{
//往该结点的右子树走
parent = cur;
cur = cur->_right;
}
else //找到了待删除结点
{
if (cur->_left == nullptr) //待删除结点的左子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_right; //让根结点的右子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
//待删除的结点只有一个孩子
if (cur->_right)//cur的右不为空,cur是黑色节点
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
parent->_left->_col = BLACK;
}
if (cur == parent->_right)
{
parent->_right = cur->_right;
parent->_right->_col = BLACK;
}
return true;
}
//cur为叶子节点
else
{
delParentPos = parent; //标记实际删除结点的父结点
delPos = cur; //标记实际删除的结点
}
}
break; //进行红黑树的调整以及结点的实际删除
}
else if (cur->_right == nullptr) //待删除结点的右子树为空
{
if (cur == _root) //待删除结点是根结点
{
_root = _root->_left; //让根结点的左子树作为新的根结点
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK; //根结点为黑色
}
delete cur; //删除原根结点
return true;
}
else
{
//待删除的结点只有一个孩子
if (cur->_left)//cur的左不为空,cur是黑色节点
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
parent->_left->_col = BLACK;
}
if (cur == parent->_right)
{
parent->_right = cur->_left;
parent->_right->_col = BLACK;
}
return true;
}
//cur为叶子节点
else
{
delParentPos = parent; //标记实际删除结点的父结点
delPos = cur; //标记实际删除的结点
}
}
break; //进行红黑树的调整以及结点的实际删除
}
else //待删除结点的左右子树均不为空
{
//替换法删除
//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
cur->_data = minRight->_data; //将待删除结点的_data改为minRight的_data
delParentPos = minParent; //标记实际删除结点的父结点
delPos = minRight; //标记实际删除的结点
break; //进行红黑树的调整以及结点的实际删除
}
}
}
//如果cur走到了空,则说明没找到
if (cur == nullptr) return false;
//记录待删除结点及其父结点(用于后续实际删除)
Node* del = delPos;
Node* delP = delParentPos;
//如果待删除结点为红色,则不会进行调整
if (delPos->_col == BLACK)
deleteFixUp(delPos, delParentPos);
//进行实际删除
if (del == delP->_left) //实际删除结点是其父结点的左孩子
{
delP->_left = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
else //实际删除结点是其父结点的右孩子
{
delP->_right = del->_left;
if (del->_left)
del->_left->_parent = delP;
}
delete del; //实际删除结点
return true;
}
void Destory(Node* root)
{
if (root == nullptr) return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
~RBTree()
{
Destory(_root);
}
//判断每条路径是否平衡
bool _CheckBalance(Node* root, int count, int BlackCount)
{
if (root == nullptr) //该路径已经走完了
{
if (count != BlackCount)
{
cout << "error:黑色结点的数目不相等" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "error:存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
{
count++;
}
return _CheckBalance(root->_left, count, BlackCount) && _CheckBalance(root->_right, count, BlackCount);
}
//判断是否为红黑树
bool CheckBalance()
{
if (_root == nullptr)
{
return true;
}
if (_root->_col == RED)
{
cout << "error:根结点为红色" << endl;
return false;
}
//找最左路径作为黑色结点数目的参考值
Node* left = _root;
int BlackCount = 0;
while (left)
{
if (left->_col == BLACK)
BlackCount++;
left = left->_left;
}
int count = 0;
return _CheckBalance(_root, count, BlackCount);
}
//中序遍历子函数
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":"<<root->_kv.second<<endl;
_Inorder(root->_right);
}
//中序遍历
void Inorder()
{
_Inorder(_root);
}
private:
Node* _root;
};
map代码
#include"RBTree.h"
namespace hjl //防止命名冲突
{
template<class K, class V>
class map
{
//仿函数
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator; //正向迭代器
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
//插入函数
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.Insert(kv);
}
//[]运算符重载函数
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
iterator it = ret.first;
return it->second;
}
//删除函数
void erase(const K& key)
{
_t.Erase(key);
}
//查找函数
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
}
set代码
#include"RBTree.h"
namespace hjl //防止命名冲突
{
template<class K>
class set
{
//仿函数
struct SetKeyOfT
{
const K& operator()(const K& key) //返回键值Key
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; //正向迭代器
typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
//插入函数
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
//删除函数
void erase(const K& key)
{
_t.Erase(key);
}
//查找函数
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
}
参考资料:
C++拾趣——STL容器的插入、删除、遍历和查找操作性能对比(ubuntu g++)——遍历和查找
STL详解(十)—— set、map、multiset、multimap的介绍及使用
C++ 仿函数