【数据结构】红黑树封装map和set(上)

简介: 【数据结构】红黑树封装map和set(上)

1.前置知识


在之前的文章中,我们模拟实现了红黑树的插入。STL种的map和set底层都是红黑树,所以今天我们要做的事情就是利用我们之前模拟实现的红黑树来简化的封装一个map和set


首先,我们把之前的红黑树代码中用于检测的部分剔除掉,下面是剔除之后的代码:

#include <iostream>
enum Color { RED, BLACK };
template<class K, class V>
struct RBTreeNode
{
    pair<K,V> _kv;
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    Color _col;
    RBTreeNode(const pair<K,V> kv)
        :_kv(kv)
        ,_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_col(RED)
    {}
};
template<class K, class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
    bool Insert(const pair<K,V>& kv)
    {
        if(_root == nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            if(cur->_kv.first < kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(cur->_kv.first < kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(kv);
        cur->_col = RED;
        cur->_parent = parent;
        if(parent->_kv.first > cur->_kv.first)
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        while(parent && cur->_parent->_col == RED)
        {
            Node* grandfather = parent->_parent;
            if(parent == grandfather->_left)
            {
                Node* uncle = grandfather->_right;
                if(uncle && uncle->_col == RED)
                {
                    grandfather->_col = RED;
                    uncle->_col = parent->_col = BLACK;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    if(parent->_left == cur)
                    {
                        RotateR(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }
                    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(parent->_right == cur)
                    {
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }
                    else
                    {
                        RotateR(parent);
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        cur->_col = BLACK;
                    }
                    break;
                }
            }
        }
        _root->_col = BLACK;
        return true;
    }
    void RotateL(Node* parent)//左单旋
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* ppNode = parent->_parent;
        parent->_right = subRL;
        if(subRL)
            subRL->_parent = parent;
        subR->_left = parent;
        parent->_parent = subR;
        if(ppNode)
        {
            subR->_parent = ppNode;
            if(ppNode->_left == parent)
            {
                ppNode->_left = subR;
            }
            else//paren
            {
                ppNode->_right = subR;
            }
        }
        else
        {
            _root = subR;
            subR->_parent = nullptr;
        }
    }
    void RotateR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* ppNode = parent->_parent;
        if(subLR)
            subLR->_parent = parent;
        parent->_left = subLR;
        subL->_right = parent;
        parent->_parent = subL;
        if(ppNode)
        {
            subL->_parent = ppNode;
            if(ppNode->_left == parent)
                ppNode->_left = subL;
            else
                ppNode->_right = subL;
        }
        else
        {
            _root = subL;
            subL->_parent = nullptr;
        }
    }
private:
    Node* _root = nullptr;
};


我们要做的就是在此基础上改写部分代码,封装出map和set。


2.结构的改写与封装


现在直接让我们来封装肯定是一头雾水,所以首先我们来看一下STL源码是怎么实现的(这里参考的是SGI版本的stl3.0,与侯捷老师的STL源码剖析相对应)

这里简化一下源码,方便观看,去除掉一些不必要的东西

set的简化源码:

ba03c340d854eadac940f8906af743e6.png

map的简化源码:

434ba4340d66edbfe99ced92cdbbf9f5.png

所以,map和set在底层上都是同一个红黑树,只是传入的第二个参数不同,set传入的第二个参数只是Key,而map传入的是一个键值对

所以,这里对RBTree的模板做一下修改,变成template class RBTree,这里的第二个参数类型就是分辨map和set的依据。


2.1 map和set的结构框架

所以对于map和set来说,其类的结构如下

//set传入Key和Key
template<class K>
class set
{
public:
    //...
private:
    RBTree<K,K> _t;
};
//map传入Key和pair
template<class K, class V>
class map
{
public:
    //....
private:
    RBTree<K, pair<const K, V>> _t;
};


那么这里出现一个问题:既然通过第二个参数就已经能够区分了,为什么还要传第一个参数?

✅对于insert来说,确实不需要第一个参数,插入的值是Key或者pair即可,但是对于find函数,通过传入的参数找到值得时候,找的是第一个参数Key而不是第二个参数,所以这里还是需要一个单独得Key存在的。


2.2 RBTreeNode结构的改写

由于在RBTree层,不知道要实现的是map还是set,所以这里使用T来代替节点内存放的数据,改写后的节点结构如下

template<class T>
struct RBTreeNode
{
    T _data;//
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    Color _col;
    RBTreeNode(const T data)
        :_data(data)
        ,_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_col(RED)
    {}
};


2.3 RBTree结构改写(仿函数的引入)

由于RBTreeNode结构的改写,RBTree也需要相应的改变,

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


但是这样的话会出现一个问题:怎么找到插入的位置?


在没有改写之前,可以采用kv.first的方式访问到Key,然后找到插入的位置和查找等。但是现在数据只有_data这一个东西,没办法确定传入的是pair还是Key,所以这里封装一个仿函数KeyOfT随着类型一起传入。所以改进后的RBTree结构和插入函数如下:

//最终的RBTree结构
template<class K, class T, class KeyOfT>//增加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;//实例化一个仿函数(函数对象),这个仿函数的功能是拿到Key
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            if(kot(cur->_data) < kot(data))//通过仿函数拿到Key
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(kot(cur->_data) > kot(data))//通过仿函数拿到Key
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(data);
        cur->_col = RED;
        cur->_parent = parent;
        if(kot(parent->_data) > kot(cur->_data))//通过仿函数拿到Key
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        while(parent && cur->_parent->_col == RED)
        {
            Node* grandfather = parent->_parent;
            if(parent == grandfather->_left)
            {
                Node* uncle = grandfather->_right;
                if(uncle && uncle->_col == RED)
                {
                    grandfather->_col = RED;
                    uncle->_col = parent->_col = BLACK;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    if(parent->_left == cur)
                    {
                        RotateR(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }
                    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(parent->_right == cur)
                    {
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }
                    else
                    {
                        RotateR(parent);
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        cur->_col = BLACK;
                    }
                    break;
                }
            }
        }
        _root->_col = BLACK;
        return true;
    }
private:
    Node* _root = nullptr;
};


然后对于map和set,分别实现一个仿函数传入

//map
template<class K, class V>
class map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<const K, V>& kv)
        {
            return kv.first;
        }
    };
private:
    RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
//set
template<class K>
class set
{
    struct SetKeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
private:
    RBTree<K, K, SetKeyOfT> _t;
};


这个时候,针对set和map,就可以通过KeyOfT实例化出不同的仿函数(函数对象),从而按照不同的方式获取到Value中的Key。

现在针对之前已有的代码的修改就已经差不多啦,下面就要开始增加一点东西了。

相关文章
|
2月前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
97 2
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
77 2
|
1月前
|
开发者
除了交集运算,Set 类型还可以用于哪些数据结构的操作?
【10月更文挑战第30天】`Set`类型在数据结构操作方面提供了丰富的功能和便利,能够帮助开发者更高效地处理各种数据集合相关的任务,提高代码的简洁性和性能。
|
2月前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
34 1
|
2月前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
35 6
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
45 4
|
3月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
50 6
【数据结构】map&set详解
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
44 1
|
2月前
|
存储 自然语言处理 安全
【数据结构】Map的使用与注意事项
【数据结构】Map的使用与注意事项
35 1
|
2月前
|
存储
ES6中的Set数据结构的常用方法和使用场景
ES6中的Set数据结构的常用方法和使用场景