前言
我们知道map和set的底层都是用红黑树实现的,但是set和map的结构不一样,set只有一个参数K,而map有两个参数K/V,难道set和map是利用俩份红黑树来实现的吗?从设计者的角度是不可能这样复现的,这样实现的话两个容器有着大量的重复代码,只是参数的个数不同就要使用两份红黑树复现是完全不符合设计规则的,那么红黑树做了怎样的处理使得传入参数的不同来复现这两个容器呢?下面我们就一起来进行学习!!
一、红黑树的设计
1.1 红黑树存储节点的设计
既然map和set的参数不同,那么红黑树就要统一的对它们的参数进行处理,当为pair<K,V>类型时复现map,为K类型时复现set。我们先来看看stl库中是如何来进行设计的:
通过上图可以看到,map 传给红黑树的key_type是Key,value_type是pair<Key, T>;而 set 传给红黑树的key_type和value_type都是Key。红黑树节点中存储的数据就是value_type类型的数据。如果传给value_type的是pair<Key, T>,那么就会实例化出 map;而传给value_type的是Key,就会实例化出 set。
那么我们下面的问题就转化成了,如何去提取出value_type类型的数据?
在插入节点的时候我们是需要根据二叉搜索树的规则来调整的,set的Value为K直接提取出来对比即可,而map中的Vaule为pair<K, V>,我们只需提取出K来对比即可,stl库中对pair的排序规则不符合并我们的要求,所以我们得自己设计一个仿函数来返回对应的数据!!
红黑树节点存储设计如下:
#pragma once
enum Colour
{
RED,
BLACK,
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
// 仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
bool Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return 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 false;
}
}
// ......
return true;
}
private:
Node* _root = nullptr;
};
1.2 红黑树的迭代器
我们知道红黑树本质上也是一颗二叉搜索树,所以通过中序遍历能得到一个升序的序列。
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
__RBTreeIterator(Node* node)
: _node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
// 当节点的右子树存在时, 一直往下沿左走 走到尽头了 证明左子树走完了 该走右子树了 右子树也重复该步骤
if (_node->_right)
{
Node* subL = _node->_right;
while (subL->_left)
{
subL = subL->_left;
}
_node = subL;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
// 右子树走完了 我们应该返回到该右子树的父节点的祖先了
while (parent && parent->_right == cur)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
if (_node->_left)
{
Node* subR = _node->_left;
while (subR->_right)
{
subR = subR->_right;
}
_node = subR;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
};
template<class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, T&, T*> iterator;
public:
// begin()为树的最左节点
iterator begin()
{
// 注: 需要避免_root为nullptr
Node* left = _root;
while (left && left->_left)
{
left = left->_left;
}
return iterator(left);
}
iterator end()
{
return iterator(nullptr);
}
private:
Node* _root = nullptr;
}
1.3 map的设计
#pragma once
#include "RBTree.h"
namespace curry
{
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first; // 提取出kv的key值
}
};
public:
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
return ret.first->second;
}
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.Insert(kv);
}
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t; // 底层调用RBTree进行插入操作
};
};
1.4 set的设计
#pragma once
#include "RBTree.h"
namespace curry
{
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
};
通过上述map和set的设计我们可以发现,实际它们的迭代器与功能操作都是由红黑树提供的!!
1.5关于map与set的const_iterator设计
我们先来看看stl库中对它的设计:
由上图可知,实际上在stl库中map/set的普通迭代器都设计成了const迭代器,那么const迭代器是如何实现的呢?
它是利用普通迭代器构造出来的。self与iterator是不一样的,我们的普通迭代器是self设计出来的,而const迭代器需要普通迭代器的构造,那么我们就需要显式定义出普通迭代器的类型进行构造。