用同一棵红黑树实现map和set【STL】

简介: 用同一棵红黑树实现map和set【STL】

1. 红黑树模板参数控制

STL非常注重代码的复用,比如const迭代器、反向迭代器,各种运算符的重载。容器适配器等。都是使用类似的代码实现功能大体相同但具体细节有差异的容器。

由于红黑树是一种平衡二叉搜索树,它还能自动排序,又由于map和set所开放的各种接口红黑树也都提供了,所以几乎所有的操作都只是调用红黑树的操作。

我们知道,set容器存储的元素是<Key,Key>键值对,而map容器存储的元素是<Key,Value>键值对,想要使用同一棵红黑树实现map和set,就需要对红黑树的模板参数作出修改,再辅以仿函数,就能达到我们想要的效果。如何用KV模型的容器同时实现K模型的set和KV模型的map呢?

用上帝视角看待map、set和红黑树:map和set组织数据的方式是用红黑树的逻辑,但是它们的元素(数据)并没有对红黑树的逻辑造成影响,这也是模板的意义。

键值对的形式是<Key,Value>,可以认为set的Value和Key相等,map才是真正的键值对。

1.1 修改红黑树模板参数

在之前,红黑树的两个模板参数是这样的:

template<class K, class V>

它在被实现时也表示每个结点都储存一个键值对<K,V>元素,实际上这是为本节的模拟实现做铺垫,所以才设置了两个参数。由于map和set的键值对中都有一个Key,所以保留红黑树的第一个模板参数。对于第二个模板参数,为了不引起歧义,把它改成T。

  • 对于map:红黑树的T就是一个键值对,它被一个pair对象保存;
  • 对于set:红黑树的T还是Key,和红黑树的第一个模板参数相同,因为set的value和key相同。

把红黑树的第二个模板参数T用来根据上层选择容器的不同,保存不同的T。那么如何对不同的T进行操作呢?

1.2 红黑树结点存储的数据

对红黑树的结点类进行改造,新增一个类型为T的元素叫做_data,用它来保存上层容器传入的模板参数对应的数据。

修改结点类如下:

// 红黑树结点类
//template<class K, class V> [旧]
template<class T>
struct RBTreeNode
{
    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    Color _col;                         // 颜色
    T _data;                            // 数据
    RBTreeNode(const T& data)
            : _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _data(data)
            , _col(RED)                 // 默认插入结点为红色
    {}
};

通过图示理解map和set与红黑树模板参数之间的关系:

1.3 小结

  • 红黑树对于map和set而言,都相当于一个红黑树类模板,通过传入不同的模板参数,可以实例化出不同的红黑树以容器使用;
  • 红黑树的第二个参数T可以接收上层map和set传入的模板参数,T可以实例化出成员变量_data
  • 对于红黑树结点:
  • map:红黑树结点成员变量_data存的是真正的<Key,Value>键值对;
  • set:红黑树结点成员变量_data存的是Key
  • 模板的使用,本质上要做的事情一件都没少,让本应该让程序员做的事交给了编译器去做,

1.4 补充

上面的讨论都是针对红黑树的第二个模板参数T而言的,第一个参数可以去掉吗?

就红黑树结点的成员变量_data保存的数据而言,map是<Key,Value>键值对,set是Key,它们都有Key,看起来红黑树本身的第一个模板参数K是多余的,但实际上它有存在的意义。

  • 对于map而言,诸如find和erase这样的接口必须要使用红黑树的第一个模板参数,因为它们的参数只有Key;
  • 对于set而言,去掉也没关系。

为了让红黑树能更好地匹配两者(主要是这个麻烦鬼:map),它的第一个模板参数被保留,以备不时之需。

2. 模板参数中的仿函数

红黑树是二叉搜索树,插入的元素是需要比较的,但是诸如pair这样的对象又不能直接比较,红黑树也不知道是map还是set。结点储存的是上层传入的模板参数T,T有可能是键值对,也有可能是Key,红黑树是如何知道接收的参数是哪种呢?或者说红黑树如何取出结点的成员变量_data进而对它操作呢?

同样地,set还好说,直接比较T就行。但是这个map就有点麻烦了,因为我们插入的时候比较的对象是Key,所以还要取出map的<Key,Value>键值对中的Key,然后再比较。

可以通过仿函数的方法取出set和map里的key,具体操作:

分别在map和set类中定义一个结构体KeyOfT,表示取出T中的键值Key,然后再KeyOfT中实现一个operator(),使其具有类似函数的功能。

2.1 map中的仿函数

对于map而言,结构体MapKeyOfT仿函数的功能是取出键值对中的Key,仿函数的实现如下:

struct MapKeyOfT
{
    const K& operator()(const pair<K, V>& kv)
    {
        return kv.first; // 仿函数取出键值K并返回
    }
};

2.2 set中的仿函数

对于set而言,结构体SetKeyOfT仿函数的功能是直接返回Key,仿函数的实现如下:

struct SetKeyOfT
{
    const K& operator()(const K& key)
    {
        return key;         // 仿函数返回key
    }
};

2.3 增加红黑树模板参数

但是这样光增加map和set的仿函数还不行,还得让红黑树接收到上层传入的仿函数。原因是仿函数的作用就是取出下层红黑树结点的_data保存的Key(可能是Key或<Key,Value>键值对),然后返回给上层使用。

所以新增一个红黑树模板参数,叫做KeyOfT:

// 红黑树类
template<class K, class T, class KeyOfT>
class RBTree
{
    // ...
}

红黑树中KeyOfT的作用是接收上层map和set仿函数的返回值。

2.4 改造红黑树Key值的比较部分

由于使用了仿函数提取map和set中的Key,所以原本红黑树中的所有Key值比较的部分都要替换成仿函数,例如:

bool Insert(const T& kv)
{
    // ... 
    KeyOfT kot;
    if (kot(data) < kot(cur->_data))
    // ...
}

3. 正向迭代器

要知道,原生指针可以作为正向迭代器使用,所以红黑树的正向迭代器实际上就是封装后的指针,取名为iterator,是一个类(或结构体)。因此红黑树的正向迭代器中只有一个成员变量:结点指针。

3.1 正向迭代器的模板参数

迭代器是一个变量,可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点来看,它就可以用指针实现。根据接口的不同,它们的返回值可能是指针或引用类型,例如解引用*返回的是一个数据的引用,->返回的是一个数据的地址。所以还要新增两个模板参数:RefPtr,分别代表引用和指针类型的迭代器。

// 在RBTree.h中实现
template<class T, class Ptr, class Ref>
struct __RBTreeIterator
{
    typedef RBTreeNode<T> Node;
    Node* _node;
};

除此之外,像前置++这样的(被重载的)操作符,它的返回值就是结点本身,所以还需要增加另一个模板参数Self

typedef __RBTreeIterator<T, Ptr, Ref> Self; // 正向迭代器的类型

当修改了红黑树的模板参数以后,上层的map和set也要多传入一个模板参数,就是取出map和set中的key:KeyOfT

template<class K>
class set
{
    // ... 
private:
    RBTree<K, K, SetKeyOfT> _t;
};
template<class K, class V>
class map 
{
    // ...
private:
    RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

注:

  • 迭代器是一个类,所以使用一个结构体或类表示;
  • 关于命名:因为这个类包含了很多模板参数,等下红黑树也要使用它,所以这个迭代器类相当于一个“子类”,稍后还要在红黑树中typedef,让这一大串变得简单,就像我们使用iterator一样。

3.2 正向迭代器的成员函数

完善了迭代器的参数列表以后,就要补充迭代器的各种功能接口。

构造函数

是迭代器最重要的接口,只要传入一个结点的指针就能构造出一个迭代器对象:

// 迭代器构造函数
    __RBTreeIterator(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;
}

需要注意的是,==!=的参数是有参数的。

自增自减运算符重载

由于map和set的底层是红黑树,所以接口中真正的难处是自增自减运算符的重载。

自增自减操作符应该用中序遍历的方式。

自增运算符重载

对结点的正向迭代器进行自增后,应该让迭代器更新到当前结点中序遍历的下一个结点。由于中序遍历的顺序是:左子树、根结点、右子树,结点是针对根结点而言的,所以要找到当前结点的下一个结点,可以有以下几种情况:

  • 当前节点右子树不为空,表示中序遍历到当前结点还未结束,所以自增以后迭代器的位置应该更新到当前结点的右子树中中序遍历的第一个结点,即右子树中的最左节点;
  • 当前结点右子树为空,表示中序遍历到当前结点已经结束,那么自增以后的迭代器的位置应该存在于当前结点的祖先中,即孩子不在父亲右子树中的祖先。

中序遍历,只有当前结点的右子树访问完毕才是整棵子树访问完毕。

除此之外,自增以后的返回值是迭代器本身。

  • 前置++返回更新后的迭代器本身;
  • 后置++返回更新前的迭代器本身。
// 前置++
Self operator++()
{
    if(_node->_right) // 当前结点右子树不为空
    {
        Node* left = _node->_left; // 找当前结点右子树中最左结点
        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++(int)
{
    Node* ret = _node;
    if(_node->_right) // 当前结点右子树不为空
    {
        Node* left = _node->_left; // 找当前结点右子树中最左结点
        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 *ret; // 返回更新前的迭代器
}
自减运算符重载

对结点进行自减操作后,迭代器应该更新到当前结点中序遍历的前一个结点。由于中序遍历的顺序是左子树、根结点、右子树,所以中序找它的前一个结点就需要反过来看:右子树、根结点、左子树,暂且称之为逆中序遍历。下面用“回走”表示这个反着的过程,有以下两种情况:

  • 当前节点左子树不为空,说明当前结点还没回走完毕,还要继续回走,自减以后的迭代器应该更新到当前结点逆中序遍历的第一个结点,即当前结点左子树中最右结点;
  • 当前结点左子树为空,说您当前结点已经回走完毕,自减以后的迭代器应该是存在于当前结点的祖先结点中,即将迭代器更新到孩子不在父亲的右子树中的祖先。

除此之外,自增以后的返回值是迭代器本身。

  • 前置–返回更新后的迭代器本身;
  • 后置–返回更新前的迭代器本身。
// 前置--
Self operator--()
{
    if(_node->_left) // 结点的左子树不为空
    {
        Node* right = _node->_left; // 找到当前结点左子树中最右结点
        while(right->_right)
        {
            right = right->_right;
        }
        _node = right;
        // 找到了
        _node = right; // 更新
    }
    else // 当前结点左子树为空
    {
        Node* cur = _node;
        Node* parent = cur->_parent;
        while(parent && parent->_left) // 找孩子不在父亲的右子树中的祖先
        {
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent; // 更新
    }
    return *this;
}

后置–依旧是同样的思路,只是要在就迭代器被修改前保存一份最后返回即可。

// 后置--
Self operator--(int)
{
    Node* ret = _node;
    if(_node->_left) // 结点的左子树不为空
    {
        Node* right = _node->_left; // 找到当前结点左子树中最右结点
        while(right->_right)
        {
            right = right->_right;
        }
        _node = right;
        // 找到了
        _node = right; // 更新
    }
    else // 当前结点左子树为空
    {
        Node* cur = _node;
        Node* parent = cur->_parent;
        while(parent && parent->_left) // 找孩子不在父亲的右子树中的祖先
        {
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent; // 更新
    }
    return *ret;
}

理论上实现了前置和后置自增自减操作符的重载,是可以实现双向迭代器的。

后置自增自减操作符的重载将会在反向迭代器中实现。

3.3 红黑树中新增begin()和end()

实现上面的正向迭代器以后,需要在红黑树的实现(RBTree类)中把那一大串包含了模板参数的迭代器定义为iterator

typedef __RBTreeIterator<T, T&, T*> iterator; // 定义正向迭代器

现在可以理解为什么迭代器类中之前要定义三种类型。

在红黑树中新增成员函数begin()、end():

  • begin():返回中序序列中第一个结点的正向迭代器,即最左结点;
  • end():返回中序序列中最后一个结点下一个位置的正向迭代器,可以直接用nullptr构造一个正向迭代器。
// 红黑树类
template<class K, class T, class KeyOfT>
class RBTree
{
  typedef RBTreeNode<T> Node; // 定义结点
public:
    typedef __RBTreeIterator<T, T&, T*> iterator; // 定义正向迭代器
    iterator begin()
    {
        Node* cur = _root;
        while(cur && cur->_left) // 找最左结点
        {
            cur = cur->_left;
        }
        return iterator(cur); // 构造一个迭代器并返回
    }
    iterator end()
    {
        return iterator(nullptr);
    }
private:
    Node *_root = nullptr;
};

3.4 补充

实现的缺陷

在STL中主要实现了正向迭代器,反向迭代器是复用正向迭代器的代码实现的。STL迭代器中,对end()位置的正向迭代器进行--操作,应该得到树中最后一个结点的正向迭代器。然而上面是用nullptr构造的end()迭代器。所以无法通过对nullptr进行--操作得到正确的结果。

在STL中,整棵红黑树的根结点之上还增加了一个头结点,它分别指向根结点、树的最左节点、树的最右结点。

那么在这样的结构中:

  • 实现begin():用头结点的左孩子构造一个正向迭代器;
  • 实现rbegin():用头结点的右孩子构造一个反向迭代器(实际上是复用正向迭代器);
  • 实现end()和rend():用有节点构造出正向和反向迭代器。

这样,就能实现对end()位置的正向迭代器进行--操作,得到树中最后一个结点的正向迭代器了。

本节的主要内容是用红黑树封装map和set,其中心还是熟练掌握模板和仿函数的用法、学习各种功能接口在红黑树上的逻辑思想,虽难度相当,但上面的实现方法更能体现出对红黑树结构的把握,至少没有破坏我们之前印象中红黑树的结构。

修改接口的返回值

这里的红黑树只实现了insert和find,要把返回值改成一个pair对象,它包含了结点的迭代器和bool返回值:

pair<iterator, bool> Insert(const T& data)
{
    // ...
    return make_pair(iterator(_root), true);
    // ...
    return make_pair(iterator(cur), false);
}

要把所有返回的地方都改成pair类型。

注意,在红黑树的插入操作之前,要保存这个新插入的结点的指针(newnode),因为后续cur可能会往上遍历祖先结点。

find接口只要返回结点的迭代器即可。

3.5 代码

// 正向迭代器(RBTree.h)
template<class T, class Ptr, class Ref>
struct __RBTreeIterator
{
    typedef RBTreeNode<T> Node;
    typedef __RBTreeIterator<T, Ptr, Ref> Self; // 正向迭代器的类型
    Node *_node;
    // 迭代器构造函数
    __RBTreeIterator(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;
            // 找到了
            _node = right; // 更新
        }
        else // 当前结点左子树为空
        {
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && parent->_left) // 找孩子不在父亲的右子树中的祖先
            {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent; // 更新
        }
        return *this;
    }
};
// 红黑树类(RBTree.h)
template<class K, class T, class KeyOfT>
class RBTree
{
  // ...
public:// [注意下面这个typedef]
    typedef __RBTreeIterator<T, T&, T*> iterator; // 定义正向迭代器
    // ...
};

注意:

  • 因为begin()和end()这样的迭代器要返回红黑树结点的指针,所以它们要被实现在红黑树的类中。为了方便,将这一长串typedefiterator。注意必须在public后定义,因为后续还要在红黑树的外部(在RBTree.h中)实现反向迭代器。
  • 前置和后置自增自减操作符的重载,返回值要使用引用类型,因为可能进行前置和后置自增自减操作后,可能要对这个对象修改。

4. 反向迭代器

反向迭代器实际上就是复用了正向迭代器的代码。因此反向迭代器可以认为是一个迭代器适配器

反向迭代器作为一个适配器,它的成员变量就是正向迭代器。反向迭代器的各种功能接口都是用调用正向迭代器的接口实现的。

4.1 定义正向迭代器模板参数类型

反向迭代器的成员变量是一个正向迭代器,而它接收的模板参数是一个正向迭代器(被typedef的iterator),但是在正向迭代器的实现中,它能接收三种类型参数,即template<class T, class Ref, class Ptr>,但是反向迭代器不知道传入的结点是引用还是指针类型,所以还需要再正向迭代器的实现中对引用和指针类型再定义迭代器类型。

typedef Ptr pointer; // 结点指针
typedef Ref reference; // 结点指针的引用

4.2 定义反向迭代器模板参数类型

在正向迭代器类中新增了指针和指针的引用的迭代器类型以后,在反向迭代器类中也要新增两个对应的类型:

typedef typename iterator::reference Ref; // 结点指针的引用
typedef typename iterator::pointer Ptr; // 结点指针

注意:**当我们使用一个类里面定义的类型时,必须用typename关键字告诉编译器这是一个类型,而不是某个变量名。**因为我们之前使用类名::访问成员的。

4.3 红黑树中新增rbegin()和rend()

反向迭代器类实现后,需要在红黑树中新增rbegin()和rend(),就像正向迭代器一样。

同样地,首先要在红黑树类中定义反向迭代器类:

typedef __RBTreeReverseIterator<iterator> reverse_iterator; // 定义反向迭代器

在红黑树中新增成员函数rbegin()、rend():

  • rbegin函数返回中序序列中最后一个结点的反向迭代器,即最右结点;
  • rend函数返回中序序列中第一个结点前一个位置的反向迭代器,用nullptr构造一个反向迭代器。
reverse_iterator rbegin()
{
    Node* right = _root;
    while (right&&right->_right) //寻找最右结点
    {
        right = right->_right;
    }
    return reverse_iterator(iterator(right));
}
reverse_iterator rend()
{
    return reverse_iterator(iterator(nullptr));
}

4.4 代码

// 红黑树类
template<class K, class T, class KeyOfT>
class RBTree
{
    typedef RBTreeNode<T> Node; // 定义结点
    public:
    typedef __RBTreeIterator<T, T&, T*> iterator; // 定义正向迭代器
    typedef __RBTreeReverseIterator<iterator> reverse_iterator; // 定义反向迭代器
  // ...
    reverse_iterator rbegin()
    {
        Node* right = _root;
        while (right&&right->_right) //寻找最右结点
        {
            right = right->_right;
        }
        return reverse_iterator(iterator(right));
    }
    reverse_iterator rend()
    {
        return reverse_iterator(iterator(nullptr));
    }
    private:
    Node *_root = nullptr;
};

5. 实现set

#include "RBTree.h"
namespace xy
{
    template<class K>
    class set
    {
        struct SetKeyOfT
        {
            const K& operator()(const K& key)
            {
                return key;         // 仿函数返回key
            }
        };
    public:
        typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; // 定义正向迭代器
        typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator; // 定义反向迭代器
        //插入函数
        pair<iterator, bool> insert(const K& key)
        {
            return _t.Insert(key);
        }
        // 迭代器相关
        iterator begin()
        {
            return _t.begin();
        }
        iterator end()
        {
            return _t.end();
        }
        reverse_iterator rbegin()
        {
            return _t.rbegin();
        }
        reverse_iterator rend()
        {
            return _t.rend();
        }
    private:
        RBTree<K, K, SetKeyOfT> _t;
    };
}

6. 实现map

STL中,map还重载了运算符[],对于操作[key]

  • 如果容器中存在key对应的value,则返回value的引用;
  • 如果容器中不存在key对应的value,则调用insert函数,插入一个键值对<key, value()>

注:

这个键值对<key, value()>中的value()可以认为调用vaule的类型对应的默认构造函数,比如value是int类型的,那么int()相当于给int类型升级为一个类了,那么它的默认构造函数就是int(),int的默认值为0。

如果value是自定义类型,会调用它自己的默认构造函数。

// []运算符重载
V& operator[](const K& key)
{
    pair<iterator, bool> ret = insert(make_pair(key, V()));
    return ret = ret.first->second;
}

实际上,内置类型如int是没有构造函数的,诸如type()这样的写法,是编译器给的一个语法糖,它让对象和变量的初始化变得简单且统一。因为对象是一个类实例化出来的,它要有构造函数构造,而内置类型的变量如int,直接赋值即可。例如insert函数接收值时,并不知道它是一个内置类型还是一个类,所以这个语法糖干脆把内置类型当做类使用。

个人觉得他们说的很好:int基本类型存在构造函数吗

事实上,STL的map也是这样实现的:

template <class Key, class T, ...>
typedef Key key_type;
typedef T data_type;  
T& operator[](const key_type& k) {
    return (*((insert(value_type(k, T()))).first)).second;
  }

map源码

6.1 代码

#include "RBTree.h"
namespace xy
{
    template<class K, class V>
    class map 
    {
        struct MapKeyOfT
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first; // 仿函数取出键值K并返回
            }
        };
    public:
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator; //定义正向迭代器
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //定义反向迭代器
        //插入函数
        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()));
            return ret = ret.first->second;
        }
        // 迭代器相关
        iterator begin()
        {
            return _t.begin();
        }
        iterator end()
        {
            return _t.end();
        }
        reverse_iterator rbegin()
        {
            return _t.rbegin();
        }
        reverse_iterator rend()
        {
            return _t.rend();
        }
    private:
        RBTree<K, pair<K, V>, MapKeyOfT> _t;
    };
}

7. 红黑树代码

其中正向和反向迭代器都塞到这里面了,原因是迭代器要使用红黑树的模版参数,而模板不支持分离编译。其中,红黑树只实现了insert和find接口,首先是它有点难度面试几乎不会问到,其次是自己积压的工作太多了,应该会在期末考试后补全删除接口。

#pragma once
#include <iostream>
using namespace std;
enum Color // 使用枚举
{
    RED,
    BLACK
};
// 红黑树结点类
//template<class K, class V> [旧]
template<class T>
struct RBTreeNode
{
    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    Color _col;                         // 颜色
    T _data;                            // 数据
    RBTreeNode(const T& data)
        : _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _data(data)
        , _col(RED)                 // 默认插入结点为红色
    {}
};
// 正向迭代器
template<class T, class Ptr, class Ref>
struct __RBTreeIterator
{
    typedef RBTreeNode<T> Node;
    typedef Ptr pointer; // 结点指针
    typedef Ref reference; // 结点指针的引用
    typedef __RBTreeIterator<T, Ptr, Ref> Self; // 正向迭代器的类型
    Node *_node;
    // 迭代器构造函数
    __RBTreeIterator(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;
            // 找到了
            _node = right; // 更新
        }
        else // 当前结点左子树为空
        {
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && parent->_left) // 找孩子不在父亲的右子树中的祖先
            {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent; // 更新
        }
        return *this;
    }
};
// 反向迭代器
template<class iterator>
struct __RBTreeReverseIterator
{
    typedef typename iterator::reference Ref; // 结点指针的引用
    typedef typename iterator::pointer Ptr; // 结点指针
    typedef __RBTreeReverseIterator<iterator> Self; // 反向迭代器类型
    iterator _it;  // 用正向迭代器对象构造
    // 构造函数
    __RBTreeReverseIterator(iterator it)
        :_it(it) // 用正向迭代器对象构造一个反向迭代器对象
    {}
    // 解引用操作符
    Ref operator*()
    {
        return *_it; // 返回正向迭代器指向的结点数据的引用
    }
    // 成员访问操作符
    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;
    }
    bool operator==(const Self& s) const
    {
        return _it == s._it;
    }
};
// 红黑树类
template<class K, class T, class KeyOfT>
class RBTree
{
    typedef RBTreeNode<T> Node; // 定义结点
public:
    typedef __RBTreeIterator<T, T&, T*> iterator; // 定义正向迭代器
    typedef __RBTreeReverseIterator<iterator> reverse_iterator; // 定义反向迭代器
    // 插入函数
    pair<iterator, bool> Insert(const T& data)
    {
        if (_root == nullptr)                   // 空树
        {
            _root = new Node(data);
            _root->_col = BLACK;
            return make_pair(iterator(_root), true);
        }
        KeyOfT kot;
        Node *cur = _root;
        Node *parent = nullptr;
        while (cur)                             // 迭代,找到插入位置
        {
            if (kot(data) < kot(cur->_data))      // 插入的值比key值小
            {
                parent = cur;
                cur = cur->_left;               // 往左走
            }
            else if (kot(data) > kot(cur->data)) // 插入的值比key值大
            {
                parent = cur;
                cur = cur->_right;              // 往右走
            }
            else                                // 找不到
            {
                return make_pair(iterator(cur), false);
            }
        }
                                                // 跳出循环,说明找到插入的位置了
        cur = new Node(data);                   // 将cur更新为新插入结点
        Node* newnode = cur;                    // 保存新插入结点以便最后返回
        if (kot(cur->_data) < kot(parent->_data)) // 新结点值比叶子(父)结点小
        {
            parent->_left = cur;                // 作为父结点的左孩子插入
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
                                                // 插入成功
                                                // 检查并调整颜色
        while (parent && parent->_col == RED)   // 父结点非空且为红,说明它是子树的根结点
        {
            Node *grandparent = parent->_parent;// 祖父结点
                                                // parent的位置分两种情况
            if (parent == grandparent->_left)   // (1). 父结点是祖父节点的左孩子
            {
                Node *uncle = grandparent->_right; // 叔叔就是祖父节点的另一个孩子
                if (uncle != nullptr && uncle->_col == RED) // 情况1:叔叔存在且为红
                {
                    parent->_col = BLACK;       // 父结点变黑
                    uncle->_col = BLACK;        // 叔叔结点变黑
                    grandparent->_col = RED;    // 祖父结点变红
                    cur = grandparent;
                    parent = cur->_parent;      // 继续向上处理
                }
                else                            // 跳出了上面的判断,有两种有效组合:叔叔为空,叔叔为黑
                {
                                                // 情况2:叔叔存在且为黑,右单旋+变色
                                                //     g    右旋       p
                                                //   p   u  -->   cur    g
                                                // cur                     u
                    if (cur == parent->_left)   // cur是parent的左子树
                    {
                        RotateR(grandparent);   // 以祖父结点为轴心右旋
                        parent->_col = BLACK;   // 父节点变黑
                        grandparent->_col = RED;// 祖父结点变黑
                    }
                    else                        // cur是parent的右子树
                    {
                                                // 情况3:
                                                //    g   左右旋     c
                                                //  p   u  -->    p   g
                                                //    c                 u
                        RotateR(grandparent);   // 以祖父结点为轴心右旋
                        grandparent->_col = RED;     // 父节点变黑
                        cur->_col = BLACK; // 祖父结点变黑
                    }
                    break;                      // 旋转后子树根节点变黑,停止向上调整
                }
            }
            else                                // (2). 父结点是祖父节点的右孩子,步骤相同
            {
                Node *uncle = grandparent->_left;
                if (uncle && uncle->_col == RED)
                {
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandparent->_col = RED;
                    cur = grandparent;
                    parent = cur->_parent;
                }
                else
                {
                    if (cur == parent->_left)
                    {
                        RotateRL(grandparent);
                        cur->_col = BLACK;
                        grandparent->_col = RED;
                    }
                    else
                    {
                        RotateL(grandparent);
                        grandparent->_col = RED;
                        parent->_col = BLACK;
                    }
                    break;
                }
            }
        }
        _root->_col = BLACK;                    // 不论根节点何种颜色,统一处理为黑色
        return make_pair(iterator(newnode), true);
    }
    // 查找函数
    iterator Find(const K& key)
    {
        Node* cur = _root;
        KeyOfT kot;
        while (cur)
        {
            if (key < kot(cur->_kv.first)) // key小于当前结点的key值
            {
                cur = cur->_left; // 在当前结点的左子树中查找
            }
            else if (key > kot(cur->_kv.first)) // key大于当前结点的key值
            {
                cur = cur->_right; // 在当前结点的右子树中查找
            }
            else // 找到了
            {
                return iterator(cur); // 返回当前结点的迭代器
            }
        }
        return end(); // 查找失败
    }
private:
    // 右单旋函数
    void RotateR(Node *parent)
    {
        Node *subL = parent->_left;
        Node *subLR = subL->_right;
        Node *pParent = parent->_parent;        // 保存父结点的父结点
        parent->_left = subLR;                  // 重建subLR和parent联系
        if (subLR != nullptr)
        {
            subLR->_parent = parent;
        }
        subL->_right = parent;                  // 重建subL和parent联系
        parent->_parent = subL;
        if (parent == _root)                    // 父结点为根结点,旋转后的subL作为根结点,无父结点
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (pParent->_left == parent)
            {
                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->_right = subRL;                 // 重建subRL和parent联系
        if (subRL != nullptr)
        {
            subRL->_parent = parent;
        }
        subR->_left = parent;                   // 重建subR和parent联系
        parent->_parent = subR;
        if (parent == _root)                    // 父结点为根结点,旋转后的subR作为根结点,无父结点
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (pParent->_left == parent)
            {
                pParent->_left = subR;
            }
            else
            {
                pParent->_right = subR;
            }
            subR->_parent = pParent;
        }
    }
    // 左右双旋函数
    void RotateLR(Node *parent)
    {
        Node *subL = parent->_left;
        RotateL(subL);
        RotateR(parent);
    }
    // 右左双旋函数
    void RotateRL(Node *parent)
    {
        Node *subR = parent->_right;
        RotateR(subR);
        RotateL(parent);
    }
    // 迭代器相关
    iterator begin()
    {
        Node* cur = _root;
        while(cur && cur->_left) // 找最左结点
        {
            cur = cur->_left;
        }
        return iterator(cur); // 构造一个迭代器并返回
    }
    iterator end()
    {
        return iterator(nullptr);
    }
    reverse_iterator rbegin()
    {
        Node* right = _root;
        while (right&&right->_right) //寻找最右结点
        {
            right = right->_right;
        }
        return reverse_iterator(iterator(right));
    }
    reverse_iterator rend()
    {
        return reverse_iterator(iterator(nullptr));
    }
private:
    Node *_root = nullptr;
};
目录
相关文章
|
3天前
|
存储 自然语言处理 C++
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
11 0
【C++航海王:追寻罗杰的编程之路】set|map|multiset|multimap简单介绍
|
1天前
|
存储 安全 程序员
老程序员分享:List、Map、Set之间的联系与区别:
老程序员分享:List、Map、Set之间的联系与区别:
|
2天前
|
C++
【c++】map和set的封装
【c++】map和set的封装
5 0
|
2天前
|
存储 C++ 容器
【c++】set|map
【c++】set|map
4 0
|
3天前
|
编译器 C++ 容器
通过红黑树封装 map 和 set 容器
通过红黑树封装 map 和 set 容器
|
3天前
|
存储 C++ 容器
【C++】学习笔记——map和set
【C++】学习笔记——map和set
7 0
|
3天前
|
Dart
Dart之集合详解(List、Set、Map)
Dart之集合详解(List、Set、Map)
9 1
|
8天前
|
存储 JavaScript 前端开发
JavaScript进阶-Map与Set集合
【6月更文挑战第20天】JavaScript的ES6引入了`Map`和`Set`,它们是高效处理集合数据的工具。`Map`允许任何类型的键,提供唯一键值对;`Set`存储唯一值。使用`Map`时,注意键可以非字符串,用`has`检查键存在。`Set`常用于数组去重,如`[...new Set(array)]`。了解它们的高级应用,如结构转换和高效查询,能提升代码质量。别忘了`WeakMap`用于弱引用键,防止内存泄漏。实践使用以加深理解。
|
3天前
|
Go
go语言map、实现set
go语言map、实现set
10 0
|
3天前
|
存储 消息中间件 算法
Java中的集合框架详解:List、Set、Map的使用场景
Java中的集合框架详解:List、Set、Map的使用场景