1.关联式容器
不同与list,vector等序列式容器,关联式容器存的是<key,value>结构的键值对,它在检索时比序列式容器的效率更高。
2.键值对(pair)
用来表示具有一一对应关系的结构,该结构一般只包含key和value两个成员变量。key代表键值,value表示与key对应的信息。比如在英汉词典中,每个英文单词都对应有它的中文意思。
在STL中定义如下:
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)
{}
};
3.树形结构的关联式容器
STL中实现了两种结构不同的关联式容器,一个是哈希结构,另一个是树形结构。其中树形结构的有:set,multiset,map,multimap。它们的底层使用红黑树实现的,它们的元素默认以key值进行升序排序。
3.1 set
1.set是按一定次序存储元素的容器。
2.它只有一个key值,每个key唯一不能重复,它和multiset一样没有重载[]。
3.传对象作为key值时需要传入比较方法。
注意:
1.因为只有key,所以不需要传键值对(pair)。
2.可以利用set元素不能重复的特性来进行去重。
3.set默认用小于比较,所以默认是升序。
4.它的查找效率为O(logn)。
5.它的key值不能被修改。因为它的底层是红黑树,随意修改会破坏它的结构。硬要修改可以先删除,再插入。
3.2 map
1.map的插入必须是键值对(pair),而pair是一个类,与一般的插入不一样,因为隐式类型的转换只支持单参数,而pair有两个成员变量。我们可以用以下方法来进行插入
其中直接用{n1,n2}是C++11的新特性,它是支持多参数的构造函数隐式类型转换。 make_pair是一个函数,在函数里面return 了一个实例化好的pair,一般还是建议使用make_pair比较好。
2.map中key的值不可以修改,而value可以修改。
3.map重载了[]操作符,可以用[key]的方式直接访问或者操作与key对应的value。
4.map默认也是小于比较,所以默认是升序,可以传入自定义的比较器。
3.3multiset
不是很常用,与set最大的区别就是它可以有多个值相同的key。但是key依旧不可以修改。multiset在底层是用的<value,value>键值对。
3.4multimap
它也是key可以有多个重复的,且key值依旧不能被修改。因为它的key相同但是value不同,导致同一个key值有不同value,所以它不支持[]访问和操作。
4.map和set的底层
在stl中,map和set的底层是由红黑树实现的,但是它们的封装比较的复杂。
传入值:
部分底层代码:
首先我们可以看到 set其实是有value的,但是它的value的值也是K。map在传给红黑树的数据是一个<K,pair<const K,T(V)>>,set之所以是<K,K>是为了兼容。
然后在rb_tree中treenode的类型模板是Value,也就是set就是K,map就是pair<const K,V>。
最后在treenode中可以看到,我们传入的Value类型实例化的变量名是 value_field。也就是它既可以是一个值也可以是一个对象。
KeyOfValue:
因为set和map共用同一个红黑树,所以它们需要经过封装才能达到共用一个红黑树。比如这个在rb_tree中有一个类型模板KeyOfValue,顾名思义就是将K转换成能比较的值。因为set传入的K可以直接比较,但map传入的K是一个pair,pair不能直接比较,要通过pair的first进行比较。所以底层实现的思路就是set的和map各实现一个仿函数类,然后传入到rb_tree中进行K的转换。
iterator重写的++和--
关于map和set的封装还有一个难点就是重写++和--。
重写++(后置)
首先是begin和end,begin返回的是最左侧的结点,而end由于我们的rbtree没有设置头结点,以end返回的是一个nullptr。当迭代器++时,核心因为是中序遍历,也就是左 根 右,同时要分两种情况。
代码实现:
Self& operator++()
{
if (_node->_right)//右子树不为空时,要找到右子树的最左结点
{
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
_node = subLeft;
}
else//右子树为空时,就要开始往前回溯,直到找到左孩子是父亲的祖先
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
重写--(后置)
随着每次--,迭代器移动的顺序是 右 根 左。
它也是两种情况,一是当左子树不为空时,找它的最左结点。二是当左子树为空时,开始回溯
代码实现:
Self& operator--()
{
if (_node->_left)
{
Node* subRight = _node->_left;//左子树不为空,找到左子树的最右结点
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
else
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && parent->_left == cur)//往上回溯,直到parent为空或者parent的右孩子是cur
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
插入数据
Map的插入
我们知道,map插入的数据是一个pair,所以在map中的插入如下:
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
由于之前对迭代器的声明就给K加了const了,所以很容易的做到map中Key不可修改而Value可以修改。
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::const_iterator const_iterator;
Set的插入
而Set只有一个值,并且它的迭代器其实都是const。
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
Set中的插入是这样的
pair<iterator, bool> Insert(const K& key)
{
pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
上面的插入代码特别注意 ,函数内第一行的iterator ret是树中的普通迭代器,它用来接收树插入的返回值,返回值的pair中的迭代器是一个const迭代器。那么直接返回会导致返回类型不匹配而编译报错,那我们看看STL中的源码怎么处理。
以上可以看出,当我们实例化的是一个const迭代器时,它直接把自己typedef成const_iterator,当我们用普通迭代器构造时,红框中的函数就是一个构造函数,它可以传一个普通迭代器来进行构造。当实例化的是一个普通迭代器时,这个函数就是一个拷贝构造。毕竟拷贝构造其实也是构造函数的重载。(这里有点绕)
typename(补充)
typename的作用是为了告诉编译这是一个类里面的内嵌类型,而不是变量名或者函数名
按需编译(补充)
当我们用泛型编程的时候,编译器对没有调用的函数不会进行编译,当这个函数有被调用才会进行编译,这就导致了这个函数如果没有被调用的话,里面就算有语法错误,编译器也可能不会检查出来。
Iterator
先看看部分代码:
template <class T,class Ptr,class Ref>
struct _TreeIterator
{
typedef RBTreeNode<T> Node;
typedef _TreeIterator<T,Ptr,Ref> Self;
typedef _TreeIterator<T, T*, T&> Iterator;
Node* _node;
_TreeIterator(const Iterator& it)
:_node(it._node)
{}
_TreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
首先它有三个模板参数,我们可以再看看在树中它是如何传入模板参数的。
template <class K, class T,class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
// 同一个类模板,传的不同的参数实例化出的不同类型
typedef _TreeIterator<T,T*,T&> iterator;
typedef _TreeIterator<T, const T*, const T&> const_iterator;
可以看出它是通过对T(也就是Value)加const来声明区分普通迭代器和const迭代器。(这里树的模板参数是简化的,除了这些还有两个)
红黑树对map和set的封装模拟实现代码
namespace hzj
{
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 T,class Ptr,class Ref>
struct _TreeIterator
{
typedef RBTreeNode<T> Node;
typedef _TreeIterator<T,Ptr,Ref> Self;
typedef _TreeIterator<T, T*, T&> Iterator;
Node* _node;
_TreeIterator(const Iterator& it)
:_node(it._node)
{}
_TreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
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->_left)
{
Node* subRight = _node->_left;//左子树不为空,找到左子树的最右结点
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
else
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && parent->_left == cur)//往上回溯,直到parent为空或者parent的右孩子是cur
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator++()
{
if (_node->_right)//右子树不为空时,要找到右子树的最左结点
{
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
_node = subLeft;
}
else//右子树为空时,就要开始往前回溯,直到找到左孩子是父亲的祖先
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
};
template <class K, class T,class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
// 同一个类模板,传的不同的参数实例化出的不同类型
typedef _TreeIterator<T,T*,T&> iterator;
typedef _TreeIterator<T, const T*, const T&> const_iterator;
public:
Node*& GetRoot()
{
return _root;
}
iterator begin()
{
Node* leftMin = _root;
while (leftMin&&leftMin->_left)
{
leftMin = leftMin->_left;
}
return iterator(leftMin);
}
iterator end()//因为这个红黑树没有设置头结点,所以end返回空
{
return iterator(nullptr);
}
const_iterator begin() const
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
return const_iterator(leftMin);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
Node* Find(const K& key)
{
Node* cur = _root;
KeyOfT kot;
while (cur)
{
if (key<kot(cur->_data) )
{
cur = cur->_left;
}
else if (key > kot(cur->_data))
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
pair<iterator,bool> Insert(const T& data)//data有可能是一个值,也可能是一个pair
{
//第一次插入
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root),true);
}
Node* parent = nullptr;
Node* cur = _root;
KeyOfT kot;
while (cur)//找到插入位置,记得记录parent
{
if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else
{
return make_pair(iterator(cur),false);
}
}
cur = new Node(data);
cur->_col = RED;//红黑树新插入的结点的颜色为红色
Node* newnode = cur;
//链接新节点
if (kot(cur->_data) < kot(parent->_data))
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//不能有连续的红节点,如果父亲是红结点,那么需要分情况调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)//parent在grandfather左边。
{
//那么此时u在grand的右边
Node* uncle = grandfather->_right;
//情况一:u存在且u为红色,那么要将u改黑,并且修改cur和parent继续调整。
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;//
cur = grandfather;
parent = cur->_parent;
}
else//情况二:u不存在或者u为黑
{
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(iterator(newnode),true);
}
bool IsBalance()
{
return IsBalance(_root);
}
bool IsBalance(Node* root)
{
KeyOfT kot;
if (root == nullptr) return true;
if (root->_col == RED)
{
cout << kot(root->_data) << "头部颜色错误" << endl;
return false;
}
int benchmark = 0;
Node* cur = root;
while (cur)
{
if (cur->_col == BLACK) benchmark++;
cur = cur->_left;
}
return CheckColour(root, 0, benchmark);
}
int Height()
{
return Height(_root);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool CheckColour(Node* root, int blacknum, int benchmark)
{
if (root == nullptr)
{
if (blacknum != benchmark)
{
cout << "路径长度错误" << endl;
return false;
}
return true;
}
if (root->_col == BLACK) blacknum++;
if (root->_col == RED && root->_parent->_col == RED)
{
KeyOfT kot;
cout << kot(root->_data) << "出现了连续的红色结点" << endl;
return false;
}
return CheckColour(root->_left, blacknum, benchmark) && CheckColour(root->_right, blacknum, benchmark);
}
private:
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = curleft;
if (curleft)
{
curleft->_parent = parent;
}
cur->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright)
curright->_parent = parent;
Node* ppnode = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
Node* _root = nullptr;
};
}
总结
另外红黑树中也有一个Compare比较器,默认是Less小于比较器。
总之STL中是通过各种复杂的操作来实现Set和Map的封装的,为了解决兼容性问题,往往都是解决了一个问题可能又会诞生新的问题,生活也是如此,问题会伴随我们一生,以平常心直面问题才能走得更远。