源码下载
一. STL源码中的封装逻辑
1)map

2)set
根据我们对map和set的了解,map是key_value结构,这和我们看到的底层一样。
但是怎么set底层也是key_value,不应该只有key么?
这就要看看封装set的红黑树的结构了。

3)rb_tree
-
红黑树的第二个模板参数value传什么,红黑树的节点中存的就是什么类型的数据。这样通过模板参数写成泛型就可以让map、set复用同一棵红黑树。set实例化rb_tree时第二个模板参数给的是key,map实例化rb_tree时第二个模板参数给的是pair<const key,T>,这样一棵红黑树既可以实现key搜索场景的set,也可以实现key_value搜索场景的map。
-
rb_tree第二个模板参数Value已经控制了红黑树结点中存储的数据类型,为什么还要传第一个模板参数Key呢?尤其是set,两个模板参数是一样的。
① 对于map和set,find/erase时的函数参数都是Key,所以第一个模板参数是传给find/erase等函数做形参的类型的。
② 对于set而言两个参数(key_type和value_type)是一样的(都是key);但是对于map而言就完全不一样了(第一个是key,第二个是pair<const key,T>),map insert的是pair对象,但是find和erase的是Key对象。

4)实例化map&&set的传参过程

二. 模拟实现map&&set
1)实现泛型红黑树结构&&封装map和set框架&&KeyOfT
之前我们实现的是key_value的红黑树,现在把之前的代码改造一下,变成泛型的,能同时被map和set复用。
cpp
// 结点里存什么类型的数据由传过来的模板参数T决定
template <class T>
struct RBtreeNode
{
T _data;
RBtreeNode<T>* _left;
RBtreeNode<T>* _right;
RBtreeNode<T>* _parent;
Color _color;
RBtreeNode(const T& data)
:_data(data)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _color(RED)
{}
};
2)1. 结点数据的类型变成泛型,我们的各种比较逻辑也要变了。之前我们是取出pair的first(也就是key)来比较,但是现在_data不一定是pair不能随便取first。

-
pair对象本身也是可以比较的,但是他比较的逻辑是,先拿first比,first比完还会再拿second比。这不符合我们期望的比较逻辑,对于map我们只希望他的first参与比较。


-
那么如何处理能让比较的逻辑同时适合key和key_value?
① 说到控制比较逻辑,很容易想到仿函数。给rbtree加一个模板参数,将map和set各自的仿函数(map和set各自知道自己的数据是key还是key_value,也就可以控制比较逻辑,该不该取first)通过模板参数传给rbtree在仿函数里控制比较逻辑,但是这种方法是行不通的。因为下图中的两种场景,一个是插入时的比较逻辑,一个是查找时的比较逻辑,_data要比较的类型不是只有T(key或pair),还有可能直接是K。连仿函数的参数都确定不了。


② 实际库里面的做法还是用到了仿函数,并通过增加一个模板参数,接受map和set各自的仿函数。只不过这个仿函数的作用不是直接控制比较逻辑,而是**从rbtree的第二个模板参数中取出key。**我们不就是希望只有key参与比较么,不管是什么类型,确保仿函数能取出其中的key部分就好。

2)迭代器
普通迭代器
-
因为结点的物理空间也是不连续的,不能用原生指针直接充当迭代器,所以红黑树迭代器的实现思路和框架和list很相似:用一个类封装节点的指针,再通过重载运算符实现像指针一样访问的行为。
-
迭代器实现的难点是operator++和operator--的实现,map和set迭代器走的是中序遍历的顺序。
++的逻辑规则:
① 迭代器++时,如果it指向的结点的右子树不为空 ,代表当前结点已经访问完了,要访问下一个结点是右子树的中序第一个,一棵树中序第一个是最左结点,所以直接找右子树的最左结点即可。

② 迭代器++时,如果it指向的结点的右子树为空 ,代表当前结点已经访问完了且当前结点所在的子树也访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上找。
那么是哪个祖先呢? cur == parent->_left 时的parent结点,因为孩子如果是父亲的右,孩子完了父亲也就完了。只有孩子是父亲的左,根据中序遍历的左根右,左完了才能轮到根。
不过也有可能一路找到了根节点也没有符合cur== parent->_left情况的祖先,此时parent为空,代表整棵树都已经遍历完了。

- begin() 返回中序的第一个节点,也就是上图中的10。
那么end() 返回最后一个节点的下一个位置应该如何表示呢?
如上图:当it指向50,++it 时,50是40的右,40是30的右,30是18的右,18到根了没有父亲,没有找到孩子是父亲左的那个祖先,这时父亲为空,那我们就把it中的结点指针置为nullptr,用nullptr去充当end。
sgi_stl源码中,红黑树增加了一个哨兵位头结点 做为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点 (在插入删除的过程中要实时维护)。
相比我们用nullptr作为end(),各有利弊,直接用空做end()需要在重载operator--时特殊处理一下,让迭代器结点指向最右结点。
- 迭代器--的实现 跟++的思路完全类似,只是逻辑正好反过来,因为他访问顺序是右子树->根结点->左子树。对end()位置--一定要注意要特殊处理。

cpp
self& operator--()
{
// 期望--end()能跳到最右节点
if (_node == nullptr)
{
Node* maxright = _root;
while (maxright && maxright->_right)
{
maxright = maxright->_right;
}
_node = maxright;
return *this;
}
// 左子树不为空,找左子树最右结点
else if (_node->_left)
{
_node = _node->_left;
while (_node->_right)
_node = _node->_right;
return *this;
}
// 左子树为空
else
{
Node* parent = _node->_parent;
if (parent && _node != parent->_right)
{
_node = parent;
parent = parent->_parent;
}
_node = parent;
return *this;
}
}
const迭代器
和链表部分思路一致,是普通还是const我们通过模板参数传,从而让编译器自己实例化,避免我么写两份几乎一样的冗余代码。
整体结构比较复杂,一层套一层,其实我感觉只有自己真正从头到尾写一遍才能真正捋清,不然真的很混乱。
map和set封装rbtree,rbtree结构中实例化普通迭代器和const迭代器,再下层普通迭代器和const迭代器都写在一个类模板中,通过传递模板参数区分。

3)key不支持修改
对应的结构中的其他位置不要忘记修改,加上const。


4)operator[] --> 依靠insert
- 回顾一下 operator[] 和 insert 的行为:

cpp
V& operator[](const K& key)
{
std::pair<iterator, bool> ret = _tree.Insert({ key, V() });
// 不管是否插入成功,此时我们想插入的数据一定已经保存在iterator中
// 先取出iterator
iterator it = ret.first;
// 再取出value并返回
return it->second;
}
三. 具体完整代码
1)rbtree.h
cpp
#pragma once
#include <iostream>
// 实现一个Key-value结构的红黑树
enum Color
{
RED,
BLACK
};
// 结点里存什么类型的数据由传过来的模板参数T决定
template <class T>
struct RBtreeNode
{
T _data;
RBtreeNode<T>* _left;
RBtreeNode<T>* _right;
RBtreeNode<T>* _parent;
Color _color;
RBtreeNode(const T& data)
:_data(data)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _color(RED)
{}
};
// 迭代器结构(普通 + const版)
template <class T, class Ref, class Ptr>
class RBtreeIterator
{
typedef RBtreeNode<T> Node;
typedef RBtreeIterator<T, Ref, Ptr> self;
private:
Node* _node;
Node* _root; // 为了end()--能拿到根
public:
RBtreeIterator(Node* node, Node* root)
:_node(node)
,_root(root)
{}
Ref operator*() // T& const T&
{
return _node->_data;
}
// ->有连续解引用的特性(编译器自动处理),要返回一个支持再次接引用的类型
Ptr operator->() // T* const T*
{
return &(_node->_data);
}
bool operator==(const self& s)
{
return _node == s._node;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
self& operator++()
{
// 右子树不为空,找右子树最左结点
if (_node->_right)
{
_node = _node->_right;
while (_node->_left)
_node = _node->_left;
return *this; // 不能直接返回_node,产生临时对象不能给引用
}
// 右子树为空
else
{
Node* parent = _node->_parent;
while (parent && _node != parent->_left)
{
_node = parent;
parent = parent->_parent;
}
_node = parent; // 不能直接返回parent,产生临时对象不能给引用
return *this;
}
}
//self& operator++(int)
//{
//
//}
self& operator--()
{
// 期望--end()能跳到最右节点
if (_node == nullptr)
{
Node* maxright = _root;
while (maxright && maxright->_right)
{
maxright = maxright->_right;
}
_node = maxright;
return *this;
}
// 左子树不为空,找左子树最右结点
else if (_node->_left)
{
_node = _node->_left;
while (_node->_right)
_node = _node->_right;
return *this;
}
// 左子树为空
else
{
Node* parent = _node->_parent;
if (parent && _node != parent->_right)
{
_node = parent;
parent = parent->_parent;
}
_node = parent;
return *this;
}
}
//self& operator--(int)
//{
//
//}
};
template <class K, class T, class KeyOfT>
class RBtree
{
typedef RBtreeNode<T> Node;
public:
typedef RBtreeIterator<T, T&, T*> Iterator;
typedef RBtreeIterator<T, const T&, const T*> ConstIterator;
private:
Node* _root = nullptr;
private:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
// 一定要记住每一个节点中都有一个_parent,不要忘记更新
parent->_left = subLR;
if (subLR) // h为0的情况
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
// 旋转完这个局部子树,要看看pparent是否为空
// 也就是原本的parent是不是整棵树的根,他还有没有父亲
if (pparent == nullptr)
{
// 直接把新根给_root
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* pparent = parent->_parent;
// 一定要记住每一个节点中都有一个_parent,不要忘记更新
parent->_right = subRL;
if (subRL) // h为0的情况
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
// 旋转完这个局部子树,要看看pparent是否为空
// 也就是原本的parent是不是整棵树的根,他还有没有父亲
if (pparent == nullptr)
{
// 直接把新根给_root
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == pparent->_left)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
KeyOfT kot;
_InOrder(root->_left);
std::cout << kot(root->_data) << " ";
_InOrder(root->_right);
}
// 统计节点个数
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
public:
Iterator Begin()
{
// 中序第一个--最左节点
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return Iterator(cur, _root);
}
Iterator End()
{
return Iterator(nullptr,_root);
}
ConstIterator Begin() const
{
// 中序第一个--最左节点
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return ConstIterator(cur, _root);
}
ConstIterator End() const
{
return ConstIterator(nullptr,_root);
}
std::pair<Iterator, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return { Iterator(_root, _root), true};
}
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
// 找空位
while (cur)
{
if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else return { Iterator(cur, _root), false };
}
cur = new Node(data);
Node* newnode = cur; // 记录插入位置,留着返回
cur->_color = RED;
// 先直接插入
if (kot(parent->_data) > kot(data))
{
parent->_left = cur;
cur->_parent = parent;
}
else if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
cur->_parent = parent;
}
// parent第一次确实是一定存在的,但是我们这是一个循环向上处理的逻辑
// 当处理到根节点时,根节点没有父亲
// 如果父亲存在且为红,根据叔叔决定处理方式,并向上处理
while (parent && parent->_color == RED)
{
// 通过爷爷找叔叔
Node* grandfather = parent->_parent;
// 叔叔在右
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// 如果叔叔存在且为红,直接变色并向上处理
if (uncle && uncle->_color == RED)
{
// 变色
uncle->_color = parent->_color = BLACK;
grandfather->_color = RED;
// 向上移动更新
cur = grandfather; // !!颜色改变可能影响上级的是爷爷,成为新的cur
parent = cur->_parent;
}
// 叔叔不存在 或者 存在且为黑,变色+旋转(单旋?双旋?)
else
{
// 变色+右旋
//if (parent == grandfather->_left && cur == parent->_left) // uncle在右,parent一定在左
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
// 变色+左右双旋
if (cur == parent->_right)
{
RotateL(parent);
RotateR(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break; // 不再继续向上更新
}
}
// 叔叔在左
else
{
Node* uncle = grandfather->_left;
// 叔叔存在且为红
if (uncle && uncle->_color == RED)
{
// 变色
uncle->_color = parent->_color = BLACK;
grandfather->_color = RED;
// 向上移动更新
cur = grandfather; // !!颜色改变可能影响上级的是爷爷,成为新的cur
parent = cur->_parent;
}
// 叔叔不存在 或者 存在且为黑,变色+旋转(单旋?双旋?)
else
{
// 变色+左旋
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
// 变色+左右双旋
if (cur == parent->_left)
{
RotateR(parent);
RotateL(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
}
// 如果没处理到根根还是黑的,但是可能处理根时将根变为红,需要变回来
// 简单处理,无论是否处理到根,最后都将根置黑一次
_root->_color = BLACK;
// 如果父亲存在且为黑,直接插入结束,return true;(不止这一种情况return true;)
return {Iterator(newnode, _root), true};
}
Iterator Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (kot(cur->_data) < key) cur = cur->_right;
else if (kot(cur->_data) > key) cur = cur->_left;
else return Iterator(cur, _root);
}
return End();
}
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
int Size()
{
return _Size(_root);
}
};
2)mymap.h
cpp
#pragma once
#include "rb_tree.h"
namespace laosi
{
template <class K, class V>
class map
{
struct mapKeyOfT
{
K operator()(const std::pair<K, V>& kv)
{
return kv.first;
}
};
private:
RBtree<K, std::pair<const K, V>, mapKeyOfT> _tree;
public:
typedef typename RBtree<K, std::pair<const K, V>, mapKeyOfT>::Iterator iterator;
typedef typename RBtree<K, std::pair<const K, V>, mapKeyOfT>::ConstIterator const_iterator;
iterator begin()
{
return _tree.Begin();
}
iterator end()
{
return _tree.End();
}
const_iterator begin() const
{
return _tree.Begin();
}
const_iterator end() const
{
return _tree.End();
}
std::pair<iterator, bool> insert(const std::pair<const K, V>& kv)
{
return _tree.Insert(kv);
}
iterator find(const K& key)
{
return _tree.Find(key);
}
V& operator[](const K& key)
{
std::pair<iterator, bool> ret = _tree.Insert({ key, V() });
// 不管是否插入成功,此时我们想插入的数据一定已经保存在iterator中
// 先取出iterator
iterator it = ret.first;
// 再取出value并返回
return it->second;
}
};
void Print(const map<std::string, std::string>& m)
{
for (const auto& e : m)
{
std::cout << e.first << " : " << e.second << std::endl;
}
}
void testmap01()
{
map<std::string, std::string> m;
m.insert({ "sort", "排序"});
m.insert({ "left", "左边" });
m.insert({ "right", "右边" });
m.insert({ "string", "字符串" });
// 需要迭代器支持
for (auto& e : m)
{
// key值不支持修改
//e.first += 'm';
e.second += 'm';
std::cout << e.first << " : " << e.second << std::endl;
}
Print(m);
// find
map<std::string, std::string>::iterator it = m.find("string");
std::cout << it->first << " : " << it->second << std::endl;
}
void testmap02() // 统计次数 -- operator[]
{
std::string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果",
"苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
map<std::string, int> countmap;
for (const auto& str : arr)
{
countmap[str]++; // 这种写法更简洁
}
for (const auto& e : countmap)
std::cout << e.first << " : " << e.second << std::endl;
std::cout << std::endl;
}
}
3)myset.h
cpp
#pragma once
#include "rb_tree.h"
namespace laosi
{
template <class K>
class set
{
struct setKeyOfT
{
K operator()(const K& k)
{
return k;
}
};
private:
RBtree<K, const K, setKeyOfT> _tree;
public:
typedef typename RBtree<K, const K, setKeyOfT>::Iterator iterator;
typedef typename RBtree<K, const K, setKeyOfT>::ConstIterator const_iterator;
iterator begin()
{
return _tree.Begin();
}
iterator end()
{
return _tree.End();
}
const_iterator begin() const
{
return _tree.Begin();
}
const_iterator end() const
{
return _tree.End();
}
std::pair<iterator, bool> insert(const K& k)
{
return _tree.Insert(k);
}
iterator find(const K& key)
{
return _tree.Find(key);
}
};
void Print(const set<int>& s)
{
set<int>::const_iterator it = s.end();
while (it != s.begin())
{
//*it += 1;
--it;
std::cout << *it << " ";
}
std::cout << std::endl;
}
void testset01()
{
set<int> s;
s.insert(5);
s.insert(1);
s.insert(14);
s.insert(77);
s.insert(2);
s.insert(1);
s.insert(30);
set<int>::iterator it = s.begin();
while (it != s.end())
{
//std::cout << ++(*it) << " "; // key值不可修改
++it;
}
std::cout << std::endl;
Print(s);
// find
std::cout << *(s.find(77)) << std::endl;
//std::cout << *(s.find(100)) << std::endl; // 不能对空指针解引用
}
}
4)test.cpp
cpp
#include <iostream>
#include "mymap.h"
#include "myset.h"
int main()
{
//laosi::set<int> s;
//laosi::map<int, int> m;
laosi::testset01();
laosi::testmap01();
laosi::testmap02();
return 0;
}
