漫步STL-模拟实现map和set

BingWallpaper28

image-20220523132147126

1. 为什么使用红黑树

1.1 AVLTree

AVLTree树利用高度来控制平衡,是一种严格平衡

1.2 Red-Black Tree

红黑树通过颜色互斥来控制平衡,是一种近似平衡,它保证了最长路径不超过最短路径的2倍
因为这个不同,如果插入相同的节点,红黑树绝对会比AVL树旋转少的次数

1.2.1 红黑树特性回顾

⚡️ 节点非红即黑
⚡️ 树中没有连续的红色节点
⚡️ 每一条路上的黑色节点数量是相等的
⚡️ 根节点是黑色的
⚡️ 最短路径:全黑 ; 最长路线:一红一黑

2.适配key型和key-value型

有时候我们需要的是key型,比如说是set,有时候我们需要的是key-value型的参数,那就比如说是map,那么难道我要重新写一份代码来一个支持key,一个支持key-value吗,其实不用

2.1 统一传T类型

看看源码可以发现通过传入的key和value,key一定是传的key进去,value却会有不同

image-20220508131725410

那么我们这里选择将之前写的红黑树进行一些改造,来实现既可以支持map也可以支持set的结构,这是T可以传普通的key也可以传pair类型,根据需要传就可以了

template<class K,class T>
class RBTree
{
    typedef RBTreeNode<T> Node;
}

2.2 仿函数控制

在RBTree的某一些接口中,比如Find和Insert就需要去比较数据的大小,才能判断位置

在源码规定中,pair的比大小有时候不仅需要first还需要second

image-20220508140110200

image-20220508140150882

所以如果只是按照T类型去比较大小的话,pair的比较大小方式和key的比较方式是不同的,这样直接来的话会造成Insert可以正常插入但是Find的时候找不到,因为Find的参数是K类型,而不是T类型

pair<Node*, bool> Insert(const T& data)
Node* Find(const K& key)

这也是为什么RBTree这里我们要两个参数的模板,而不是只一个T就可以了,毕竟我们有时候要的是K类型,map的查找是用key来找,不是用piar来找的

template<class K,class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
	···

对于红黑树来说不知道自己存的数据类型存的是key还是pair,这是上层控制的,所以我们必须要一个高维度的泛型来控制

源码这里其实是通过在传一个上层函数来控制的

image-20220508141031237

所以我们在这里要使用一个仿函数放在模板中,仿函数的目的就是取出key

2.2.2 set_KeyOfT
template<class K, class V>
class set
{
    //内部类封装一个仿函数,用来返回key
    struct set_KeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
public:
    bool insert(const K& k)
    {
        _t.Insert(k);
        return true;
    }
private:
    RBTree<K, K,set_KeyOfT> _t;
};
2.2.2 map_KeyOfT
template<class K, class V>
class map
{
    //内部类封装一个仿函数,用来返回key
    struct map_KeyOfT
    {
        const K& operator()(const pair<const K,V>& kv)
        {
            return kv.first;
        }
    };
public:
    bool insert(const pair<const K, V>& kv)
    {
        _t.Insert(kv);
        return true;
    }
private:
    RBTree<K, pair<const K, V>,map_KeyOfT> _t;
};
2.2.3 RBTree template

红黑树的模板这里就可以更新了

template<class K,class T,class KeyOfT>
class RBTree
{
    typedef RBTreeNode<T> Node;
    ...

2.3 revised Insert

pair<Node*, bool> Insert(const T& data)
{
    if (_root == nullptr)
    {
        _root = new Node(data);
        _root->_col = BLACK;
        return make_pair(_root, 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 make_pair(cur, false);
        }
    }

    Node* newnode = new Node(data);
    newnode->_col = RED;
    if (kot(parent->_data) < kot(data))
    {
        parent->_right = newnode;
        newnode->_parent = parent;
    }
    else
    {
        parent->_left = newnode;
        newnode->_parent = parent;
    }
    cur = newnode;

    // 如果父亲存在,且颜色为红色就需要处理
    while (parent && parent->_col == RED)
    {
        Node* grandfather = parent->_parent;
        // 关键是看叔叔
        if (parent == grandfather->_left)
        {
            Node* uncle = grandfather->_right;
            // 情况1:uncle存在且为红
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;

                // 继续往上处理
                cur = grandfather;
                parent = cur->_parent;
            }
            else // 情况2+3:uncle不存在 uncle存在且为黑
            {
                // 情况2:单旋
                if (cur == parent->_left)
                {
                    _Rotate_R(grandfather);
                    grandfather->_col = RED;
                    parent->_col = BLACK;
                }
                else // 情况3:双旋
                {
                    _Rotate_L(parent);
                    _Rotate_R(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }

                break;
            }
        }
        else // parent == grandfather->_right
        {
            Node* uncle = grandfather->_left;
            // 情况1:
            if (uncle && uncle->_col == RED)
            {
                uncle->_col = parent->_col = BLACK;
                grandfather->_col = RED;

                cur = grandfather;
                parent = cur->_parent;
            }
            else // 情况2:+ 情况3:
            {
                if (cur == parent->_right)
                {
                    _Rotate_L(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else // cur == parent->_left
                {
                    _Rotate_R(parent);
                    _Rotate_L(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                // 插入结束
                break;
            }
        }
    }

    _root->_col = BLACK;
    return make_pair(newnode, true);
}

2.5 revised Find

Node* Find(const K& key)
{
    KeyOfT kot;
    Node* cur = _root;
    while (cur)
    {
        if (kot(cur->_data) > key)
        {
            cur = cur->_left;
        }
        else if (kot(cur->_data) < key)
        {
            cur = cur->_right;
        }
        else
        {
            return cur;
        }
    }

    return nullptr;
}

image-20220508144626266

总结一下就是:

一个模板参数控制红黑树里面存key还是存pair,实现key结构和key_value结构

一个仿函数去控制值里面用什么去比较

3. 红黑树迭代器

一个容器必须的就是迭代器,红黑树也不能少,之前AVL树中没有实现,因为红黑树很类似,于是下面来实现一下红黑树的迭代器
而map和set的迭代器本质上用的都是红黑树的迭代器,因此模拟实现map和set的难点在于红黑树

image-20220509100512008

From 《STL源码剖析》 红黑树迭代器的源码结构关系

3.0 简单的基本结构

如下这些重载和构造实现起来比较简单,难的主要在后面

struct __TreeIterator  
{
    typedef RBTreeNode<T> Node;
    typedef __TreeIterator<T,  Ref, Ptr> Self;

    Node* _node;

    __TreeIterator(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;
    }
}

3.1 begin()与end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?

答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置,也就是说是一个哨兵位的头节点,不存数据
image-20220508194526783

当然也是可以不加头节点做的

typedef __TreeIterator < T, T&, T* > iterator;

iterator begin()
{
    Node* left = _root;
    while (left && left->_left)
    {
        left = left->_left;
    }

    return iterator(left);
}
iterator end()
{
    return iterator(nullptr);
}

3.2 operator++()

Situation寻找方式节点描述
当前节点的右树不为空右树中,中序遍历的第一个节点右子树最左节点无需描述
右子树等于空沿着三叉链往上走,找出孩子不是父亲右的那个祖先孩子不是父亲右的那个祖先因为cur的右为空,所以说明cur所在的子树已经访问完了,cur是parent右的话,说明parent也已经访问完了,继续往上找
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 = cur->_parent;
            parent = parent->_parent;
        }
        _node = parent;
    }
    return *this;
}

3.3 operator–()

访问顺序是 右子树 根 左子树

Situation寻找方式节点
左不为空左树中,中序遍历的最后节点左子树的最右节点
左为空沿着三叉链往上走,找出孩子不是父亲左的那个祖先孩子不是父亲左的那个祖先
Self& operator--()
{
    if (_node->_left)
    {
        Node* right = _node->_left;
        while (right->_right)
        {
            right = right->_right;
        }
        _node = right;
    }
    else
    {
        Node* cur = _node;
        Node* parent = cur->_parent;
        while (parent && cur==parent->_left)
        {
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent;
    }
    return *this;
}

3.4 反向迭代器

反向迭代器可以利用一个正向迭代器来适配实现,这里就不采用迭代器萃取,因为太麻烦,所以说这里的反向迭代器采取的是一个迭代器适配器
反向迭代器的++相当于是正向迭代器的operator--,–就是正向迭代器的operator++

// 反向迭代器--迭代器适配器
template<class Iterator>
struct Reverse_Iterator
{
	typedef typename Iterator::reference Ref;
	typedef typename Iterator::pointer Ptr;
	typedef Reverse_Iterator<Iterator> Self;
	Iterator _it;

	ReverseIterator(Iterator it)
		:_it(it)
	{}

	Ref operator*()
	{
		return *_it;
	}

	Ptr operator->()
	{
		return _it.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;
	}
};

image-20220509120520988

3.5 rbegin()和rend()

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. 利用红黑树迭代器

map和set的迭代器关键在于直接利用红黑树的迭代器,所以说红黑树的迭代器要写好
注意这里的typename一定要加

4.1 set

//这里需要加一个typename因为这里的红黑树还没有实例化,没有实例化不能去类内找内置类型,所以说要加
typedef typename RBTree<K,K,set_KeyOfT>::iterator  iterator;
typedef typename RBTree<K, K, set_KeyOfT>::reverse_iterator reverse_iterator;

reverse_iterator rbegin()
{
    return _t.rbegin();
}

reverse_iterator rend()
{
    return _t.rend();
}

iterator begin()
{
    return _t.begin();
}

iterator end()
{
    return _t.end();
}

4.2 map

typedef  typename RBTree<K, pair<const K, V>, map_KeyOfT>::iterator  iterator;
typedef typename RBTree<K, pair<const K, V>,  map_KeyOfT>::reverse_iterator reverse_iterator;

reverse_iterator rbegin()
{
    return _t.rbegin();
}

reverse_iterator rend()
{
    return _t.rend();
}

iterator begin()
{
    return _t.begin();
}

iterator end()
{
    return _t.end();
}

5. map 和 set 模拟实现

这里有了迭代器,可以把pair中的first改成iterator去替换原来的Node*,比如在insert中的修改

也就是说其实map和set这里涉及的都是封装,本质上是把红黑树实现好,这里红黑树的erase没有实现,所以map和set这里也不封装了

5.1 map

template<class K, class V>
class map
{
    //内部类封装一个仿函数,用来返回key
    struct map_KeyOfT
    {
        const K& operator()(const pair<const K,V>& kv)
        {
            return kv.first;
        }
    };
public:
    typedef  typename RBTree<K, pair<const K, V>, map_KeyOfT>::iterator  iterator;
    typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::reverse_iterator reverse_iterator;

    reverse_iterator rbegin()
    {
        return _t.rbegin();
    }

    reverse_iterator rend()
    {
        return _t.rend();
    }

    iterator begin()
    {
        return _t.begin();
    }

    iterator end()
    {
        return _t.end();
    }

    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.first->second;
    }
private:
    RBTree<K, pair<const K, V>, map_KeyOfT> _t;
};

5.2 set

template<class K>
class set
{
    //内部类封装一个仿函数,用来返回key
    struct set_KeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
public:
    //这里需要加一个typename因为这里的红黑树还没有实例化,没有实例化不能去类内找内置类型,所以说要加
    typedef typename RBTree<K,K,set_KeyOfT>::iterator  iterator;
    typedef typename RBTree<K, K, set_KeyOfT>::reverse_iterator reverse_iterator;

    reverse_iterator rbegin()
    {
        return _t.rbegin();
    }

    reverse_iterator rend()
    {
        return _t.rend();
    }

    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,set_KeyOfT> _t;
};

代码在我的github仓库https://github.com/Allen9012/cpp/tree/main/STL/%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0map%E5%92%8Cset

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言之命至9012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值