【C++】set和map的模拟实现

本文详细介绍了红黑树的实现以及如何通过模板和仿函数模拟实现C++中的set和map,重点讲解了迭代器的实现、模版参数的作用以及如何在map中重载[]操作符。
摘要由CSDN通过智能技术生成

这篇文章将介绍set类和map类的模拟实现,set和map的底层都是红黑树来实现的。

通过模拟实现set和map,我们对模版、仿函数、复用会有更深刻的认识!

一、红黑树

如果想要详细的了解红黑树的实现,请看我的另一篇博客:​​​​​​​

我在这里直接给出实现set和map所需要的红黑树代码的框架(包括迭代器)

//表示红黑树节点颜色
enum Colour
{
    BLACK,
    RED
};

//红黑树的节点
template<class T>
struct RBTreeNode
{
public:
    RBTreeNode(const T& data, Colour colour = RED)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_data(data)
        ,_colour(colour)
    {}
public:
    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    T _data;
    Colour _colour;
};

//红黑树的迭代器
template<class T>
class __TreeIterator
{
public:
    typedef RBTreeNode<T> Node;
    typedef __TreeIterator<T> Self;
public:
    __TreeIterator(Node* node)
        :_node(node)
    {}
    
    T& operator*(){return _node->_data;}
    T* operator->(){return &(_node->_data);}
    bool operator!=(const Self& s){return _node != s._node;}    

    Self& operator++(){}//一会讲解
    Self& operator--(){}//不讲解,大家自己实现
private:
    Node* _node;
};

//红黑树本身
template<class K, class T, class KeyOfValue>
class RBTree
{
public:
    typedef RBTreeNode<T> Node;
    typedef __TreeIterator<T> iterator;
public:
    //迭代器
    iterator begin(){}
    iterator end(){}

    //插入
    pair<iterator, bool> Insert(const T& data){}//请看我写红黑树的博客
    
    //旋转处理
    //1.左单旋
    void RotateL(Node* parent){}//请看我写红黑树的博客
    //2.右单旋
    void RotateR(Node* parent){}//请看我写红黑树的博客
    
    //查找
    iterator Find(const K& data){}//请看我写红黑树的博客
    
private:
    Node* _root = nullptr;
};

在这个部分重点要讲的是红黑树迭代器的实现和模版参数的含义

1.迭代器

红黑树的迭代器和之前模拟的vector/list的本质都一样:通过封装节点的指针,重载++、--、

*、->等运算符来达到我们想要的效果。

对于红黑树的迭代器,我选择重点讲解++:如果++迭代器,就会指向中序的下一个

(1)当前节点(it)的右不为空:++以后就是it的右子树的最左节点

(2)当前节点(it)的右为空:++以后的节点一定是他的某一个祖先

具体是哪一个祖先呢?标记cur和他的parent,当cur是parent是左孩子时,parent就是我们要

找的++后的节点;当cur是parent是右孩子时,继续往上更新。

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 && cur == parent->_right)
        {
            cur = cur->_parent;
            parent = parent->_parent;
        }
        _node = parent;
    }
    
    //前置++:返回++之后的
    return *this;
}

2.模版参数

template<class K, class T, class KeyOfValue>

(1)class K:对set来说就是key的类型;对map来说就是key:value键值对中key的类型

(2)class T:表示红黑树节点里面存储的数据的类型。

(3)class KeyOfValue:仿函数,作用是得到要比较的元素。

        对于set:作用是将key中的key取出来,也就是直接返回。

        对于map:作用是将key:value键值对里的key取出来。

有了KeyOfValue这个仿函数,就可以复用同一棵树来实现set和map,减少了代码冗余。

因为我们在插入的过程中需要根据key来把数据插入到正确的位置。如果是set的话,节点数据域

就是一个值,可以直接进行比较;但如果是map的话,节点数据域是一个键值对,我们无法直接

根据这个键值对直接进行比较。我们的确可以实现两颗红黑树,一颗用于set、一颗用于map,

但是这样代码冗余。所以我们使用仿函数来提取出key,这样仅用一颗树就可以实现set和map。

3.begin和end

(1)iterator begin()的实现

//返回起始位置的迭代器
iterator begin()
{
    //找最小的:就是找最左边的
    Node* cur = _root;
    while (cur && cur->_left)
        cur = cur->_left;
    
    return iterator(cur);
}

(2)iterator end()的实现

//返回结束为止的迭代器
iterator end()
{
    //找最后一个
    return iterator(nullptr);
}

(3)STL中红黑树的结构

STL中的红黑树增加了一个头节点,目的是将end()放在头结点的位置。

而我们只是为了了解和学习原理,所以没有增加一个头节点。

二、模拟实现set

set的底层结构就是红黑树,因此在set内部封装一棵红黑树,然后包装一下接口即可将set实现

template<class K>
class set
{
public:
    //仿函数:作用是将key中的key取出来,也就是直接返回
    struct SetKeyOfValue
    {
        const K& operator()(const K& k)
        {
            return k;
        }
    };
    
    //迭代器  typename告诉编译器这是一个类型
    typedef typename RBTree<K, K, SetKeyOfValue>::iterator iterator;
    
    iterator begin(){return _t.begin();}
    iterator end(){return _t.end();}
    
    pair<iterator, bool> Insert(const K& k){return _t.Insert(k);}
private:
    RBTree<K, K, SetKeyOfValue> _t;
};

下图展示了各个模版参数之间的调用

这样我们就实现了一个自己的set,map的实现过程和set类似,下面我们来看map。

三、模拟实现map

map的底层结构就是红黑树,因此在map中封装一棵红黑树,然后包装一下接口即可将map实现

template<class K, class V>
class map
{
public:
    //仿函数:作用是将key:value键值对里的key取出来
    struct MapKeyOfValue
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
    
    //迭代器  typename告诉编译器这是一个类型
    typedef typename RBTree<K, pair<K, V>, MapKeyOfValue>::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<K, V>& kv){return _t.Insert(kv);}
private:
    RBTree<K, pair<K, V>, MapKeyOfValue> _t;
};

下图展示了各个模版参数之间的调用

​​​​​​​

与set不同的是,map中还有一个很重要的东西:重载[],下面讲解一下operator[]的实现。

(1)STL中insert的实现:返回 迭代器:布尔 的键值对

①iterator的含义:指向被插入元素的迭代器,如果元素已经存在,就是指向现有元素的迭代器

②bool的含义:插入成功为true,插入失败为flase

(2)实现operator[]:V& operator[](const K& key)

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

①ret.first指向了插入的节点

②(ret.first)->second利用了迭代器中 重载-> 取出来key:value中的value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值